Mulakan integrasi
- Minta pentadbir mencipta aplikasi anda dalam Admin → API & integrations. Setiap aplikasi mempunyai persekitaran, skop kandungan, saluran, bahasa, pengarang, tempoh luput dan kuota permintaan yang berasingan.
- Simpan kelayakan pada pelayan anda. Pengeluaran menggunakan kelayakan ft_live_; kotak pasir menggunakan ft_test_. Hantar Authorization: Bearer <credential> bersama setiap permintaan.
- Baca /capabilities dan /content/taxonomies untuk mendapatkan ID pengarang dan topik yang dibenarkan. Gunakan skema dalam OpenAPI untuk menyediakan dokumen anda.
- Cipta draf, hantar untuk semakan dan dengarkan acara penerbitan. Automasi anda boleh menyediakan kandungan yang berguna sementara editor kekal bertanggungjawab terhadap penerbitan.
URL asas: https://www.formtruth.com/integrations/v1
Kandungan & penerbitan
Setiap draf luaran memerlukan kelulusan kakitangan terhadap kandungannya semasa sebelum diterbitkan. Kaedah penulisan manusia yang diisytiharkan atau tetapan penerbitan automatik tidak memberikan kelulusan editorial.
Catatan, panduan pembeli dan Soalan Lazim berkongsi satu format dokumen dan sejarah versi. Gunakan /content/posts, /content/guides atau /content/faqs dengan kebenaran yang sepadan. Aplikasi hanya boleh mengakses kandungan yang dimilikinya; membaca draf peribadi berbeza daripada membaca laman web yang diterbitkan.
Dokumen mengandungi bahasa sumber, pengarang sebenar, topik, sumber serta tajuk, slug, ringkasan dan blok kandungan berstruktur yang dilokalkan. Artikel berbantukan AI atau automatik mesti mengenal pasti matlamat pembacanya dan sumbangan asalnya. Suntingan terjemahan tidak boleh mengubah fakta produk, nombor, ID imej, pautan atau dimensi struktur.
Setiap penulisan memerlukan Idempotency-Key. Kemas kini, semakan, penerbitan dan penarikan balik juga memerlukan If-Match dengan ETag yang disertakan dalam tanda petikan. Penyuntingan kandungan yang telah disemak akan membatalkan kelulusan yang terjejas. Perubahan pada sumber akan menyebabkan terjemahan yang terjejas menjadi lapuk sehingga diselaraskan dan disemak semula.
Imej yang dimuat naik kekal belum diluluskan sehingga editor memeriksa hak dan kualitinya. Keluaran yang diterbitkan ialah petikan tidak boleh ubah. Penarikan balik mengalih keluar keluaran daripada kandungan awam, suapan dan penemuan carian.
Rujukan API
Laluan di bawah adalah relatif kepada URL asas. Laluan artikel yang sama digunakan untuk panduan dan Soalan Lazim dengan menukar keluarga kandungan. Muat turun OpenAPI untuk skema permintaan lengkap, skop yang diperlukan dan respons.
| Kaedah | Laluan | Tujuan |
|---|
GET | /capabilities | Kebenaran dan had berkuat kuasa untuk anda |
GET / POST | /content/posts | Senaraikan artikel anda / cipta draf |
GET / PATCH | /content/posts/{id} | Baca draf dan ETag / simpan semakan |
PUT | /content/posts/by-external-id/{externalId} | Cipta atau kemas kini draf menggunakan ID luaran stabil anda |
POST | /content/posts/{id}/submit-review | Hantar untuk semakan editorial |
POST | /content/posts/{id}/publications | Terbitkan bahasa yang diluluskan atau jadualkan keluaran |
DELETE | /content/posts/{id}/publications/{releaseId} | Tarik balik keluaran dengan sebab |
GET | /content/taxonomies | Topik aktif dan pengarang yang dibenarkan |
POST | /content/imports | Beratur sehingga 50 draf dan periksa hasil setiap item |
POST | /assets/uploads | Muat naik imej untuk semakan hak dan kualiti |
POST | /assets/{id}/complete | Baca status muat naik yang disimpan |
GET | /jobs/{id} | Baca hasil import anda |
GET / POST | /webhooks/subscriptions | Senaraikan atau daftarkan penerima acara HTTPS anda |
POST | /webhooks/subscriptions/{id} | Kemas kini, putar kunci atau nyahdayakan penghantaran |
Katalog, pesanan dan inventori disimpan untuk modul API yang akan datang. Semua ini belum tersedia melalui get laluan v1 ini.
Ralat & percubaan semula
Hantar JSON UTF-8 tanpa pemampatan. Had badan permintaan ialah 2 MiB untuk penulisan biasa, 8 MiB untuk import dan 18 MiB untuk muat naik imej yang dikodkan; badan mesti diterima dalam tempoh sepuluh saat. 413 bermaksud badan terlalu besar, 415 bermaksud format tidak disokong dan 408 bermaksud muat naik tamat masa.
Ralat mengembalikan error.code, error.message, butiran medan apabila berkenaan, retryable dan request_id. Simpan X-Request-Id untuk sokongan. 401 bermaksud masalah kelayakan; 403 bermaksud kebenaran; 412 bermaksud versi telah berubah; 422 bermaksud kandungan tidak sah atau belum bersedia; 428 bermaksud prasyarat tiada.
Untuk 429, patuhi Retry-After. Bagi 503 sementara atau gangguan rangkaian, cuba semula dengan pengunduran terhad dan kunci serta input yang SAMA. Muatan berbeza memerlukan kunci baharu. Apabila 409, selaraskan operasi terdahulu dan bukannya mengulanginya secara membuta tuli. Import mengembalikan hasil setiap item melalui /jobs/{id}.
Webhook bertandatangan
Gunakan penerima HTTPS awam pada port 443. Acara termasuk content.review_required, content.published, content.withdrawn, content.publication_failed dan import.completed. Sahkan isi kandungan permintaan mentah sebelum menghuraikannya.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Baca X-Formtruth-Timestamp, X-Formtruth-Event-Id dan X-Formtruth-Signature. Tolak cap masa di luar lima minit. Pengepala tandatangan mungkin mengandungi beberapa nilai v1= yang dipisahkan koma semasa tempoh putaran kunci selama 24 jam; terima mana-mana tandatangan sah untuk kunci yang anda simpan.
Simpan ID acara secara atomik untuk deduplikasi, masukkan kerja ke dalam baris gilir dan pulangkan 2xx dengan segera. Penghantaran boleh dicuba semula, jadi acara pendua tidak boleh menerbitkan atau mengenakan bayaran dua kali. Simpan rahsia tandatangan berasingan daripada kelayakan API.
Contoh kod
Sediakan document.json menggunakan skema KnowledgeDocument dan ID taksonomi sebenar yang dibenarkan. Tetapkan FORMTRUTH_API_TOKEN dengan selamat pada pelayan anda. Contoh ini mencipta draf; penerbitan masih memerlukan aliran kerja semakan.
import { readFile } from "node:fs/promises"
import { randomUUID } from "node:crypto"
const base = process.env.FORMTRUTH_URL || "https://www.formtruth.com"
const token = process.env.FORMTRUTH_API_TOKEN
if (!token) throw new Error("Set FORMTRUTH_API_TOKEN in a secure environment")
const document = JSON.parse(await readFile("document.json", "utf8"))
// Persist the key before calling. Reuse it only for an identical retry.
const key = process.env.FORMTRUTH_REQUEST_KEY || randomUUID()
console.log("Request key:", key)
const response = await fetch(base + "/integrations/v1/content/posts", {
method: "POST",
headers: { Authorization: "Bearer " + token, "Content-Type": "application/json", "Idempotency-Key": key },
body: JSON.stringify({ channel: "b2b", external_id: document.locales[document.source_locale].slug, document }),
})
const result = await response.json()
if (!response.ok) throw new Error(JSON.stringify(result.error))
console.log("Draft:", result.entry.id, "ETag:", response.headers.get("etag"))
// Creating a draft does not publish it. Submit for editorial review separately.
import json, os, uuid, urllib.request, urllib.error
base = os.environ.get("FORMTRUTH_URL", "https://www.formtruth.com")
token = os.environ["FORMTRUTH_API_TOKEN"]
with open("document.json", encoding="utf-8") as source:
document = json.load(source)
# Persist this key before sending; reuse for an identical retry.
key = os.environ.get("FORMTRUTH_REQUEST_KEY", str(uuid.uuid4()))
print("Request key:", key)
payload = {"channel": "b2b", "external_id": document["locales"][document["source_locale"]]["slug"], "document": document}
request = urllib.request.Request(base + "/integrations/v1/content/posts",
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"), method="POST",
headers={"Authorization": "Bearer " + token, "Content-Type": "application/json", "Idempotency-Key": key})
try:
with urllib.request.urlopen(request, timeout=45) as response:
result = json.load(response)
print("Draft:", result["entry"]["id"], "ETag:", response.headers.get("ETag"))
except urllib.error.HTTPError as error:
raise RuntimeError(error.read().decode("utf-8")) from error
# Creating a draft does not publish it. Submit for editorial review separately.
import { createHmac, timingSafeEqual } from "node:crypto"
export function verifyWebhook(rawBody, headers, secret, now = Date.now()) {
const timestamp = headers.get("x-formtruth-timestamp") || ""
const eventId = headers.get("x-formtruth-event-id") || ""
const signatures = (headers.get("x-formtruth-signature") || "").split(",")
if (!/^[0-9]{10}$/.test(timestamp) || !eventId || Math.abs(now - Number(timestamp) * 1000) > 300000) return false
const expected = createHmac("sha256", secret).update(timestamp + "." + eventId + ".").update(rawBody).digest()
return signatures.some(value => {
const part = value.trim()
if (!/^v1=[a-f0-9]{64}$/.test(part)) return false
const actual = Buffer.from(part.slice(3), "hex")
return actual.length === expected.length && timingSafeEqual(actual, expected)
})
}
// Verify the raw body BEFORE JSON parsing. Atomically record eventId in durable
// storage to deduplicate retries; enqueue accepted work, then return HTTP 2xx.
// During rotation both v1 signatures are sent for 24 hours.
import hashlib, hmac, re, time
def verify_webhook(raw_body, headers, secret, now=None):
timestamp = headers.get("x-formtruth-timestamp", "")
event_id = headers.get("x-formtruth-event-id", "")
signatures = headers.get("x-formtruth-signature", "").split(",")
current = time.time() if now is None else now
if not re.fullmatch(r"[0-9]{10}", timestamp) or not event_id or abs(current - int(timestamp)) > 300:
return False
signed = (timestamp + "." + event_id + ".").encode("utf-8") + raw_body
expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
return any(re.fullmatch(r"v1=[a-f0-9]{64}", part.strip()) and hmac.compare_digest(part.strip()[3:], expected) for part in signatures)
# Normalize header names to lowercase. Verify raw bytes before parsing JSON.
# Atomically persist event_id for deduplication, enqueue work, then return 2xx.