شروع یک یکپارچهسازی
- از یک مدیر بخواهید برنامه شما را در بخش مدیریت → API و یکپارچهسازیها ایجاد کند. هر برنامه محیط، دامنههای محتوا، کانالها، زبانها، نویسندگان، تاریخ انقضا و سهمیه درخواست جداگانهای دارد.
- اعتبارنامه را روی سرور خود نگه دارید. محیط عملیاتی از اعتبارنامههای ft_live_ و محیط آزمایشی از ft_test_ استفاده میکند. هدر Authorization: Bearer <credential> را همراه هر درخواست ارسال کنید.
- برای دریافت شناسههای نویسندگان و موضوعات مجاز، /capabilities و /content/taxonomies را بخوانید. برای آمادهسازی سند خود از شِمای OpenAPI استفاده کنید.
- یک پیشنویس ایجاد کنید، آن را برای بررسی ارسال کنید و رویدادهای انتشار را دریافت کنید. خودکارسازی شما میتواند محتوای مفیدی آماده کند، در حالی که مسئولیت انتشار همچنان بر عهده ویراستار است.
نشانی پایه: https://www.formtruth.com/integrations/v1
محتوا و انتشار
هر پیشنویس خارجی نیاز به تأیید کارکنان از محتوای فعلی آن قبل از انتشار دارد. روش نویسندگی انسانی اعلامشده یا تنظیم انتشار خودکار به معنای تأیید سردبیری نیست.
مقالهها، راهنماهای خریداران و پرسشهای متداول از یک قالب سند و تاریخچه نسخه مشترک استفاده میکنند. از /content/posts، /content/guides یا /content/faqs با مجوز متناظر استفاده کنید. برنامهها فقط میتوانند به محتوایی دسترسی داشته باشند که مالک آن هستند؛ خواندن یک پیشنویس خصوصی با خواندن وبسایت منتشرشده متفاوت است.
سندها شامل زبان مبدأ، نویسنده واقعی، موضوعات، منابع و عنوانها، نامکها، خلاصهها و بلوکهای بدنه ساختاریافته محلیسازیشده هستند. مقالههای تهیهشده با کمک هوش مصنوعی یا بهصورت خودکار باید هدف خواننده و سهم اصیل خود را مشخص کنند. ویرایشهای ترجمه نمیتوانند واقعیتهای محصول، اعداد، شناسههای تصویر، پیوندها یا ابعاد ساختاری را تغییر دهند.
هر نوشتن نیاز به Idempotency-Key دارد. بهروزرسانی، بررسی، انتشار و لغو نیز به If-Match با ETag نقلقولشده نیاز دارند. ویرایش محتوای بررسیشده تأیید مربوطه را باطل میکند. تغییر منبع ترجمههای مرتبط را تا هماهنگ و بررسی مجدد منسوخ میکند.
تصاویر بارگذاریشده تا زمانی که ویراستار حقوق و کیفیت آنها را بررسی کند، تأییدنشده باقی میمانند. انتشارهای منتشرشده، نماهای تغییرناپذیر هستند. پسگرفتن انتشار، آن را از محتوای عمومی، فیدها و نتایج جستوجو حذف میکند.
مرجع API
مسیرهای زیر نسبت به نشانی پایه هستند. با تغییر خانواده محتوا، همین مسیرهای مقاله برای راهنماها و پرسشهای متداول نیز کاربرد دارند. برای دریافت شِماهای کامل درخواست، دامنههای الزامی و پاسخها، OpenAPI را بارگیری کنید.
| روش | مسیر | هدف |
|---|
GET | /capabilities | مجوزها و محدودیتهای مؤثر بر شما |
GET / POST | /content/posts | فهرست مقالهها / ایجاد پیشنویس |
GET / PATCH | /content/posts/{id} | خواندن پیشنویس و ETag / ذخیره ویرایش |
PUT | /content/posts/by-external-id/{externalId} | ایجاد یا بهروزرسانی پیشنویس با شناسه خارجی پایدار شما |
POST | /content/posts/{id}/submit-review | ارسال برای بررسی تحریریه |
POST | /content/posts/{id}/publications | انتشار زبانهای تأییدشده یا زمانبندی انتشار |
DELETE | /content/posts/{id}/publications/{releaseId} | پسگرفتن انتشار با ذکر دلیل |
GET | /content/taxonomies | موضوعات فعال و نویسندگان مجاز |
POST | /content/imports | صفبندی حداکثر 50 پیشنویس و بررسی نتایج هر مورد |
POST | /assets/uploads | بارگذاری تصویر برای بررسی حقوق و کیفیت |
POST | /assets/{id}/complete | خواندن وضعیت پایدارشده بارگذاری |
GET | /jobs/{id} | خواندن نتایج واردسازی شما |
GET / POST | /webhooks/subscriptions | فهرست کردن یا ثبت دریافتکنندههای رویداد HTTPS شما |
POST | /webhooks/subscriptions/{id} | بهروزرسانی، چرخش کلید یا غیرفعالکردن ارسال |
کاتالوگ، سفارشها و موجودی برای ماژولهای API بعدی در نظر گرفته شدهاند. این موارد هنوز از طریق درگاه v1 در دسترس نیستند.
خطاها و تلاشهای مجدد
JSON فشردهنشده UTF-8 ارسال کنید. محدودیتهای بدنه درخواست 2 مگابایت برای نوشتنهای عادی، 8 مگابایت برای واردات و 18 مگابایت برای بارگذاری تصاویر رمزنگاریشده است؛ بدنه باید ظرف ده ثانیه برسد. 413 به معنای بزرگ بودن بیش از حد بدنه، 415 به معنای قالب پشتیبانینشده و 408 به معنای اتمام زمان بارگذاری است.
خطاها شامل error.code، error.message، جزئیات فیلد در صورت لزوم، retryable و request_id هستند. X-Request-Id را برای پشتیبانی نگه دارید. 401 به معنای مشکل اعتبارنامه است؛ 403 به معنای مجوز؛ 412 به معنای تغییر نسخه؛ 422 به معنای محتوای نامعتبر یا آمادهنشده؛ و 428 به معنای پیششرط مفقود است.
برای 429، Retry-After را رعایت کنید. در صورت بروز موقت 503 یا اختلال شبکه، با وقفه افزایشی محدود و با همان کلید و ورودی دوباره تلاش کنید. برای محتوای متفاوت به کلید جدیدی نیاز است. در 409، بهجای تکرار کورکورانه، عملیات قبلی را تطبیق دهید. واردسازیها نتایج هر مورد را از طریق /jobs/{id} برمیگردانند.
وبهوکهای امضاشده
از یک دریافتکننده عمومی HTTPS روی درگاه 443 استفاده کنید. رویدادها شامل content.review_required، content.published، content.withdrawn، content.publication_failed و import.completed هستند. پیش از تجزیه، بدنه خام درخواست را تأیید کنید.
HMAC-SHA256(secret, timestamp + "." + event_id + "." + raw_body)
X-Formtruth-Timestamp، X-Formtruth-Event-Id و X-Formtruth-Signature را بخوانید. مُهرهای زمانی خارج از بازه پنج دقیقه را رد کنید. هدر امضا ممکن است در طول پنجره چرخش کلید 24 ساعته، شامل چند مقدار ویرگولجداشده v1= باشد؛ هر امضای معتبری را که با کلید نگهداریشده شما مطابقت دارد بپذیرید.
شناسه رویداد را برای حذف موارد تکراری بهصورت اتمی ذخیره کنید، کار را در صف قرار دهید و 2xx را سریع برگردانید. ارسال ممکن است دوباره انجام شود، بنابراین یک رویداد تکراری نباید دوباره منتشر کند یا هزینهای دوباره ایجاد کند. راز امضا را جدا از اعتبارنامه API نگه دارید.
نمونههای کد
با استفاده از شِمای KnowledgeDocument و شناسههای واقعی و مجاز طبقهبندی، document.json را آماده کنید. FORMTRUTH_API_TOKEN را بهصورت امن روی سرور خود تنظیم کنید. این نمونهها یک پیشنویس ایجاد میکنند؛ انتشار همچنان به فرایند بررسی نیاز دارد.
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.