Započnite integraciju
- Zamolite administratora da izradi vašu aplikaciju u odjeljku Admin → API i integracije. Svaka aplikacija ima zasebno okruženje, opsege sadržaja, kanale, jezike, autore, rok važenja i kvotu zahtjeva.
- Pohranite vjerodajnicu na svojem poslužitelju. Produkcija upotrebljava vjerodajnice ft_live_, a sandbox upotrebljava ft_test_. Uz svaki zahtjev pošaljite Authorization: Bearer <credential>.
- Pročitajte /capabilities i /content/taxonomies kako biste dobili dopuštene ID-jeve autora i tema. Za pripremu dokumenta upotrijebite shemu u OpenAPI-ju.
- Izradite skicu, pošaljite je na pregled i pratite događaje objave. Vaša automatizacija može pripremiti koristan sadržaj, dok urednik i dalje odgovara za objavu.
Osnovni URL: https://www.formtruth.com/integrations/v1
Sadržaj i objava
Svaka vanjska skica zahtijeva odobrenje osoblja za njezin trenutni sadržaj prije objavljivanja. Deklarirani način ljudskog autorstva ili automatizirana postavka objavljivanja ne daju urednički pristanak.
Objave, vodiči za kupce i često postavljana pitanja dijele jedan format dokumenta i povijest verzija. Upotrijebite /content/posts, /content/guides ili /content/faqs s odgovarajućom dozvolom. Aplikacije mogu pristupiti samo sadržaju koji posjeduju; čitanje privatne skice razlikuje se od čitanja objavljenog web-mjesta.
Dokumenti sadržavaju izvorni jezik, stvarnog autora, teme, izvore te lokalizirane naslove, slugove, sažetke i strukturirane blokove tijela. Članci izrađeni uz pomoć umjetne inteligencije ili automatizirano moraju navesti cilj čitatelja i izvorni doprinos. Uređivanja prijevoda ne smiju mijenjati činjenice o proizvodu, brojeve, ID-jeve slika, poveznice ni strukturne dimenzije.
Svaki upis zahtijeva Idempotency-Key. Ažuriranja, recenzija, objavljivanje i povlačenje također zahtijevaju If-Match s navedenim ETag-om. Uređivanje recenziranog sadržaja poništava dotično odobrenje. Promjena izvora čini dotične prijevode zastarjelima dok se ne usklađuju i ponovno recenziraju.
Prenesene slike ostaju neodobrene dok urednik ne provjeri prava i kvalitetu. Objavljene verzije nepromjenjive su snimke stanja. Povlačenjem se objava uklanja iz javnog sadržaja, feedova i rezultata pretraživanja.
API referenca
Putanje u nastavku relativne su u odnosu na osnovni URL. Iste rute za članke primjenjuju se na vodiče i često postavljana pitanja promjenom obitelji sadržaja. Preuzmite OpenAPI za potpune sheme zahtjeva, obavezne opsege i odgovore.
| Metoda | Putanja | Svrha |
|---|
GET | /capabilities | Vaše učinkovite dozvole i ograničenja |
GET / POST | /content/posts | Izlistajte svoje članke / izradite skicu |
GET / PATCH | /content/posts/{id} | Pročitajte skicu i ETag / spremite reviziju |
PUT | /content/posts/by-external-id/{externalId} | Stvorite ili ažurirajte skicu pomoću vašeg stabilnog vanjskog ID-a |
POST | /content/posts/{id}/submit-review | Pošaljite na urednički pregled |
POST | /content/posts/{id}/publications | Objavite odobrene jezike ili zakažite objavu |
DELETE | /content/posts/{id}/publications/{releaseId} | Povucite objavu uz navođenje razloga |
GET | /content/taxonomies | Aktivne teme i dopušteni autori |
POST | /content/imports | Stavite do 50 skica u red čekanja i pregledajte rezultate stavki |
POST | /assets/uploads | Prenesite sliku na provjeru prava i kvalitete |
POST | /assets/{id}/complete | Pročitajte trajno spremljeni status prijenosa |
GET | /jobs/{id} | Pročitajte rezultate svojeg uvoza |
GET / POST | /webhooks/subscriptions | Navedite ili registrirajte svoje HTTPS prijemnike događaja |
POST | /webhooks/subscriptions/{id} | Ažurirajte, rotirajte ključ ili onemogućite isporuku |
Katalog, narudžbe i zalihe predviđeni su za kasnije module API-ja. Još nisu dostupni putem ovog pristupnika v1.
Pogreške i ponovni pokušaji
Šaljite nekomprimirani UTF-8 JSON. Ograničenja tijela zahtjeva su 2 MiB za obične upise, 8 MiB za uvoz i 18 MiB za kodirane prijenose slika; tijelo mora stići u roku od deset sekundi. 413 znači da je tijelo preveliko, 415 znači nepodržani format, a 408 znači da je prijenos istekao.
Pogreške vraćaju error.code, error.message, pojedinosti o poljima kada je primjenjivo, retryable i request_id. Za podršku sačuvajte X-Request-Id. 401 znači problem s vjerodajnicom; 403 znači problem s dozvolom; 412 znači promijenjenu verziju; 422 znači nevažeći ili nepripremljeni sadržaj; 428 znači da nedostaje preduvjet.
Za 429 poštujte Retry-After. U slučaju privremenog 503 ili mrežnog prekida ponovite zahtjev uz ograničeno postupno povećavanje čekanja te ISTI ključ i ulazne podatke. Drugačiji sadržaj zahtjeva traži novi ključ. U slučaju 409 uskladite prethodnu operaciju umjesto da je naslijepo ponavljate. Uvozi vraćaju rezultate po stavkama putem /jobs/{id}.
Potpisani webhookovi
Upotrijebite javni HTTPS prijamnik na portu 443. Događaji uključuju content.review_required, content.published, content.withdrawn, content.publication_failed i import.completed. Provjerite izvorno tijelo zahtjeva prije njegova raščlanjivanja.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Pročitajte X-Formtruth-Timestamp, X-Formtruth-Event-Id i X-Formtruth-Signature. Odbijte vremenske oznake izvan pet minuta. Zaglavlje potpisa može sadržavati više vrijednosti oblika v1= odvojenih zarezima tijekom 24-satnog razdoblja rotacije ključa; prihvatite svaki valjani potpis za ključ koji ste zadržali.
Atomski spremite ID događaja radi uklanjanja duplikata, stavite obradu u red čekanja i brzo vratite 2xx. Isporuka se može ponoviti, stoga duplicirani događaj ne smije ponovno objaviti sadržaj ni dvaput naplatiti. Tajnu za potpisivanje čuvajte odvojeno od vjerodajnice API-ja.
Primjeri koda
Pripremite document.json koristeći shemu KnowledgeDocument i stvarne dopuštene ID-jeve taksonomije. Sigurno postavite FORMTRUTH_API_TOKEN na svojem poslužitelju. Ovi primjeri izrađuju skicu; za objavu je i dalje potreban tijek rada s pregledom.
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.