Bir entegrasyon başlatın
- Bir yöneticiden Admin → API ve entegrasyonlar bölümünde uygulamanızı oluşturmasını isteyin. Her uygulamanın ayrı bir ortamı, içerik kapsamları, kanalları, dilleri, yazarları, sona erme süresi ve istek kotası vardır.
- Kimlik bilgisini sunucunuzda saklayın. Üretim ortamında ft_live_ kimlik bilgileri, sandbox ortamında ft_test_ kullanılır. Her istekte Authorization: Bearer <credential> gönderin.
- İzin verilen yazar ve konu kimliklerini almak için /capabilities ve /content/taxonomies uç noktalarını okuyun. Belgenizi hazırlamak için OpenAPI şemasını kullanın.
- Bir taslak oluşturun, incelemeye gönderin ve yayınlama olaylarını dinleyin. Otomasyonunuz yararlı içerik hazırlayabilir; ancak yayınlamadan editör sorumludur.
Temel URL: https://www.formtruth.com/integrations/v1
İçerik ve yayınlama
Her harici taslak, yayınlanmadan önce mevcut içeriğinin personel onayını gerektirir. Bildirilen insan yazarlık yöntemi veya otomatik yayınlama ayarı, editöryal onay sağlamaz.
Gönderiler, alıcı rehberleri ve SSS'ler tek bir belge biçimini ve sürüm geçmişini paylaşır. İlgili izinle /content/posts, /content/guides veya /content/faqs kullanın. Uygulamalar yalnızca sahibi oldukları içeriğe erişebilir; özel bir taslağı okumak, yayınlanmış web sitesini okumaktan farklıdır.
Belgeler bir kaynak dili, gerçek bir yazar, konular, kaynaklar ve yerelleştirilmiş başlıklar, kısa açıklamalar ve yapılandırılmış gövde blokları içerir. Yapay zekâ destekli veya otomatik makaleler, okuyucu hedefini ve özgün katkıyı belirtmelidir. Çeviri düzenlemeleri ürün bilgilerini, sayıları, görsel kimliklerini, bağlantıları veya yapısal boyutları değiştiremez.
Her yazma işlemi bir Idempotency-Key gerektirir. Güncelleme, inceleme, yayınlama ve geri çekme ayrıca tırnak içinde ETag ile If-Match gerektirir. İncelenmiş içeriği düzenlemek ilgili onayı geçersiz kılar. Kaynağı değiştirmek, hizalanıp tekrar incelenene kadar etkilenen çevirileri güncel olmayan hale getirir.
Yüklenen görseller, bir editör hakları ve kaliteyi kontrol edene kadar onaylanmamış olarak kalır. Yayınlanmış sürümler değiştirilemez anlık görüntülerdir. Geri çekme işlemi yayını herkese açık içerikten, akışlardan ve arama sonuçlarından kaldırır.
API referansı
Aşağıdaki yollar temel URL'ye göredir. İçerik ailesini değiştirerek aynı makale rotaları rehberler ve SSS'ler için de geçerlidir. Eksiksiz istek şemaları, gerekli kapsamlar ve yanıtlar için OpenAPI'yi indirin.
| Yöntem | Yol | Amaç |
|---|
GET | /capabilities | Etkin izinleriniz ve sınırlarınız |
GET / POST | /content/posts | Makalelerinizi listeleyin / taslak oluşturun |
GET / PATCH | /content/posts/{id} | Taslağı ve ETag'i okuyun / revizyon kaydedin |
PUT | /content/posts/by-external-id/{externalId} | Sabit harici kimliğinizle bir taslak oluşturun veya güncelleyin |
POST | /content/posts/{id}/submit-review | Editoryal incelemeye gönderin |
POST | /content/posts/{id}/publications | Onaylanan dilleri yayınlayın veya bir yayını zamanlayın |
DELETE | /content/posts/{id}/publications/{releaseId} | Bir yayını gerekçesiyle geri çekin |
GET | /content/taxonomies | Etkin konular ve izin verilen yazarlar |
POST | /content/imports | 50 adede kadar taslağı sıraya alın ve öğe sonuçlarını inceleyin |
POST | /assets/uploads | Haklar ve kalite incelemesi için bir görsel yükleyin |
POST | /assets/{id}/complete | Kalıcı yükleme durumunu okuyun |
GET | /jobs/{id} | İçe aktarma sonuçlarınızı okuyun |
GET / POST | /webhooks/subscriptions | HTTPS etkinlik alıcılarınızı listeleyin veya kaydedin |
POST | /webhooks/subscriptions/{id} | Anahtarı güncelleyin, yenileyin veya teslimatı devre dışı bırakın |
Katalog, siparişler ve envanter sonraki API modülleri için ayrılmıştır. Bunlar henüz bu v1 ağ geçidi üzerinden kullanılamaz.
Hatalar ve yeniden denemeler
Sıkıştırılmamış UTF-8 JSON gönderin. İstek gövdesi sınırları sıradan yazmalar için 2 MiB, içe aktarmalar için 8 MiB ve kodlanmış görsel yüklemeleri için 18 MiB'dir; gövde on saniye içinde ulaşmalıdır. 413, gövdenin çok büyük olduğu, 415 desteklenmeyen bir format ve 408 bir yüklemenin zaman aşımına uğradığı anlamına gelir.
Hatalar error.code, error.message, uygulanabildiğinde alan ayrıntıları, retryable ve request_id döndürür. Destek için X-Request-Id değerini saklayın. Bir 401 kimlik bilgisi sorununu; 403 izin sorununu; 412 değişmiş bir sürümü; 422 geçersiz veya hazır olmayan içeriği; 428 ise eksik bir ön koşulu belirtir.
429 için Retry-After yönergesine uyun. Geçici bir 503 veya ağ kesintisi durumunda, AYNI anahtar ve girdiyi kullanarak sınırlı geri çekilmeyle yeniden deneyin. Farklı bir yük yeni bir anahtar gerektirir. 409 durumunda işlemi körü körüne tekrarlamak yerine önceki işlemi uzlaştırın. İçe aktarmalar, /jobs/{id} üzerinden öğe başına sonuç döndürür.
İmzalı web kancaları
443 bağlantı noktasında herkese açık bir HTTPS alıcısı kullanın. Olaylar content.review_required, content.published, content.withdrawn, content.publication_failed ve import.completed olaylarını içerir. Ayrıştırmadan önce ham istek gövdesini doğrulayın.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
X-Formtruth-Timestamp, X-Formtruth-Event-Id ve X-Formtruth-Signature değerlerini okuyun. Beş dakikanın dışındaki zaman damgalarını reddedin. İmza başlığı, 24 saatlik anahtar yenileme penceresi sırasında birden çok virgülle ayrılmış v1= değeri içerebilir; sakladığınız anahtar için geçerli imzalardan herhangi birini kabul edin.
Yinelenenleri önlemek için olay kimliğini atomik olarak kaydedin, işi kuyruğa alın ve 2xx değerini hızla döndürün. Teslimat yeniden denenebilir; bu nedenle yinelenen bir olay iki kez yayınlamamalı veya ücretlendirme yapmamalıdır. İmzalama sırrını API kimlik bilgisinden ayrı tutun.
Kod örnekleri
KnowledgeDocument şemasını ve gerçekten izin verilen sınıflandırma kimliklerini kullanarak document.json dosyasını hazırlayın. FORMTRUTH_API_TOKEN değerini sunucunuzda güvenli biçimde ayarlayın. Bu örnekler bir taslak oluşturur; yayınlama için yine de inceleme iş akışı gerekir.
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.