Începe o integrare
- Cere unui administrator să îți creeze aplicația în Admin → API și integrări. Fiecare aplicație are un mediu separat, domenii de conținut, canale, limbi, autori, termen de expirare și o cotă de solicitări.
- Păstrează credentialul pe serverul tău. Producția folosește credentiale ft_live_; mediul sandbox folosește ft_test_. Trimite Authorization: Bearer <credential> la fiecare solicitare.
- Citește /capabilities și /content/taxonomies pentru a obține ID-urile autorilor și subiectelor permise. Folosește schema din OpenAPI pentru a-ți pregăti documentul.
- Creează o schiță, trimite-o pentru revizie și urmărește evenimentele de publicare. Automatizarea ta poate pregăti conținut util, în timp ce un editor rămâne responsabil pentru lansare.
URL de bază: https://www.formtruth.com/integrations/v1
Conținut și publicare
Fiecare schiță externă necesită aprobarea personalului pentru conținutul său curent înainte de publicare. O metodă declarată de redactare umană sau o setare de publicare automată nu acordă aprobare editorială.
Articolele, ghidurile pentru cumpărători și întrebările frecvente folosesc același format de document și același istoric al versiunilor. Folosește /content/posts, /content/guides sau /content/faqs cu permisiunea corespunzătoare. Aplicațiile pot accesa doar conținutul pe care îl dețin; citirea unei schițe private diferă de citirea conținutului publicat pe site.
Documentele conțin o limbă sursă, un autor real, subiecte, surse și titluri, sluguri, rezumate și blocuri de conținut structurat localizate. Articolele asistate de AI sau automatizate trebuie să identifice obiectivul cititorului și contribuția originală. Modificările de traducere nu pot schimba informațiile despre produse, numerele, ID-urile imaginilor, linkurile sau dimensiunile structurale.
Fiecare scriere necesită un Idempotency-Key. Actualizările, revizuirea, publicarea și retragerea necesită de asemenea If-Match cu ETag între ghilimele. Editarea conținutului revizuit invalidează aprobarea afectată. Modificarea sursei face ca traducerile afectate să fie învechite până la aliniere și revizuire ulterioară.
Imaginile încărcate rămân neaprobate până când un editor verifică drepturile și calitatea. Lansările publicate sunt instantanee imuabile. Retragerea elimină lansarea din conținutul public, fluxuri și rezultatele căutării.
Referință API
Căile de mai jos sunt relative la URL-ul de bază. Aceleași rute pentru articole se aplică ghidurilor și întrebărilor frecvente prin schimbarea familiei de conținut. Descarcă OpenAPI pentru schemele complete ale solicitărilor, domeniile obligatorii și răspunsuri.
| Metodă | Cale | Scop |
|---|
GET | /capabilities | Permisiunile și limitele tale efective |
GET / POST | /content/posts | Listează articolele tale / creează o schiță |
GET / PATCH | /content/posts/{id} | Citește schița și ETag-ul / salvează o revizie |
PUT | /content/posts/by-external-id/{externalId} | Creează sau actualizează o schiță folosind ID-ul tău extern stabil |
POST | /content/posts/{id}/submit-review | Trimite pentru revizie editorială |
POST | /content/posts/{id}/publications | Publică limbile aprobate sau programează o lansare |
DELETE | /content/posts/{id}/publications/{releaseId} | Retrage o lansare cu un motiv |
GET | /content/taxonomies | Subiecte active și autori autorizați |
POST | /content/imports | Pune în coadă până la 50 schițe și verifică rezultatele elementelor |
POST | /assets/uploads | Încarcă o imagine pentru verificarea drepturilor și a calității |
POST | /assets/{id}/complete | Citește starea persistentă a încărcării |
GET | /jobs/{id} | Citește rezultatele importului tău |
GET / POST | /webhooks/subscriptions | Listează sau înregistrează receptorii tăi de evenimente HTTPS |
POST | /webhooks/subscriptions/{id} | Actualizează, rotește cheia sau dezactivează livrarea |
Catalogul, comenzile și inventarul sunt rezervate pentru module API ulterioare. Acestea nu sunt încă disponibile prin acest gateway v1.
Erori și reîncercări
Trimite JSON UTF-8 necompresat. Limitele corpului cererii sunt 2 MiB pentru scrieri obișnuite, 8 MiB pentru importuri și 18 MiB pentru încărcări de imagini codificate; corpul trebuie să sosească în zece secunde. Un 413 înseamnă că corpul este prea mare, 415 înseamnă un format neacceptat și 408 înseamnă că o încărcare a expirat.
Erorile returnează error.code, error.message, detalii despre câmpuri atunci când este cazul, retryable și request_id. Păstrează X-Request-Id pentru asistență. Un 401 înseamnă o problemă a credentialului; 403 înseamnă lipsa permisiunii; 412 înseamnă o versiune schimbată; 422 înseamnă conținut invalid sau nepregătit; 428 înseamnă o precondiție lipsă.
Pentru 429, respectă Retry-After. Pentru un 503 temporar sau o întrerupere a rețelei, reîncearcă folosind o perioadă de așteptare limitată și ACEEAȘI cheie și aceleași date de intrare. Un payload diferit necesită o cheie nouă. Pentru 409, reconciliază operațiunea anterioară în loc să o repeți orbește. Importurile returnează rezultate pentru fiecare element prin /jobs/{id}.
Webhook-uri semnate
Folosește un receptor HTTPS public pe portul 443. Evenimentele includ content.review_required, content.published, content.withdrawn, content.publication_failed și import.completed. Verifică corpul brut al solicitării înainte de a-l analiza.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Citește X-Formtruth-Timestamp, X-Formtruth-Event-Id și X-Formtruth-Signature. Respinge marcajele temporale din afara intervalului de cinci minute. Antetul semnăturii poate conține mai multe valori v1= separate prin virgulă în timpul ferestrei de rotație a cheii de 24 ore; acceptă orice semnătură validă pentru cheia păstrată.
Salvează atomic ID-ul evenimentului pentru deduplicare, pune operațiunea în coadă și returnează prompt 2xx. Livrarea poate fi reîncercată, astfel încât un eveniment duplicat nu trebuie să publice sau să taxeze de două ori. Păstrează secretul de semnare separat de credentialul API.
Exemple de cod
Pregătește document.json folosind schema KnowledgeDocument și ID-uri reale de taxonomie permise. Setează în siguranță FORMTRUTH_API_TOKEN pe serverul tău. Aceste exemple creează o schiță; publicarea necesită în continuare fluxul de revizie.
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.