इंटीग्रेशन शुरू करें
- किसी एडमिनिस्ट्रेटर से Admin → API & integrations में आपका एप्लिकेशन बनाने के लिए कहें। प्रत्येक एप्लिकेशन का अलग वातावरण, सामग्री स्कोप, चैनल, भाषाएँ, लेखक, समाप्ति और अनुरोध कोटा होता है।
- क्रेडेंशियल अपने सर्वर पर सुरक्षित रखें। प्रोडक्शन में ft_live_ क्रेडेंशियल और सैंडबॉक्स में ft_test_ क्रेडेंशियल का उपयोग होता है। प्रत्येक अनुरोध के साथ Authorization: Bearer <credential> भेजें।
- अनुमत लेखक और विषय ID प्राप्त करने के लिए /capabilities और /content/taxonomies पढ़ें। अपना दस्तावेज़ तैयार करने के लिए OpenAPI में दिए गए स्कीमा का उपयोग करें।
- ड्राफ्ट बनाएँ, उसे समीक्षा के लिए सबमिट करें और प्रकाशन इवेंट सुनें। आपका ऑटोमेशन उपयोगी सामग्री तैयार कर सकता है, जबकि रिलीज़ की ज़िम्मेदारी संपादक की बनी रहती है।
बेस URL: https://www.formtruth.com/integrations/v1
सामग्री और प्रकाशन
हर बाह्य ड्राफ़्ट को रिलीज़ से पहले इसकी वर्तमान सामग्री की स्टाफ़ अनुमोदन की आवश्यकता होती है। घोषित मानव लेखन विधि या स्वचालित प्रकाशन सेटिंग संपादकीय अनुमोदन प्रदान नहीं करती।
पोस्ट, खरीदार मार्गदर्शिकाएँ और अक्सर पूछे जाने वाले प्रश्न एक ही दस्तावेज़ प्रारूप और संस्करण इतिहास साझा करते हैं। संबंधित अनुमति के साथ /content/posts, /content/guides या /content/faqs का उपयोग करें। एप्लिकेशन केवल अपने स्वामित्व वाली सामग्री तक पहुँच सकते हैं; निजी ड्राफ्ट पढ़ना प्रकाशित वेबसाइट पढ़ने से अलग है।
दस्तावेज़ों में स्रोत भाषा, वास्तविक लेखक, विषय, स्रोत तथा स्थानीयकृत शीर्षक, स्लग, सारांश और संरचित बॉडी ब्लॉक होते हैं। AI-सहायता प्राप्त या स्वचालित लेखों में अपने पाठक का उद्देश्य और मौलिक योगदान स्पष्ट होना चाहिए। अनुवाद संपादन उत्पाद संबंधी तथ्यों, संख्याओं, छवि ID, लिंक या संरचनात्मक आयामों को नहीं बदल सकते।
प्रत्येक राइट के लिए Idempotency-Key चाहिए। अपडेट, समीक्षा, प्रकाशन और वापसी के लिए उद्धृत ETag के साथ If-Match भी चाहिए। समीक्षित सामग्री को संपादित करने से प्रभावित अनुमोदन अमान्य हो जाता है। स्रोत बदलने से प्रभावित अनुवाद तब तक पुराने हो जाते हैं जब तक उन्हें संरेखित और फिर से समीक्षा नहीं किया जाता।
अपलोड की गई छवियाँ तब तक अस्वीकृत रहती हैं, जब तक कोई संपादक अधिकारों और गुणवत्ता की जाँच न कर ले। प्रकाशित रिलीज़ अपरिवर्तनीय स्नैपशॉट होती हैं। वापस लेने पर रिलीज़ सार्वजनिक सामग्री, फ़ीड और खोज परिणामों से हट जाती है।
API संदर्भ
नीचे दिए गए पथ बेस URL के सापेक्ष हैं। सामग्री परिवार बदलकर यही लेख रूट guides और FAQs पर भी लागू होते हैं। पूर्ण अनुरोध स्कीमा, आवश्यक स्कोप और प्रतिक्रियाओं के लिए OpenAPI डाउनलोड करें।
| विधि | पथ | उद्देश्य |
|---|
GET | /capabilities | आपकी प्रभावी अनुमतियाँ और सीमाएँ |
GET / POST | /content/posts | अपने लेखों की सूची बनाएँ / ड्राफ्ट बनाएँ |
GET / PATCH | /content/posts/{id} | ड्राफ्ट और ETag पढ़ें / संशोधन सहेजें |
PUT | /content/posts/by-external-id/{externalId} | अपने स्थिर बाह्य ID द्वारा ड्राफ़्ट बनाएं या अपडेट करें |
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 गेटवे के माध्यम से उपलब्ध नहीं हैं।
त्रुटियाँ और पुनः प्रयास
असंपीड़ित UTF-8 JSON भेजें। सामान्य राइट के लिए अनुरोध बॉडी सीमा 2 MiB है, आयात के लिए 8 MiB और एन्कोडेड छवि अपलोड के लिए 18 MiB; बॉडी दस सेकंड के भीतर आनी चाहिए। 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} के माध्यम से लौटाते हैं।
हस्ताक्षरित वेबहुक
443 पोर्ट पर सार्वजनिक HTTPS रिसीवर का उपयोग करें। इवेंट में 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= मान हो सकते हैं; अपनी सुरक्षित रखी गई कुंजी के लिए किसी भी मान्य सिग्नेचर को स्वीकार करें।
डुप्लिकेशन रोकने के लिए इवेंट ID को परमाणु रूप से सहेजें, काम को कतार में डालें और तुरंत 2xx लौटाएँ। डिलीवरी का पुनः प्रयास हो सकता है, इसलिए डुप्लिकेट इवेंट से दोबारा प्रकाशन या दोबारा शुल्क नहीं लगना चाहिए। साइनिंग सीक्रेट को API क्रेडेंशियल से अलग रखें।
कोड उदाहरण
KnowledgeDocument स्कीमा और वास्तविक अनुमत टैक्सोनॉमी ID का उपयोग करके 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.