Een integratie starten
- Vraag een beheerder om je applicatie aan te maken in Beheer → API en integraties. Elke applicatie heeft een afzonderlijke omgeving, contentscopes, kanalen, talen, auteurs, vervaldatum en aanvraagquotum.
- Bewaar de inloggegevens op je server. In productie gebruik je ft_live_-inloggegevens; in de sandbox gebruik je ft_test_. Stuur bij elke aanvraag Authorization: Bearer <credential> mee.
- Lees /capabilities en /content/taxonomies om de toegestane auteur- en onderwerp-ID's op te halen. Gebruik het schema in OpenAPI om je document voor te bereiden.
- Maak een concept, dien het in voor beoordeling en luister naar publicatie-events. Je automatisering kan bruikbare content voorbereiden terwijl een redacteur verantwoordelijk blijft voor de publicatie.
Basis-URL: https://www.formtruth.com/integrations/v1
Content en publicatie
Elk extern concept vereist goedkeuring door medewerkers van de huidige inhoud voordat het kan worden gepubliceerd. Een gedeclareerde methode voor menselijk auteurschap of een geautomatiseerde publicatie-instelling verleent geen redactionele goedkeuring.
Berichten, koopgidsen en FAQ's gebruiken één documentindeling en versiegeschiedenis. Gebruik /content/posts, /content/guides of /content/faqs met de bijbehorende toestemming. Applicaties hebben alleen toegang tot content die ze zelf beheren; een privéconcept lezen is iets anders dan de gepubliceerde website lezen.
Documenten bevatten een brontaal, een echte auteur, onderwerpen, bronnen en gelokaliseerde titels, slugs, samenvattingen en gestructureerde inhoudsblokken. Artikelen die met AI zijn ondersteund of geautomatiseerd, moeten hun lezersdoel en oorspronkelijke bijdrage vermelden. Vertaalbewerkingen mogen productfeiten, getallen, afbeeldings-ID's, links of structurele afmetingen niet wijzigen.
Elke schrijfactie vereist een Idempotency-Key. Updates, beoordeling, publicatie en intrekking vereist ook If-Match met de aangehaalde ETag. Het bewerken van beoordeelde inhoud maakt de betreffende goedkeuring ongeldig. Het wijzigen van de bron maakt betreffende vertalingen verouderd totdat ze zijn afgestemd en opnieuw beoordeeld.
Geüploade afbeeldingen blijven ongoedgekeurd totdat een redacteur de rechten en kwaliteit heeft gecontroleerd. Gepubliceerde releases zijn onveranderlijke momentopnamen. Door een release in te trekken, wordt deze verwijderd uit openbare content, feeds en zoekresultaten.
API-referentie
De onderstaande paden zijn relatief ten opzichte van de basis-URL. Dezelfde artikelroutes gelden voor gidsen en FAQ's; wijzig daarvoor de contentfamilie. Download OpenAPI voor volledige aanvraagschema's, vereiste scopes en reacties.
| Methode | Pad | Doel |
|---|
GET | /capabilities | Je effectieve rechten en limieten |
GET / POST | /content/posts | Je artikelen weergeven / een concept maken |
GET / PATCH | /content/posts/{id} | Het concept en de ETag lezen / een revisie opslaan |
PUT | /content/posts/by-external-id/{externalId} | Maak of werk een concept bij via je stabiele externe ID |
POST | /content/posts/{id}/submit-review | Indienen voor redactionele beoordeling |
POST | /content/posts/{id}/publications | Goedgekeurde talen publiceren of een release inplannen |
DELETE | /content/posts/{id}/publications/{releaseId} | Een release intrekken met een reden |
GET | /content/taxonomies | Actieve onderwerpen en toegestane auteurs |
POST | /content/imports | Maximaal 50 concepten in de wachtrij plaatsen en itemresultaten bekijken |
POST | /assets/uploads | Een afbeelding uploaden voor controle van rechten en kwaliteit |
POST | /assets/{id}/complete | De opgeslagen uploadstatus lezen |
GET | /jobs/{id} | Je importresultaten lezen |
GET / POST | /webhooks/subscriptions | Toon of registreer je HTTPS-ontvangers voor gebeurtenissen |
POST | /webhooks/subscriptions/{id} | De sleutel bijwerken, rouleren of levering uitschakelen |
Catalogus, bestellingen en voorraad zijn gereserveerd voor latere API-modules. Ze zijn nog niet beschikbaar via deze v1-gateway.
Fouten en nieuwe pogingen
Verstuur ongecomprimeerde UTF-8 JSON. Limieten voor de aanvraagbody zijn 2 MiB voor gewone schrijfacties, 8 MiB voor imports en 18 MiB voor gecodeerde afbeeldingsuploads; de body moet binnen tien seconden arriveren. Een 413 betekent dat de body te groot is, 415 betekent een niet-ondersteund formaat en 408 betekent dat een upload is verlopen.
Fouten retourneren error.code, error.message, veldgegevens indien van toepassing, retryable en request_id. Bewaar X-Request-Id voor ondersteuning. Een 401 betekent een probleem met de inloggegevens; 403 betekent onvoldoende rechten; 412 betekent een gewijzigde versie; 422 betekent ongeldige of niet-gereedstaande content; 428 betekent een ontbrekende voorwaarde.
Respecteer bij 429 Retry-After. Probeer bij een tijdelijke 503 of netwerkonderbreking opnieuw met begrensde back-off en DEZELFDE sleutel en invoer. Voor een andere payload is een nieuwe sleutel nodig. Verwerk bij 409 de vorige bewerking opnieuw in plaats van deze blind te herhalen. Imports retourneren resultaten per item via /jobs/{id}.
Ondertekende webhooks
Gebruik een openbare HTTPS-ontvanger op poort 443. Events omvatten content.review_required, content.published, content.withdrawn, content.publication_failed en import.completed. Verifieer de onbewerkte requestbody voordat je deze parseert.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Lees X-Formtruth-Timestamp, X-Formtruth-Event-Id en X-Formtruth-Signature. Weiger tijdstempels buiten vijf minuten. De signature-header kan tijdens het 24 uur durende venster voor sleutelroulatie meerdere door komma's gescheiden waarden van het type v1= bevatten; accepteer elke geldige handtekening voor een bewaarde sleutel.
Sla de event-ID atomair op voor deduplicatie, zet het werk in de wachtrij en retourneer snel 2xx. Levering kan opnieuw worden geprobeerd, dus een dubbel event mag niet tweemaal publiceren of kosten in rekening brengen. Bewaar het ondertekeningsgeheim afzonderlijk van de API-inloggegevens.
Codevoorbeelden
Bereid document.json voor met het KnowledgeDocument-schema en echte toegestane taxonomie-ID's. Stel FORMTRUTH_API_TOKEN veilig in op je server. Deze voorbeelden maken een concept; voor publicatie blijft de beoordelingsworkflow vereist.
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.