Geliştiriciler

İçeriğini kendi sitene al

CoText'te onaylanıp Kendi web siten kanalına yayınlanan içerik iki yoldan sitene ulaşır: CoText yayınladığında sitene imzalı bir webhook gönderir, ya da siten içeriği okuma API'sinden kendisi çeker. İkisini birlikte de kullanabilirsin. Zapier, Make ve n8n da aynı yolları kullanır.

Webhook ve imza doğrulama

Kanalı eklerken sitenin webhook adresini (yalnız https) yazarsın; CoText bir imza sırrı (whsec_…) üretir ve onu yalnız bir kez gösterir. Her istek Standard Webhooks biçiminde imzalanır; hazır kütüphaneleri (Node, Python, PHP, Go …) doğrudan kullanabilirsin.

  • webhook-id: olay kimliği. Yeniden denemelerde aynı kalır; daha önce işlediğin kimliği tekrar işleme.
  • webhook-timestamp: Unix saniye. 5 dakikadan eski istekleri reddet (tekrar saldırısı).
  • webhook-signature: v1,<base64>. İmzalanan metin ${webhook-id}.${webhook-timestamp}.${ham-gövde}, algoritma HMAC-SHA256, anahtar whsec_ önekinden sonraki base64 değerin baytları. Sır yenilenince 24 saat boyunca boşlukla ayrılmış iki imza gelir.

İmzayı ham gövde üzerinde, JSON'u ayrıştırmadan önce doğrula:

import { createHmac, timingSafeEqual } from "node:crypto";

// secret: CoText'in kanal kaydında bir kez gösterdiği "whsec_…" değeri
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();
  // Sır yenilenirken başlıkta boşlukla ayrılmış iki imza olabilir: biri tutarsa yeterli.
  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);
  });
}

Gövde

