Développeurs
Intégrez vos contenus à votre propre site
Les contenus approuvés dans CoText et publiés sur le canal Votre propre site arrivent sur votre site de deux façons : CoText envoie un webhook signé à votre site lors de la publication, ou votre site récupère les contenus via l'API de lecture. Vous pouvez utiliser les deux. Zapier, Make et n8n passent par les mêmes chemins.
Webhooks et vérification de signature
Lorsque vous ajoutez le canal, vous saisissez l'URL de webhook de votre site (https uniquement). CoText génère un secret de signature (whsec_…) et ne l'affiche qu'une seule fois. Chaque requête est signée au format Standard Webhooks : vous pouvez donc utiliser telles quelles les bibliothèques existantes (Node, Python, PHP, Go…).
webhook-id: l'identifiant de l'événement. Il reste identique d'une tentative à l'autre ; ne traitez pas un identifiant déjà traité.webhook-timestamp: secondes Unix. Rejetez les requêtes de plus de 5 minutes (attaques par rejeu).webhook-signature:v1,<base64>. Le texte signé est${webhook-id}.${webhook-timestamp}.${raw-body}, l'algorithme est HMAC-SHA256, et la clé correspond aux octets de la valeur base64 qui suit le préfixewhsec_. Pendant les 24 heures qui suivent le renouvellement d'un secret, deux signatures séparées par une espace sont envoyées.
Vérifiez la signature sur le corps brut, avant d'analyser le JSON :
import { createHmac, timingSafeEqual } from "node:crypto";
// secret : la valeur "whsec_…" que CoText affiche une seule fois lors de l'ajout du canal
export function verify(rawBody, headers, secret) {
const id = headers["webhook-id"];
const ts = headers["webhook-timestamp"];
const sigs = headers["webhook-signature"] ?? "";
if (!id || !ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = createHmac("sha256", key).update(`${id}.${ts}.${rawBody}`).digest();
// Pendant le renouvellement d'un secret, l'en-tête peut contenir deux signatures séparées par une espace : une seule correspondance suffit.
return sigs.split(" ").some((s) => {
const [v, b64] = s.split(",");
const got = Buffer.from(b64 ?? "", "base64");
return v === "v1" && got.length === expected.length && timingSafeEqual(got, expected);
});
}Corps
{
"type": "article.published", // article.updated · article.unpublished · ping
"timestamp": "2026-10-16T09:30:00.000Z",
"data": {
"article": {
"id": "7c0e…", // identifiant du contenu CoText : insérer ou mettre à jour selon celui-ci
"title": "Guide pour bien préparer son café",
"slug": "guide-preparation-cafe",
"status": "published", // source de vérité unique : "published" | "draft"
"excerpt": "…", "meta_description": "…", "focus_keyword": "…",
"tags": ["café"], "category": "Guide", "content_type": "guide",
"language": "fr", "language_name": "Français", "translation_group_id": "7c0e…",
"translations": [{ "language": "tr", "url": "https://…", "article_id": "9d41…", "channel_kind": "custom" }],
"html": "<h2>…</h2><p>…</p>", "markdown": "## …",
"canonical_url": null, "author_name": null,
"featured_image": { "url": "https://…/kapak.jpg", "alt": "…", "width": 1600, "height": 900, "mime": "image/jpeg", "credit": null, "ai": false },
"published_at": "2026-10-16T09:30:00.000Z", "version_id": "…"
}
}
}- Insérez ou mettez à jour selon
data.article.id(upsert) ; un événementarticle.unpublishedne contient que l'id. - Le type d'événement n'est qu'une indication ; consultez
data.article.statuspour connaître l'état de l'article. L'ordre de livraison n'est pas garanti. - Langues :
languageest le code de la langue du contenu (tr,en,de,fr,es;nullsi CoText ne la reconnaît pas) etlanguage_namele nom enregistré. Les versions linguistiques d'un même contenu partagent untranslation_group_id(l'identifiant de la source du groupe) ;translationsliste les autres versions déjà en ligne, avec leurs adresses (l'article_idde la source est égal àtranslation_group_id: utilisez-le pourx-default). Construisez vos lienshreflangà partir de ces données. Une version publiée plus tard ne renvoie pas les précédentes ; l'API de lecture fournit toujours la liste complète. - Image de couverture :
featured_imagevaut{ url, alt, width, height, mime, credit, ai }ounull.urlest une adresse publique permanente (affichez-la directement ou copiez le fichier). Sicreditn'est pasnull, affichez-le avec l'image ; pour les images générées par IA,aivauttrue. - Données structurées :
structured_dataest une liste d'objets JSON-LD schema.org : unBlogPostinget, si le texte contient une FAQ (un h2 suivi de questions en h3 avec des réponses en paragraphes), unFAQPage. Placez chacun dans l'en-tête de la page, dans<script type="application/ld+json">. - Renvoyez un code 2xx (éventuellement avec
{ "url": "https://…" }pour indiquer l'adresse de l'article). Renvoyez 401 si la signature n'est pas valide. 410 signifie « point de terminaison supprimé » et n'est pas retenté ; 408, 429 et 5xx sont retentés ; les redirections ne sont pas suivies.
API de lecture
Le propriétaire de l'espace de travail crée une clé dans Paramètres > Clés API (ctx_live_…). La clé n'est affichée qu'une seule fois, à sa création ; CoText n'en conserve qu'une empreinte (hash). Envoyez l'en-tête Authorization: Bearer ctx_live_… avec chaque requête.
L'API ne renvoie que les contenus en ligne sur le canal Votre propre site, dans la version envoyée à ce canal : les modifications faites dans CoText après publication mais pas encore approuvées et envoyées n'apparaissent pas, et les contenus dépubliés du canal sortent de la liste.
GET /api/v1/articles: liste. Paramètres :limit(1–100, 20 par défaut),cursor(lenext_cursorde la réponse précédente),updated_since(ISO 8601, inclusif),order(desc|asc, selonupdated_at),channel(identifiant du canal),language(code de langue du contenu :tr,en,de,fr,es).- Chaque élément contient
language(code),language_name,translation_group_idettranslations: ses autres versions linguistiques servies par cette API, sous la forme{ language, slug, url }, quel que soit le filtrechanneloulanguage. Utilisez-les pour les lienshreflanget un sélecteur de langue. featured_image: la couverture de la version publiée, au même format que dans le webhook ({ url, alt, width, height, mime, credit, ai }), ounull.structured_data: la même liste JSON-LD schema.org que dans le webhook (BlogPosting, plusFAQPages'il y a une FAQ).GET /api/v1/articles/{slug}: un seul contenu ; l'identifiant du contenu fonctionne aussi à la place du slug.GET /api/v1/openapi.json: la définition OpenAPI 3.1 (sans clé).
curl https://cotext.net/api/v1/articles?limit=20 \
-H "Authorization: Bearer ctx_live_…"
{
"data": [
{
"id": "7c0e…", "title": "…", "slug": "…", "excerpt": "…", "meta_description": "…",
"tags": ["…"], "category": "…", "content_type": "blog",
"language": "fr", "language_name": "Français", "translation_group_id": "7c0e…",
"translations": [{ "language": "tr", "slug": "…", "url": "https://…" }],
"html": "<p>…</p>", "markdown": "…",
"published_at": "…", "updated_at": "…", "version_id": "…", "canonical_url": null,
"featured_image": { "url": "https://…/kapak.jpg", "alt": "…", "width": 1600, "height": 900, "mime": "image/jpeg", "credit": null, "ai": false }
}
],
"next_cursor": "WyIyMDI2LTEwLTE2…",
"has_more": true
}Pour synchroniser, utilisez order=asc&updated_since=<dernière synchronisation> et parcourez les pages jusqu'à ce que next_cursor soit vide. Pour être informé des contenus supprimés ou dépubliés, écoutez le webhook (article.unpublished) ou récupérez régulièrement la liste complète.
MCP : connectez votre assistant IA
CoText est un serveur MCP (Model Context Protocol, Streamable HTTP) : Claude, Cursor et d'autres clients MCP peuvent planifier, générer, modifier, approuver et programmer du contenu dans votre espace de travail. Tout passe par le flux de CoText : voix de marque, règles éditoriales apprises, crédits, versions et approbation liée à la version.
Le propriétaire crée une clé avec l'accès MCP : assistant IA dans Réglages > Clés API (content:write). L'assistant travaille avec les droits d'éditeur, au nom de la personne qui a créé la clé ; si elle n'est plus propriétaire ou éditrice, la clé cesse de fonctionner. Adresse : https://cotext.net/api/mcp. Dans Claude Code :
claude mcp add --transport http cotext https://cotext.net/api/mcp \
--header "Authorization: Bearer ctx_live_…"- Outils :
workspace_overview,get_brand_voice,list_content,get_content,create_idea,plan_quick_pack,generate_articles,get_batch,update_content,run_ai_editor,submit_for_review,approve_content,request_changes,schedule_publish,cancel_publish. - La génération passe par la file d'attente :
generate_articlesrenvoie un lot immédiatement et l'assistant le suit avecget_batch. L'approbation demande trois confirmations et la révision actuelle ; la publication n'envoie que la version approuvée. - Chaque écriture apparaît dans le journal d'activité comme assistant IA avec le préfixe de la clé. Les modifications de texte de l'assistant sont enregistrées comme une version distincte et CoText n'en tire pas de règles de marque. Révoquez la clé pour couper l'accès immédiatement.
- Dans claude.ai, ChatGPT et les autres applications compatibles avec les connecteurs MCP, ajoutez
https://cotext.net/api/mcpcomme connecteur : l'application vous envoie vers CoText, vous choisissez un ou plusieurs espaces de travail et autorisez l'accès ; l'assistant peut ensuite travailler dans chacune de leurs marques (list_workspaces, brand_id) (OAuth 2.1 avec PKCE ; aucune clé à copier). Les connexions apparaissent dans Réglages > Clés API et peuvent y être déconnectées.
Erreurs et limites
- Corps d'erreur :
{ "error": { "code": "…", "message": "…", "message_en": "…" } }.messageest dans la langue de la requête (en-têteAccept-Language: anglais, turc, allemand, espagnol, français, arabe ou japonais ; sinon anglais) ;message_enest toujours en anglais. Codes :unauthorized,key_revoked,key_expired,insufficient_scope,api_access_disabled,invalid_parameter,not_found,rate_limited. - 600 requêtes par heure et par clé. Les réponses contiennent
X-RateLimit-Limit,X-RateLimit-RemainingetX-RateLimit-Reset; au-delà de la limite, vous recevez un 429 etRetry-After. - Les points de terminaison acceptent les requêtes
GETde toute origine (CORS, sans cookies). Une clé utilisée dans le navigateur est visible par tous : utilisez-la côté serveur si possible ; elle ne lit que les contenus en ligne et ne peut rien modifier. Si vous pensez qu'elle a fuité, révoquez-la et créez-en une nouvelle.