Magsimula ng integrasyon
- Hilingin sa isang administrator na gawin ang iyong application sa Admin → API at mga integrasyon. May hiwalay na environment, saklaw ng nilalaman, channel, wika, may-akda, pag-expire at quota ng mga request ang bawat application.
- Itago ang credential sa iyong server. Gumagamit ang production ng mga ft_live_ credential; gumagamit ang sandbox ng ft_test_. Ipadala ang Authorization: Bearer <credential> sa bawat request.
- Basahin ang /capabilities at /content/taxonomies upang makuha ang mga pinapahintulutang ID ng may-akda at paksa. Gamitin ang schema sa OpenAPI upang ihanda ang iyong dokumento.
- Gumawa ng draft, isumite ito para sa pagsusuri at makinig sa mga event ng paglalathala. Makapaghahanda ang iyong awtomasyon ng kapaki-pakinabang na nilalaman habang nananatiling responsable ang editor sa release.
Batayang URL: https://www.formtruth.com/integrations/v1
Nilalaman at paglalathala
Ang bawat external draft ay nangangailangan ng pag-apruba ng staff sa kasalukuyang nilalaman nito bago ilabas. Ang ideklarang manual na paraan ng pagsusulat o automated na setting ng publikasyon ay hindi nagbibigay ng editorial approval.
Iisa ang format ng dokumento at kasaysayan ng bersyon ng mga post, gabay para sa mamimili at FAQ. Gamitin ang /content/posts, /content/guides o /content/faqs kasama ang kaukulang pahintulot. Mga nilalamang pagmamay-ari lamang nila ang maaaring ma-access ng mga application; iba ang pagbabasa ng pribadong draft sa pagbabasa ng inilathalang website.
Naglalaman ang mga dokumento ng pinagmulang wika, tunay na may-akda, mga paksa, source at mga naka-localize na pamagat, slug, buod at istrukturadong body block. Kailangang tukuyin ng mga artikulong tinulungan ng AI o awtomatikong ginawa ang layunin ng mambabasa at orihinal na ambag ng mga ito. Hindi maaaring baguhin ng mga saling edit ang mga katotohanan tungkol sa produkto, numero, image ID, link o mga sukat ng istruktura.
Ang bawat pagsusulat ay nangangailangan ng Idempotency-Key. Ang mga update, review, publikasyon at pag-withdraw ay nangangailangan din ng If-Match na may quoted na ETag. Ang pag-edit ng narebyu na nilalaman ay nagpapawalang-bisa sa apektadong pag-apruba. Ang pagbabago ng source ay gumagawang stale ang apektadong mga pagsasalin hanggang sa maisaayos at marebyu muli.
Mananatiling hindi aprubado ang mga na-upload na larawan hanggang masuri ng editor ang mga karapatan at kalidad. Ang mga inilathalang release ay hindi nababagong snapshot. Inaalis ng pagbawi ang release mula sa pampublikong nilalaman, feed at pagtuklas sa paghahanap.
Sanggunian ng API
Relatibo sa batayang URL ang mga path sa ibaba. Pareho ang mga route ng artikulo para sa mga gabay at FAQ; palitan lamang ang pamilya ng nilalaman. I-download ang OpenAPI para sa kumpletong schema ng request, kinakailangang saklaw at mga tugon.
| Paraan | Path | Layunin |
|---|
GET | /capabilities | Iyong mga pahintulot at limitasyong epektibo |
GET / POST | /content/posts | Ilista ang iyong mga artikulo / gumawa ng draft |
GET / PATCH | /content/posts/{id} | Basahin ang draft at ETag / mag-save ng rebisyon |
PUT | /content/posts/by-external-id/{externalId} | Lumikha o mag-update ng draft gamit ang iyong stable external ID |
POST | /content/posts/{id}/submit-review | Isumite para sa pagsusuring editoryal |
POST | /content/posts/{id}/publications | Ilathala ang mga naaprubahang wika o mag-iskedyul ng release |
DELETE | /content/posts/{id}/publications/{releaseId} | Bawiin ang release na may dahilan |
GET | /content/taxonomies | Mga aktibong paksa at pinapahintulutang may-akda |
POST | /content/imports | Mag-queue ng hanggang 50 draft at suriin ang mga resulta ng item |
POST | /assets/uploads | Mag-upload ng larawan para sa pagsusuri ng mga karapatan at kalidad |
POST | /assets/{id}/complete | Basahin ang naka-save na katayuan ng upload |
GET | /jobs/{id} | Basahin ang mga resulta ng iyong import |
GET / POST | /webhooks/subscriptions | Ilista o irehistro ang iyong mga HTTPS event receiver |
POST | /webhooks/subscriptions/{id} | I-update, paikutin ang key o huwag paganahin ang delivery |
Inilalaan ang catalog, mga order at imbentaryo para sa mga susunod na module ng API. Hindi pa available ang mga ito sa gateway na v1.
Mga error at muling pagsubok
Magpadala ng uncompressed na UTF-8 JSON. Ang mga limitasyon sa request body ay 2 MiB para sa ordinaryong pagsusulat, 8 MiB para sa mga import at 18 MiB para sa mga naka-encode na pag-upload ng larawan; dapat dumating ang body sa loob ng sampung segundo. Ang 413 ay nangangahulugang masyadong malaki ang body, ang 415 ay nangangahulugang hindi suportadong format at ang 408 ay nangangahulugang nag-time out ang pag-upload.
Ibinabalik ng mga error ang error.code, error.message, mga detalye ng field kung naaangkop, retryable at request_id. Panatilihin ang X-Request-Id para sa suporta. Ang 401 ay nangangahulugan ng problema sa credential; ang 403 ay nangangahulugan ng pahintulot; ang 412 ay nangangahulugan ng nagbagong bersyon; ang 422 ay nangangahulugan ng hindi wasto o hindi pa handang nilalaman; ang 428 ay nangangahulugan ng nawawalang paunang kondisyon.
Para sa 429, sundin ang Retry-After. Para sa pansamantalang 503 o pagkaantala sa network, muling subukan gamit ang may hangganang backoff at ang PAREHONG key at input. Kailangan ng bagong key para sa ibang payload. Sa 409, itugma ang nakaraang operasyon sa kasalukuyang kalagayan sa halip na basta ulitin ito. Ibinabalik ng mga import ang mga resulta ng bawat item sa pamamagitan ng /jobs/{id}.
Mga nilagdaang webhook
Gumamit ng pampublikong HTTPS receiver sa port 443. Kabilang sa mga event ang content.review_required, content.published, content.withdrawn, content.publication_failed at import.completed. I-verify ang raw request body bago ito i-parse.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
Basahin ang X-Formtruth-Timestamp, X-Formtruth-Event-Id at X-Formtruth-Signature. Tanggihan ang mga timestamp na lampas sa limang minuto. Maaaring maglaman ang signature header ng maraming comma-separated na v1= value sa panahon ng 24-oras na palugit para sa pagpapalit ng key; tanggapin ang anumang wastong signature para sa iyong pinanatiling key.
Atomically i-save ang event ID para sa deduplication, i-queue ang gawain at ibalik kaagad ang 2xx. Maaaring subukang muli ang delivery, kaya hindi dapat maglathala o maningil nang dalawang beses ang duplicate na event. Panatilihing hiwalay ang signing secret sa API credential.
Mga halimbawa ng code
Ihanda ang document.json gamit ang KnowledgeDocument schema at mga tunay na pinapahintulutang taxonomy ID. Itakda nang ligtas ang FORMTRUTH_API_TOKEN sa iyong server. Gumagawa ng draft ang mga halimbawang ito; kailangan pa rin ang workflow ng pagsusuri para sa paglalathala.
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.