Start en integration
- Bed en administrator om at oprette din applikation i Admin → API og integrationer. Hver applikation har sit eget miljø, indholdsomfang, kanaler, sprog, forfattere, udløb og anmodningskvote.
- Opbevar legitimationsoplysningerne på din server. Produktion bruger ft_live_-legitimationsoplysninger; sandkasse bruger ft_test_-legitimationsoplysninger. Send Authorization: Bearer <credential> med hver anmodning.
- Læs /capabilities og /content/taxonomies for at hente de tilladte forfatter- og emne-ID'er. Brug skemaet i OpenAPI til at forberede dit dokument.
- Opret et udkast, indsend det til gennemgang, og lyt efter publiceringshændelser. Din automatisering kan forberede nyttigt indhold, mens en redaktør fortsat er ansvarlig for udgivelsen.
Basis-URL: https://www.formtruth.com/integrations/v1
Indhold og publicering
Alle eksterne udkast kræver medarbejdergodkendelse af det aktuelle indhold før udgivelse. En angivet menneskelig forfatningsmetode eller automatiseret udgivelsesindstilling giver ikke redaktionel godkendelse.
Indlæg, køberguides og ofte stillede spørgsmål deler ét dokumentformat og én versionshistorik. Brug /content/posts, /content/guides eller /content/faqs med den tilsvarende tilladelse. Applikationer kan kun få adgang til indhold, de selv ejer; at læse et privat udkast er noget andet end at læse det publicerede websted.
Dokumenter indeholder et kildesprog, en rigtig forfatter, emner, kilder samt lokaliserede titler, slugs, resuméer og strukturerede indholdsblokke. AI-assisterede eller automatiserede artikler skal identificere deres læsermål og originale bidrag. Oversættelsesredigeringer må ikke ændre produktfakta, tal, billed-ID'er, links eller strukturelle dimensioner.
Hver skrivning kræver en Idempotency-Key. Opdateringer, gennemgang, udgivelse og tilbagetrækning kræver også If-Match med den citerede ETag. Redigering af gennemgået indhold ugyldiggør den berørte godkendelse. Ændring af kilden gør berørte oversættelser forældede, indtil de er tilpasset og gennemgået igen.
Uploadede billeder forbliver ikke-godkendte, indtil en redaktør kontrollerer rettigheder og kvalitet. Publicerede udgivelser er uforanderlige øjebliksbilleder. Tilbagetrækning fjerner udgivelsen fra offentligt indhold, feeds og søgning.
API-reference
Stierne nedenfor er relative til basis-URL'en. De samme artikelruter gælder for guides og ofte stillede spørgsmål, når indholdsfamilien ændres. Download OpenAPI for komplette skemaer for anmodninger, påkrævede omfang og svar.
| Metode | Sti | Formål |
|---|
GET | /capabilities | Dine effektive tilladelser og grænser |
GET / POST | /content/posts | Vis dine artikler / opret et udkast |
GET / PATCH | /content/posts/{id} | Læs udkastet og ETag / gem en revision |
PUT | /content/posts/by-external-id/{externalId} | Opret eller opdatér et udkast ved hjælp af dit stabile eksterne id |
POST | /content/posts/{id}/submit-review | Indsend til redaktionel gennemgang |
POST | /content/posts/{id}/publications | Publicer godkendte sprog, eller planlæg en udgivelse |
DELETE | /content/posts/{id}/publications/{releaseId} | Træk en udgivelse tilbage med en begrundelse |
GET | /content/taxonomies | Aktive emner og tilladte forfattere |
POST | /content/imports | Sæt op til 50 udkast i kø, og se resultaterne for hvert element |
POST | /assets/uploads | Upload et billede til gennemgang af rettigheder og kvalitet |
POST | /assets/{id}/complete | Læs den gemte uploadstatus |
GET | /jobs/{id} | Læs resultaterne af din import |
GET / POST | /webhooks/subscriptions | Vis eller registrér dine HTTPS-hændelsesmodtagere |
POST | /webhooks/subscriptions/{id} | Opdater, roter nøglen, eller deaktivér levering |
Katalog, ordrer og lager er reserveret til senere API-moduler. De er endnu ikke tilgængelige via denne v1-gateway.
Fejl og gentagelser
Send ukomprimeret UTF-8 JSON. Grænserne for request-bodyen er 2 MiB for almindelige skrivninger, 8 MiB for importer og 18 MiB for kodede billeduploads; bodyen skal være modtaget inden for ti sekunder. En 413 betyder, at bodyen er for stor, 415 betyder et format, der ikke understøttes, og 408 betyder, at en upload fik timeout.
Fejl returnerer error.code, error.message, feltoplysninger når det er relevant, retryable og request_id. Gem X-Request-Id til support. En 401 betyder et problem med legitimationsoplysningerne; 403 betyder manglende tilladelse; 412 betyder en ændret version; 422 betyder ugyldigt eller ikke-klart indhold; 428 betyder en manglende forudsætning.
Ved 429 skal du respektere Retry-After. Ved en midlertidig 503 eller en netværksafbrydelse skal du forsøge igen med begrænset backoff og den SAMME nøgle og det SAMME input. En anden nyttelast kræver en ny nøgle. Ved 409 skal du afstemme den tidligere handling i stedet for at gentage den ukritisk. Importer returnerer resultater for hvert element via /jobs/{id}.
Signerede webhooks
Brug en offentlig HTTPS-modtager på port 443. Hændelser omfatter content.review_required, content.published, content.withdrawn, content.publication_failed og import.completed. Kontrollér den rå anmodningskrop, før du fortolker den.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Læs X-Formtruth-Timestamp, X-Formtruth-Event-Id og X-Formtruth-Signature. Afvis tidsstempler, der ligger uden for fem minutter. Signaturheaderen kan indeholde flere kommaseparerede v1=-værdier under nøglens 24-timer lange rotationsvindue; acceptér enhver gyldig signatur for din bevarede nøgle.
Gem hændelses-ID'et atomisk til deduplikering, læg arbejdet i kø, og returnér 2xx hurtigt. Levering kan forsøges igen, så en dubleret hændelse må ikke publicere eller opkræve betaling to gange. Opbevar signeringshemmeligheden adskilt fra API-legitimationsoplysningerne.
Kodeeksempler
Forbered document.json ved hjælp af KnowledgeDocument-skemaet og rigtige tilladte taksonomi-ID'er. Indstil FORMTRUTH_API_TOKEN sikkert på din server. Disse eksempler opretter et udkast; publicering kræver stadig arbejdsgangen for gennemgang.
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.