Έναρξη ενσωμάτωσης
- Ζητήστε από έναν διαχειριστή να δημιουργήσει την εφαρμογή σας στο Admin → API & integrations. Κάθε εφαρμογή έχει ξεχωριστό περιβάλλον, πεδία περιεχομένου, κανάλια, γλώσσες, συγγραφείς, λήξη και ποσόστωση αιτημάτων.
- Αποθηκεύστε το διαπιστευτήριο στον διακομιστή σας. Η παραγωγή χρησιμοποιεί διαπιστευτήρια ft_live_· το sandbox χρησιμοποιεί ft_test_. Στείλτε Authorization: Bearer <credential> με κάθε αίτημα.
- Διαβάστε /capabilities και /content/taxonomies για να λάβετε τα επιτρεπόμενα ID συγγραφέων και θεμάτων. Χρησιμοποιήστε το σχήμα στο OpenAPI για να προετοιμάσετε το έγγραφό σας.
- Δημιουργήστε ένα προσχέδιο, υποβάλετέ το για αξιολόγηση και παρακολουθήστε συμβάντα δημοσίευσης. Η αυτοματοποίησή σας μπορεί να προετοιμάσει χρήσιμο περιεχόμενο ενώ ένας συντάκτης παραμένει υπεύθυνος για την κυκλοφορία.
Βασική διεύθυνση URL: https://www.formtruth.com/integrations/v1
Περιεχόμενο & δημοσίευση
Κάθε εξωτερικό προσχέδιο απαιτεί έγκριση προσωπικού του τρέχοντος περιεχομένου πριν από τη δημοσίευση. Μια δηλωμένη μέθοδος ανθρώπινης συγγραφής ή ρύθμιση αυτόματης δημοσίευσης δεν χορηγεί συντακτική έγκριση.
Αναρτήσεις, οδηγοί αγοραστών και FAQ μοιράζονται μία μορφή εγγράφου και ιστορικό εκδόσεων. Χρησιμοποιήστε /content/posts, /content/guides ή /content/faqs με την αντίστοιχη άδεια. Οι εφαρμογές μπορούν να έχουν πρόσβαση μόνο σε περιεχόμενο που τους ανήκει· η ανάγνωση ενός ιδιωτικού προσχεδίου διαφέρει από την ανάγνωση του δημοσιευμένου ιστότοπου.
Τα έγγραφα περιέχουν γλώσσα πηγής, πραγματικό συγγραφέα, θέματα, πηγές και μεταφρασμένους τίτλους, slugs, περιλήψεις και δομημένα μπλοκ σώματος. Άρθρα με υποβοήθηση AI ή αυτοματοποιημένα πρέπει να προσδιορίζουν τον στόχο τους προς τον αναγνώστη και την πρωτότυπη συνεισφορά τους. Οι μεταφραστικές επεξεργασίες δεν μπορούν να αλλάξουν γεγονότα προϊόντων, αριθμούς, ID εικόνων, συνδέσμους ή δομικές διαστάσεις.
Κάθε εγγραφή χρειάζεται ένα Idempotency-Key. Οι ενημερώσεις, ο έλεγχος, η δημοσίευση και η απόσυρση χρειάζονται επίσης If-Match με το ETag σε εισαγωγικά. Η επεξεργασία ελεγμένου περιεχομένου ακυρώνει την αντίστοιχη έγκριση. Η αλλαγή της πηγής καθιστά τις επηρεαζόμενες μεταφράσεις μη ενημερωμένες μέχρι την ευθυγράμμιση και τον έλεγχό τους εκ νέου.
Οι μεταφορτωμένες εικόνες παραμένουν μη εγκεκριμένες μέχρι ένας συντάκτης να ελέγξει δικαιώματα και ποιότητα. Οι δημοσιευμένες κυκλοφορίες είναι αμετάβλητα στιγμιότυπα. Η ανάκληση αφαιρεί την κυκλοφορία από δημόσιο περιεχόμενο, ροές και ανακάλυψη αναζήτησης.
Αναφορά API
Οι διαδρομές παρακάτω είναι σχετικές με τη βασική διεύθυνση URL. Οι ίδιες διαδρομές άρθρων ισχύουν για οδηγούς και FAQ αλλάζοντας την οικογένεια περιεχομένου. Κατεβάστε το 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 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}.
Υπογεγραμμένα webhooks
Χρησιμοποιήστε έναν δημόσιο παραλήπτη 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. Απορρίψτε χρονικές σημάνσεις εκτός πέντε λεπτών. Η κεφαλίδα υπογραφής μπορεί να περιέχει πολλαπλές τιμές v1= χωρισμένες με κόμματα κατά τη διάρκεια του παραθύρου εναλλαγής κλειδιού 24 ωρών· αποδεχτείτε οποιαδήποτε έγκυρη υπογραφή για το κλειδί που διατηρείτε.
Αποθηκεύστε ατομικά το ID συμβάντος για αποκαταδίπλωση, βάλετε τη δουλειά σε ουρά και επιστρέψτε 2xx άμεσα. Η παράδοση μπορεί να επαναληφθεί, επομένως ένα διπλό συμβάν δεν πρέπει να δημοσιεύσει ή να χρεώσει δύο φορές. Διατηρήστε το μυστικό υπογραφής ξεχωριστό από το διαπιστευτήριο API.
Παραδείγματα κώδικα
Προετοιμάστε το document.json χρησιμοποιώντας το σχήμα KnowledgeDocument και πραγματικά επιτρεπόμενα ID ταξινομίας. Ορίστε το 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.