Rozpocznij integrację
- Poproś administratora o utworzenie aplikacji w sekcji Administracja → API i integracje. Każda aplikacja ma osobne środowisko, zakresy treści, kanały, języki, autorów, termin wygaśnięcia i limit żądań.
- Przechowuj dane uwierzytelniające na swoim serwerze. Środowisko produkcyjne używa danych uwierzytelniających ft_live_, a piaskownica używa ft_test_. Wysyłaj Authorization: Bearer <credential> z każdym żądaniem.
- Odczytaj /capabilities i /content/taxonomies, aby uzyskać dozwolone identyfikatory autorów i tematów. Użyj schematu z OpenAPI, aby przygotować dokument.
- Utwórz wersję roboczą, prześlij ją do weryfikacji i nasłuchuj zdarzeń publikacji. Twoja automatyzacja może przygotować wartościowe treści, ale za publikację nadal odpowiada redaktor.
Bazowy adres URL: https://www.formtruth.com/integrations/v1
Treści i publikowanie
Każdy zewnętrzny szkic wymaga zatwierdzenia jego bieżącej treści przez personel przed publikacją. Zadeklarowana metoda tworzenia przez człowieka lub zautomatyzowane ustawienie publikacji nie przyznaje zatwierdzenia redakcyjnego.
Posty, poradniki dla kupujących i sekcje FAQ korzystają z jednego formatu dokumentu i historii wersji. Używaj /content/posts, /content/guides lub /content/faqs wraz z odpowiednim uprawnieniem. Aplikacje mogą uzyskiwać dostęp wyłącznie do treści, których są właścicielami; odczyt prywatnej wersji roboczej różni się od odczytu opublikowanej strony.
Dokumenty zawierają język źródłowy, rzeczywistego autora, tematy, źródła oraz zlokalizowane tytuły, slugi, podsumowania i uporządkowane bloki treści. Artykuły tworzone z pomocą AI lub automatycznie muszą określać cel czytelnika i oryginalny wkład. Edycje tłumaczenia nie mogą zmieniać faktów dotyczących produktu, liczb, identyfikatorów obrazów, linków ani wymiarów strukturalnych.
Każdy zapis wymaga Idempotency-Key. Aktualizacje, recenzja, publikacja i wycofanie wymagają również If-Match z cytowanym ETag. Edycja zrecenzowanej treści unieważnia dotyczące zatwierdzenie. Zmiana źródła powoduje dezaktualizację dotkniętych tłumaczeń do czasu ich wyrównania i ponownej recenzji.
Przesłane obrazy pozostają niezatwierdzone, dopóki redaktor nie sprawdzi praw i jakości. Opublikowane wydania są niezmiennymi migawkami. Wycofanie usuwa wydanie z publicznych treści, kanałów i wyników wyszukiwania.
Dokumentacja API
Poniższe ścieżki są względne względem bazowego adresu URL. Te same trasy artykułów dotyczą poradników i sekcji FAQ po zmianie rodziny treści. Pobierz OpenAPI, aby uzyskać pełne schematy żądań, wymagane zakresy i odpowiedzi.
| Metoda | Ścieżka | Cel |
|---|
GET | /capabilities | Twoje skuteczne uprawnienia i limity |
GET / POST | /content/posts | Wyświetl listę artykułów / utwórz wersję roboczą |
GET / PATCH | /content/posts/{id} | Odczytaj wersję roboczą i ETag / zapisz rewizję |
PUT | /content/posts/by-external-id/{externalId} | Utwórz lub zaktualizuj szkic według swojego stałego zewnętrznego ID |
POST | /content/posts/{id}/submit-review | Prześlij do weryfikacji redakcyjnej |
POST | /content/posts/{id}/publications | Opublikuj zatwierdzone wersje językowe lub zaplanuj publikację |
DELETE | /content/posts/{id}/publications/{releaseId} | Wycofaj publikację, podając przyczynę |
GET | /content/taxonomies | Aktywne tematy i uprawnieni autorzy |
POST | /content/imports | Dodaj do kolejki do 50 wersji roboczych i sprawdź wyniki poszczególnych elementów |
POST | /assets/uploads | Prześlij obraz do weryfikacji praw i jakości |
POST | /assets/{id}/complete | Odczytaj zapisany status przesyłania |
GET | /jobs/{id} | Odczytaj wyniki importu |
GET / POST | /webhooks/subscriptions | Wylistuj lub zarejestruj swoje odbiorniki zdarzeń HTTPS |
POST | /webhooks/subscriptions/{id} | Zaktualizuj, obróć klucz lub wyłącz dostarczanie |
Katalog, zamówienia i zapasy są zarezerwowane dla późniejszych modułów API. Nie są jeszcze dostępne przez bramę v1.
Błędy i ponowienia
Wysyłaj nieskompresowany JSON w UTF-8. Limity treści żądania to 2 MiB dla zwykłych zapisów, 8 MiB dla importów i 18 MiB dla zakodowanych przesyłań obrazów; treść musi dotrzeć w ciągu dziesięciu sekund. 413 oznacza, że treść jest za duża, 415 oznacza nieobsługiwany format, a 408 oznacza przekroczenie czasu przesyłania.
Błędy zwracają error.code, error.message, szczegóły pól, gdy mają zastosowanie, retryable oraz request_id. Zachowaj X-Request-Id na potrzeby pomocy technicznej. 401 oznacza problem z danymi uwierzytelniającymi; 403 oznacza brak uprawnień; 412 oznacza zmienioną wersję; 422 oznacza nieprawidłową lub niegotową treść; 428 oznacza brak warunku wstępnego.
W przypadku 429 przestrzegaj wartości Retry-After. W razie tymczasowego 503 lub przerwy w sieci ponów próbę z ograniczonym narastającym opóźnieniem, używając TEGO SAMEGO klucza i danych wejściowych. Inny zestaw danych wymaga nowego klucza. W przypadku 409 uzgodnij poprzednią operację zamiast bezmyślnie jej powtarzać. Importy zwracają wyniki dla poszczególnych elementów przez /jobs/{id}.
Podpisane webhooki
Użyj publicznego odbiornika HTTPS na porcie 443. Zdarzenia obejmują content.review_required, content.published, content.withdrawn, content.publication_failed i import.completed. Zweryfikuj surowe ciało żądania przed jego przetworzeniem.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Odczytaj X-Formtruth-Timestamp, X-Formtruth-Event-Id i X-Formtruth-Signature. Odrzucaj znaczniki czasu starsze lub nowsze niż pięć minut. Nagłówek podpisu może zawierać wiele rozdzielonych przecinkami wartości v1= w trakcie 24-godzinnego okresu rotacji klucza; zaakceptuj dowolny prawidłowy podpis dla zachowanego klucza.
Atomowo zapisz identyfikator zdarzenia na potrzeby deduplikacji, dodaj zadanie do kolejki i szybko zwróć 2xx. Dostarczanie może być ponawiane, dlatego zduplikowane zdarzenie nie może opublikować treści ani pobrać opłaty po raz drugi. Przechowuj klucz podpisu oddzielnie od danych uwierzytelniających API.
Przykłady kodu
Przygotuj document.json zgodnie ze schematem KnowledgeDocument i użyj rzeczywistych, dozwolonych identyfikatorów taksonomii. Bezpiecznie ustaw FORMTRUTH_API_TOKEN na swoim serwerze. Te przykłady tworzą wersję roboczą; publikacja nadal wymaga procesu weryfikacji.
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.