Start en integrasjon
- Be en administrator om å opprette applikasjonen din i Admin → API og integrasjoner. Hver applikasjon har et separat miljø, innholdsområder, kanaler, språk, forfattere, utløpstid og forespørselskvote.
- Lagre legitimasjonen på serveren din. Produksjon bruker ft_live_-legitimasjon; sandkasse bruker ft_test_. Send Authorization: Bearer <credential> med hver forespørsel.
- Les /capabilities og /content/taxonomies for å hente tillatte forfatter- og emne-ID-er. Bruk skjemaet i OpenAPI for å klargjøre dokumentet.
- Opprett et utkast, send det til gjennomgang og lytt etter publiseringshendelser. Automatiseringen din kan klargjøre nyttig innhold mens en redaktør fortsatt er ansvarlig for publiseringen.
Basis-URL: https://www.formtruth.com/integrations/v1
Innhold og publisering
Hvert eksternt utkast krever medarbeidergodkjenning av gjeldende innhold før publisering. En deklarert manuell forfattermetode eller automatisert publiseringsinnstilling gir ikke redaksjonell godkjenning.
Innlegg, kjøperguider og vanlige spørsmål bruker samme dokumentformat og versjonshistorikk. Bruk /content/posts, /content/guides eller /content/faqs med den tilsvarende tillatelsen. Applikasjoner får bare tilgang til innhold de eier; å lese et privat utkast er noe annet enn å lese det publiserte nettstedet.
Dokumenter inneholder et kildespråk, en faktisk forfatter, emner, kilder og lokaliserte titler, slugger, sammendrag og strukturerte innholdsblokker. AI-assisterte eller automatiserte artikler må identifisere leserens mål og sitt originale bidrag. Oversettelsesredigeringer kan ikke endre produktfakta, tall, bilde-ID-er, lenker eller strukturelle dimensjoner.
Hver skriving krever en Idempotency-Key. Oppdateringer, gjennomgang, publisering og tilbaketrekking krever også If-Match med sitert ETag. Redigering av gjennomgått innhold ugyldiggjør berørt godkjenning. Endring av kilden gjør berørte oversettelser utdaterte inntil de er tilpasset og gjennomgått på nytt.
Opplastede bilder forblir ikke godkjent før en redaktør har kontrollert rettigheter og kvalitet. Publiserte lanseringer er uforanderlige øyeblikksbilder. Tilbaketrekking fjerner lanseringen fra offentlig innhold, feeder og søkesynlighet.
API-referanse
Banene nedenfor er relative til basis-URL-en. De samme artikkelrutene gjelder for guider og vanlige spørsmål når innholdsfamilien endres. Last ned OpenAPI for fullstendige forespørselsskjemaer, nødvendige områder og svar.
| Metode | Bane | Formål |
|---|
GET | /capabilities | Dine gjeldende tillatelser og grenser |
GET / POST | /content/posts | List artiklene dine / opprett et utkast |
GET / PATCH | /content/posts/{id} | Les utkastet og ETag / lagre en revisjon |
PUT | /content/posts/by-external-id/{externalId} | Opprett eller oppdater et utkast med din stabile eksterne ID |
POST | /content/posts/{id}/submit-review | Send til redaksjonell gjennomgang |
POST | /content/posts/{id}/publications | Publiser godkjente språk eller planlegg en lansering |
DELETE | /content/posts/{id}/publications/{releaseId} | Trekk tilbake en lansering med en begrunnelse |
GET | /content/taxonomies | Aktive emner og tillatte forfattere |
POST | /content/imports | Legg i kø opptil 50 utkast og se resultatene for hvert element |
POST | /assets/uploads | Last opp et bilde for gjennomgang av rettigheter og kvalitet |
POST | /assets/{id}/complete | Les statusen for den lagrede opplastingen |
GET | /jobs/{id} | Les importresultatene dine |
GET / POST | /webhooks/subscriptions | List opp eller registrer dine HTTPS-hendelsesmottakere |
POST | /webhooks/subscriptions/{id} | Oppdater, roter nøkkelen eller deaktiver levering |
Katalog, ordrer og lager er reservert for senere API-moduler. De er ikke tilgjengelige gjennom denne gatewayen i versjon v1 ennå.
Feil og nye forsøk
Send ukomprimert UTF-8 JSON. Grenser for forespørselskropp er 2 MiB for vanlige skrivinger, 8 MiB for importering og 18 MiB for kodede bildeopplastinger; kroppen må ankomme innen ti sekunder. En 413 betyr at kroppen er for stor, 415 betyr et format som ikke støttes, og 408 betyr at en opplasting fikk tidsavbrudd.
Feil returnerer error.code, error.message, feltdetaljer når det er aktuelt, retryable og request_id. Ta vare på X-Request-Id for brukerstøtte. En 401 betyr et problem med legitimasjonen; 403 betyr manglende tillatelse; 412 betyr en endret versjon; 422 betyr ugyldig eller ikke klart innhold; 428 betyr en manglende forutsetning.
For 429 må du følge Retry-After. Ved en midlertidig 503 eller et nettverksavbrudd prøver du på nytt med begrenset backoff og SAMME nøkkel og inndata. En annen nyttelast krever en ny nøkkel. Ved 409 må du avstemme den forrige operasjonen i stedet for å gjenta den ukritisk. Importer returnerer resultater for hvert element via /jobs/{id}.
Signerte webhooks
Bruk en offentlig HTTPS-mottaker på port 443. Hendelser omfatter content.review_required, content.published, content.withdrawn, content.publication_failed og import.completed. Kontroller den rå forespørselskroppen før du analyserer den.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Les X-Formtruth-Timestamp, X-Formtruth-Event-Id og X-Formtruth-Signature. Avvis tidsstempler utenfor fem minutter. Signaturhodet kan inneholde flere kommaseparerte v1=-verdier i løpet av 24-timersperioden for nøkkelrotasjon; godta enhver gyldig signatur for en nøkkel du fortsatt har.
Lagre hendelses-ID-en atomisk for deduplisering, legg arbeidet i kø og returner 2xx raskt. Levering kan forsøkes på nytt, så en duplikathendelse må ikke publisere eller belaste to ganger. Hold signeringshemmeligheten adskilt fra API-legitimasjonen.
Kodeeksempler
Forbered document.json ved hjelp av KnowledgeDocument-skjemaet og faktiske tillatte taksonomi-ID-er. Angi FORMTRUTH_API_TOKEN sikkert på serveren din. Disse eksemplene oppretter et utkast; publisering krever fortsatt arbeidsflyten for gjennomgang.
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.