{
  "type": "article.published",          // article.updated · article.unpublished · ping
  "timestamp": "2026-10-16T09:30:00.000Z",
  "data": {
    "article": {
      "id": "7c0e…",                     // CoText içerik kimliği: buna göre ekle/güncelle
      "title": "Kahve demleme rehberi",
      "slug": "kahve-demleme-rehberi",
      "status": "published",             // tek doğru kaynak: "published" | "draft"
      "excerpt": "…", "meta_description": "…", "focus_keyword": "…",
      "tags": ["kahve"], "category": "Rehber", "content_type": "guide",
      "language": "tr", "language_name": "Türkçe", "translation_group_id": "7c0e…",
      "translations": [{ "language": "en", "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": "…"
    }
  }
}
  • data.article.id ile ekle ya da güncelle (upsert); article.unpublished olayında yalnız id gelir.
  • Olay türü ipucudur; yazının durumu için data.article.status'a bak. Teslim sırası garanti değildir.
  • Diller: language içerik dilinin kodudur (tr, en, de, fr, es; CoText tanımıyorsa null), language_name kayıtlı adıdır. Aynı içeriğin dil sürümleri aynı translation_group_id'yi taşır (grubun kaynağının kimliği); translations yayında olan diğer sürümleri adresleriyle listeler (kaynağın article_id'si translation_group_id'ye eşittir, x-default için onu kullan). hreflang bağlantılarını buradan kur. Sonradan yayınlanan sürüm öncekileri yeniden göndermez; tam liste her zaman okuma API'sinde.
  • Kapak görseli: featured_image alanı { url, alt, width, height, mime, credit, ai } ya da null. url kalıcı, herkese açık bir adrestir (doğrudan göster ya da dosyayı kopyala). credit null değilse görselle birlikte göster; AI ile üretilen görselde ai true olur.
  • Yapılandırılmış veri: structured_data, schema.org JSON-LD nesnelerinin listesi: bir BlogPosting, metinde SSS bölümü varsa (h2 başlığı, altında soru biçiminde h3'ler ve paragraf cevaplar) bir de FAQPage. Her birini sayfanın head bölümüne <script type="application/ld+json"> içinde koy.
  • 2xx dön (isteğe bağlı { "url": "https://…" } ile yazının adresini bildir). İmza geçersizse 401 dön. 410 "uç kaldırıldı" demektir ve yeniden denenmez; 408, 429 ve 5xx yeniden denenir; yönlendirmeler izlenmez.

Okuma API'si

Çalışma alanı sahibi Ayarlar > API anahtarları'ndan bir anahtar oluşturur (ctx_live_…). Anahtar yalnız oluşturulduğunda bir kez gösterilir; CoText yalnız özetini saklar. Her istekte Authorization: Bearer ctx_live_… başlığını gönder.

API yalnız Kendi web siten kanalında yayında olan içeriği, o kanala gönderilen sürümüyle verir: yayından sonra CoText'te yapılan ama henüz onaylanıp gönderilmemiş düzenlemeler görünmez; kanalda yayından kaldırılan içerik listeden çıkar.

  • GET /api/v1/articles: liste. Sorgu: limit (1–100, varsayılan 20), cursor (bir önceki yanıtın next_cursor'ı), updated_since (ISO 8601, dahil), order (desc | asc, updated_at'e göre), channel (kanal kimliği), language (içerik dili kodu: tr, en, de, fr, es).
  • Her içerikte language (kod), language_name, translation_group_id ve translations var: bu API'nin verdiği diğer dil sürümleri, { language, slug, url } olarak — channel ya da language süzgecinden bağımsız. hreflang bağlantıları ve dil seçici için kullan.
  • featured_image: yayınlanan sürümün kapağı, webhook'takiyle aynı biçimde ({ url, alt, width, height, mime, credit, ai }) ya da null.
  • structured_data: webhook'takiyle aynı schema.org JSON-LD listesi (BlogPosting, SSS bölümü varsa FAQPage).
  • GET /api/v1/articles/{slug}: tek içerik; slug yerine içerik kimliği de olur.
  • GET /api/v1/openapi.json: OpenAPI 3.1 tanımı (anahtar gerekmez).
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": "tr", "language_name": "Türkçe", "translation_group_id": "7c0e…",
      "translations": [{ "language": "en", "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
}

Eşitleme için order=asc&updated_since=<son eşitleme> kullanıp next_cursor boşalana kadar sayfaları izle. Silinen ya da yayından kaldırılan içeriği öğrenmek için webhook'u (article.unpublished) dinle ya da arada bir tam liste al.

MCP: AI asistanını bağla

CoText bir MCP sunucusudur (Model Context Protocol, Streamable HTTP): Claude, Cursor ve diğer MCP istemcileri çalışma alanında içerik planlar, üretir, düzenler, onaylar ve yayın planlar. Her şey CoText'in akışından geçer: marka sesi, öğrenilen editoryal kurallar, krediler, sürümler ve sürüme bağlı onay.

Sahip, Ayarlar > API anahtarları'ndan MCP: AI asistanı erişimiyle bir anahtar açar (content:write). Asistan, anahtarı açan kişinin adıyla editör yetkisiyle çalışır; o kişi artık sahip ya da editör değilse anahtar çalışmaz. Adres: https://cotext.net/api/mcp. Claude Code'da:

claude mcp add --transport http cotext https://cotext.net/api/mcp \
  --header "Authorization: Bearer ctx_live_…"
  • Araçlar: 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.
  • Üretim kuyrukta: generate_articles hemen bir toplu iş döner, asistan get_batch ile izler. Onay üç beyan ve güncel revizyon ister; yayın yalnız onaylı sürümü gönderir.
  • Her yazma işlemi etkinlik kaydında anahtarın ön ekiyle AI asistanı olarak görünür. Asistanın metin düzenlemeleri ayrı bir sürüm olarak kaydedilir ve CoText bunlardan marka kuralı öğrenmez. Erişimi hemen kesmek için anahtarı iptal et.
  • claude.ai, ChatGPT ve MCP bağlayıcısını destekleyen diğer uygulamalarda https://cotext.net/api/mcp adresini bağlayıcı olarak ekle: uygulama seni CoText'e yönlendirir, bir ya da birden çok çalışma alanı seçip izin verirsin — asistan sonra her birinin markalarında çalışabilir (list_workspaces, brand_id) (PKCE'li OAuth 2.1; kopyalanacak anahtar yok). Bağlantılar Ayarlar > API anahtarları'nda görünür ve oradan kesilir.

Hatalar ve sınırlar

  • Hata gövdesi: { "error": { "code": "…", "message": "…", "message_en": "…" } }. message isteğin dilindedir (Accept-Language başlığı: İngilizce, Türkçe, Almanca, İspanyolca, Fransızca, Arapça ya da Japonca; diğerlerinde İngilizce); message_en her zaman İngilizcedir. Kodlar: unauthorized, key_revoked, key_expired, insufficient_scope, api_access_disabled, invalid_parameter, not_found, rate_limited.
  • Anahtar başına saatte 600 istek. Yanıtlarda X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; sınır aşılınca 429 ve Retry-After.
  • Uçlar her kökenden GET isteğine açıktır (CORS, çerezsiz). Anahtar tarayıcıda görünür olacağından mümkünse sunucu tarafında kullan; yalnız yayındaki içeriği okur, hiçbir şeyi değiştiremez. Sızdığını düşünürsen iptal edip yenisini oluştur.