Entwickler
Bring deine Inhalte auf deine eigene Website
Inhalte, die in CoText freigegeben und auf dem Kanal Deine eigene Website veröffentlicht werden, erreichen deine Website auf zwei Wegen: CoText sendet deiner Website beim Veröffentlichen einen signierten Webhook, oder deine Website ruft die Inhalte über die Lese-API ab. Du kannst beides nutzen. Zapier, Make und n8n nutzen dieselben Wege.
Webhooks und Signaturprüfung
Beim Hinzufügen des Kanals gibst du die Webhook-URL deiner Website ein (nur https). CoText erzeugt einen Signaturschlüssel (whsec_…) und zeigt ihn nur einmal an. Jede Anfrage wird im Format Standard Webhooks signiert, sodass du die fertigen Bibliotheken (Node, Python, PHP, Go …) direkt verwenden kannst.
webhook-id: die Ereignis-ID. Sie bleibt bei Wiederholungen gleich; verarbeite keine ID, die du schon behandelt hast.webhook-timestamp: Unix-Sekunden. Lehne Anfragen ab, die älter als 5 Minuten sind (Replay-Angriffe).webhook-signature:v1,<base64>. Der signierte Text ist${webhook-id}.${webhook-timestamp}.${raw-body}, der Algorithmus HMAC-SHA256, und der Schlüssel sind die Bytes des Base64-Werts nach dem Präfixwhsec_. In den 24 Stunden nach dem Erneuern eines Schlüssels kommen zwei durch Leerzeichen getrennte Signaturen an.
Prüfe die Signatur am unveränderten Body, bevor du das JSON parst:
import { createHmac, timingSafeEqual } from "node:crypto";
// secret: der Wert "whsec_…", den CoText beim Hinzufügen des Kanals einmal anzeigt
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();
// Während ein Schlüssel erneuert wird, kann der Header zwei durch Leerzeichen getrennte Signaturen enthalten: Ein Treffer genügt.
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);
});
}Body
{
"type": "article.published", // article.updated · article.unpublished · ping
"timestamp": "2026-10-16T09:30:00.000Z",
"data": {
"article": {
"id": "7c0e…", // CoText-Inhalts-ID: anhand dieser einfügen oder aktualisieren
"title": "Ein Leitfaden zum Kaffeebrühen",
"slug": "leitfaden-kaffee-bruehen",
"status": "published", // einzige verlässliche Quelle: "published" | "draft"
"excerpt": "…", "meta_description": "…", "focus_keyword": "…",
"tags": ["Kaffee"], "category": "Ratgeber", "content_type": "guide",
"language": "de", "language_name": "Deutsch", "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": "…"
}
}
}- Füge anhand von
data.article.idein oder aktualisiere (Upsert); einarticle.unpublished-Ereignis enthält nur dieid. - Der Ereignistyp ist nur ein Hinweis; den Zustand des Beitrags liefert
data.article.status. Die Reihenfolge der Zustellung ist nicht garantiert. - Sprachen:
languageist der Code der Inhaltssprache (tr,en,de,fr,es;null, wenn CoText sie nicht erkennt) undlanguage_nameder gespeicherte Name. Die Sprachversionen desselben Inhalts teilen einetranslation_group_id(die ID der Quelle der Gruppe);translationslistet die anderen bereits veröffentlichten Versionen mit ihren Adressen (diearticle_idder Quelle entspricht dertranslation_group_id; nutze sie fürx-default). Baue daraus deinehreflang-Links. Eine später veröffentlichte Version sendet die früheren nicht erneut; die Lese-API hat immer die vollständige Liste. - Titelbild:
featured_imageist{ url, alt, width, height, mime, credit, ai }odernull.urlist eine dauerhafte öffentliche Adresse (direkt einbinden oder die Datei kopieren). Istcreditnichtnull, zeige ihn mit dem Bild an; bei KI-generierten Bildern istaitrue. - Strukturierte Daten:
structured_dataist eine Liste von schema.org-JSON-LD-Objekten: einBlogPostingund, wenn der Text einen FAQ-Abschnitt hat (h2, darunter h3-Fragen mit Absatz-Antworten), einFAQPage. Setze jedes Objekt im Seitenkopf in<script type="application/ld+json">. - Antworte mit 2xx (optional mit
{ "url": "https://…" }, um die Adresse des Beitrags zu melden). Antworte mit 401, wenn die Signatur ungültig ist. 410 bedeutet „Endpunkt entfernt“ und wird nicht wiederholt; 408, 429 und 5xx werden wiederholt; Weiterleitungen werden nicht verfolgt.
Lese-API
Der Inhaber des Arbeitsbereichs erstellt unter Einstellungen > API-Schlüssel einen Schlüssel (ctx_live_…). Der Schlüssel wird nur einmal beim Erstellen angezeigt; CoText speichert nur einen Hash davon. Sende bei jeder Anfrage den Header Authorization: Bearer ctx_live_….
Die API liefert nur Inhalte, die auf dem Kanal Deine eigene Website live sind, in der an diesen Kanal gesendeten Version: Änderungen, die nach dem Veröffentlichen in CoText gemacht, aber noch nicht freigegeben und gesendet wurden, erscheinen nicht, und vom Kanal zurückgezogene Inhalte fallen aus der Liste.
GET /api/v1/articles: Liste. Abfrage:limit(1–100, Standard 20),cursor(dernext_cursorder vorherigen Antwort),updated_since(ISO 8601, einschließlich),order(desc|asc, nachupdated_at),channel(Kanal-ID),language(Code der Inhaltssprache:tr,en,de,fr,es).- Jeder Eintrag enthält
language(Code),language_name,translation_group_idundtranslations: die anderen Sprachversionen, die diese API ausliefert, als{ language, slug, url }– unabhängig vom Filterchanneloderlanguage. Nutze sie fürhreflang-Links und einen Sprachumschalter. featured_image: das Titelbild der veröffentlichten Version, im selben Format wie im Webhook ({ url, alt, width, height, mime, credit, ai }), odernull.structured_data: dieselbe schema.org-JSON-LD-Liste wie im Webhook (BlogPosting, bei FAQ-Abschnitt zusätzlichFAQPage).GET /api/v1/articles/{slug}: ein einzelner Inhalt; statt des Slugs funktioniert auch die Inhalts-ID.GET /api/v1/openapi.json: die OpenAPI-3.1-Definition (kein Schlüssel nötig).
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": "de", "language_name": "Deutsch", "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
}Zum Synchronisieren nutze order=asc&updated_since=<letzte Synchronisierung> und folge den Seiten, bis next_cursor leer ist. Um von gelöschten oder zurückgezogenen Inhalten zu erfahren, hör auf den Webhook (article.unpublished) oder rufe ab und zu die vollständige Liste ab.
MCP: KI-Assistenten verbinden
CoText ist ein MCP-Server (Model Context Protocol, Streamable HTTP): Claude, Cursor und andere MCP-Clients können in deinem Arbeitsbereich Inhalte planen, erstellen, bearbeiten, freigeben und die Veröffentlichung planen. Alles läuft durch den Ablauf von CoText: Markenstimme, gelernte Redaktionsregeln, Credits, Versionen und versionsgebundene Freigabe.
Der Inhaber erstellt unter Einstellungen > API-Schlüssel einen Schlüssel mit dem Zugriff MCP: KI-Assistent (content:write). Der Assistent arbeitet mit Editor-Rechten im Namen der Person, die den Schlüssel erstellt hat; ist sie nicht mehr Inhaber oder Editor, funktioniert der Schlüssel nicht mehr. Adresse: https://cotext.net/api/mcp. In Claude Code:
claude mcp add --transport http cotext https://cotext.net/api/mcp \
--header "Authorization: Bearer ctx_live_…"- Werkzeuge:
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. - Die Generierung läuft in der Warteschlange:
generate_articlesliefert sofort einen Stapel, den der Assistent mitget_batchverfolgt. Die Freigabe verlangt drei Bestätigungen und die aktuelle Revision; veröffentlicht wird nur die freigegebene Version. - Jede Schreibaktion erscheint im Aktivitätsprotokoll als KI-Assistent mit dem Präfix des Schlüssels. Textänderungen des Assistenten werden als eigene Version gespeichert, und CoText lernt daraus keine Markenregeln. Widerrufe den Schlüssel, um den Zugriff sofort zu beenden.
- Füge in claude.ai, ChatGPT und anderen Apps mit MCP-Connector-Unterstützung
https://cotext.net/api/mcpals Connector hinzu: Die App leitet dich zu CoText weiter, du wählst einen oder mehrere Arbeitsbereiche und erlaubst den Zugriff – der Assistent kann dann in jeder ihrer Marken arbeiten (list_workspaces, brand_id) (OAuth 2.1 mit PKCE; kein Schlüssel zum Kopieren). Verbindungen erscheinen unter Einstellungen > API-Schlüssel und lassen sich dort trennen.
Fehler und Limits
- Fehler-Body:
{ "error": { "code": "…", "message": "…", "message_en": "…" } }.messageist in der Sprache der Anfrage (HeaderAccept-Language: Englisch, Türkisch, Deutsch, Spanisch, Französisch, Arabisch oder Japanisch; sonst Englisch);message_enist immer Englisch. Codes:unauthorized,key_revoked,key_expired,insufficient_scope,api_access_disabled,invalid_parameter,not_found,rate_limited. - 600 Anfragen pro Stunde und Schlüssel. Antworten enthalten
X-RateLimit-Limit,X-RateLimit-RemainingundX-RateLimit-Reset; über dem Limit erhältst du 429 undRetry-After. - Die Endpunkte akzeptieren
GET-Anfragen von jedem Ursprung (CORS, ohne Cookies). Ein Schlüssel im Browser ist für alle sichtbar, nutze ihn also möglichst auf dem Server; er liest nur Live-Inhalte und kann nichts ändern. Wenn du glaubst, dass er offengelegt wurde, widerrufe ihn und erstelle einen neuen.