المطوّرون

محتواك على موقعك الخاص

يصل المحتوى المعتمد في CoText والمنشور على قناة موقعك الخاص إلى موقعك بطريقتين: يرسل CoText إلى موقعك طلب Webhook موقّعًا عند النشر، أو يسحب موقعك المحتوى من واجهة القراءة (API). ويمكنك استخدام الطريقتين معًا. وتعتمد Zapier وMake وn8n المسارات نفسها.

Webhook والتحقق من التوقيع

عند إضافة القناة، تُدخل عنوان URL الخاص بـ Webhook في موقعك (https فقط). يُنشئ CoText سرّ توقيع (whsec_…) ويعرضه مرة واحدة فقط. كل طلب موقَّع بتنسيق Standard Webhooks، لذا يمكنك استخدام المكتبات الجاهزة (Node وPython وPHP وGo …) كما هي.

  • webhook-id: معرّف الحدث. يبقى كما هو عند إعادة المحاولة؛ يُرجى عدم معالجة معرّف سبقت معالجته.
  • webhook-timestamp: الوقت بالثواني بتوقيت Unix. يُرجى رفض الطلبات الأقدم من خمس دقائق (هجمات إعادة الإرسال).
  • webhook-signature: v1,<base64>. النص الموقَّع هو ${webhook-id}.${webhook-timestamp}.${raw-body}، والخوارزمية HMAC-SHA256، والمفتاح هو بايتات قيمة base64 التي تلي البادئة whsec_. وخلال 24 ساعة بعد تدوير السر، يصل توقيعان يفصل بينهما فراغ.

يُرجى التحقق من التوقيع على متن الطلب الخام قبل تحليل JSON:

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

// secret: قيمة «whsec_…» التي يعرضها CoText مرة واحدة عند إضافة القناة
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();
  // أثناء تدوير السر، قد تحمل الترويسة توقيعين يفصل بينهما فراغ: يكفي تطابق أحدهما.
  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);
  });
}

متن الطلب

{
  "type": "article.published",          // article.updated · article.unpublished · ping
  "timestamp": "2026-10-16T09:30:00.000Z",
  "data": {
    "article": {
      "id": "7c0e…",                     // معرّف المحتوى في CoText: الإدراج أو التحديث وفقه
      "title": "دليل تحضير القهوة",
      "slug": "دليل-تحضير-القهوة",
      "status": "published",             // المصدر الوحيد للحقيقة: "published" | "draft"
      "excerpt": "…", "meta_description": "…", "focus_keyword": "…",
      "tags": ["قهوة"], "category": "دليل", "content_type": "guide",
      "language": "ar", "language_name": "العربية", "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": "…"
    }
  }
}
  • يكون الإدراج أو التحديث وفق data.article.id (upsert)؛ أما حدث article.unpublished فلا يحمل إلا id.
  • نوع الحدث مجرد إشارة؛ يُرجى التحقق من data.article.status لمعرفة حالة المنشور. ترتيب التسليم غير مضمون.
  • اللغات: language هو رمز لغة المحتوى (tr وen وde وfr وes؛ وnull إن لم يتعرّف CoText عليها)، وlanguage_name هو الاسم المخزَّن. تشترك النسخ اللغوية للمحتوى نفسه في translation_group_id (معرّف المصدر في المجموعة)؛ ويسرد translations النسخ الأخرى المنشورة بالفعل مع عناوينها (قيمة article_id للمصدر تساوي translation_group_id، ويمكنك استخدامها لـ x-default). ومنه يمكنك بناء روابط hreflang. والنسخة التي تُنشر لاحقًا لا تعيد إرسال النسخ السابقة؛ أما واجهة القراءة (API) فتعرض القائمة الكاملة دائمًا.
  • صورة الغلاف: featured_image هو { url, alt, width, height, mime, credit, ai } أو null. url عنوان عام دائم (اعرضه مباشرةً أو انسخ الملف). إذا لم يكن credit مساويًا لـ null، فاعرضه مع الصورة؛ وتكون قيمة ai هي true في الصور المُنشأة بالذكاء الاصطناعي.
  • البيانات المنظَّمة: structured_data قائمة بكائنات JSON-LD من schema.org: كائن BlogPosting، ومعه FAQPage إذا احتوى النص على قسم أسئلة شائعة (عنوان h2 تليه أسئلة h3 بإجابات في فقرات). ضع كل كائن في رأس الصفحة داخل <script type="application/ld+json">.
  • يُرجى إرجاع 2xx (ويمكن إرفاق { "url": "https://…" } للإبلاغ عن عنوان المنشور)، وإرجاع 401 إذا كان التوقيع غير صالح. الرمز 410 يعني «تمت إزالة نقطة النهاية» ولا تُعاد المحاولة بعده؛ أما 408 و429 و5xx فتُعاد المحاولة بعدها؛ ولا تُتبَع عمليات إعادة التوجيه.

واجهة القراءة (API)

ينشئ مالك مساحة العمل مفتاحًا من الإعدادات › مفاتيح API (ctx_live_…). لا يُعرض المفتاح إلا مرة واحدة عند إنشائه؛ ولا يخزّن CoText منه إلا قيمة مجزّأة (hash). يُرجى إرسال الترويسة Authorization: Bearer ctx_live_… مع كل طلب.

لا تعرض الواجهة إلا المحتوى المنشور على قناة موقعك الخاص، بالنسخة التي أُرسلت إلى تلك القناة: فالتعديلات التي تُجرى في CoText بعد النشر ولم تُعتمد وتُرسَل بعد لا تظهر، والمحتوى الذي أُلغي نشره من القناة يختفي من القائمة.

  • GET /api/v1/articles: القائمة. معاملات الاستعلام: limit (1–100، والافتراضي 20)، وcursor (قيمة next_cursor من الاستجابة السابقة)، وupdated_since (ISO 8601، شاملًا)، وorder (desc | asc، حسب updated_at)، وchannel (معرّف القناة)، وlanguage (رمز لغة المحتوى: tr وen وde وfr وes).
  • يحمل كل عنصر language (الرمز) وlanguage_name وtranslation_group_id وtranslations: نسخه اللغوية الأخرى التي تقدّمها هذه الواجهة، بصيغة { language, slug, url }، بصرف النظر عن عامل التصفية channel أو language. ويمكنك استخدامها في روابط hreflang وفي مبدّل اللغة.
  • featured_image: غلاف النسخة المنشورة، بالشكل نفسه الموجود في الـ webhook ({ url, alt, width, height, mime, credit, ai })، أو null.
  • structured_data: قائمة JSON-LD نفسها من schema.org الموجودة في الـ webhook (BlogPosting، ومعه FAQPage إذا وُجد قسم أسئلة شائعة).
  • GET /api/v1/articles/{slug}: محتوى واحد؛ ويمكن أيضًا استخدام معرّف المحتوى بدلًا من slug.
  • GET /api/v1/openapi.json: تعريف OpenAPI 3.1 (لا يلزم مفتاح).
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": "ar", "language_name": "العربية", "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
}

للمزامنة، يُرجى استخدام order=asc&updated_since=<last sync> ومتابعة الصفحات حتى تصبح قيمة next_cursor فارغة. ولمعرفة المحتوى المحذوف أو الذي أُلغي نشره، يمكنك الاستماع إلى Webhook (article.unpublished) أو جلب القائمة الكاملة من حين لآخر.

MCP: اربط مساعد الذكاء الاصطناعي

CoText خادم MCP ‏(Model Context Protocol، Streamable HTTP): يستطيع Claude وCursor وغيرهما من عملاء MCP تخطيط المحتوى وإنشاءه وتحريره واعتماده وجدولة نشره في مساحة عملك. يمر كل شيء عبر مسار CoText: صوت العلامة التجارية، والقواعد التحريرية المتعلَّمة، والرصيد، والنسخ، والاعتماد المرتبط بالنسخة.

ينشئ المالك مفتاحًا بصلاحية MCP: مساعد الذكاء الاصطناعي من الإعدادات > مفاتيح API ‏(content:write). يعمل المساعد بصلاحيات المحرر باسم من أنشأ المفتاح؛ وإذا لم يعد مالكًا أو محررًا يتوقف المفتاح عن العمل. العنوان: https://cotext.net/api/mcp. في Claude Code:

claude mcp add --transport http cotext https://cotext.net/api/mcp \
  --header "Authorization: Bearer ctx_live_…"
  • الأدوات: 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.
  • يجري الإنشاء عبر قائمة الانتظار: تعيد generate_articles دفعة فورًا ويتابعها المساعد عبر get_batch. يتطلب الاعتماد ثلاثة تأكيدات والنسخة الحالية، ولا يُنشر إلا الإصدار المعتمد.
  • تظهر كل عملية كتابة في سجل النشاط باسم مساعد الذكاء الاصطناعي مع بادئة المفتاح. تُحفظ تعديلات المساعد على النص نسخةً منفصلة، ولا يتعلم CoText منها قواعد العلامة التجارية. ألغِ المفتاح لقطع الوصول فورًا.
  • في claude.ai وChatGPT وغيرهما من التطبيقات التي تدعم موصِّلات MCP، أضف https://cotext.net/api/mcp موصِّلًا: يرسلك التطبيق إلى CoText، فتختار مساحة عمل واحدة أو أكثر وتسمح بالوصول، ثم يستطيع المساعد العمل في كل علامة تجارية فيها (list_workspaces، brand_id) (OAuth 2.1 مع PKCE؛ لا مفتاح لنسخه). تظهر الاتصالات في الإعدادات > مفاتيح API ويمكن قطعها من هناك.

الأخطاء والحدود

  • متن الخطأ: { "error": { "code": "…", "message": "…", "message_en": "…" } }. تأتي message بلغة الطلب (الترويسة Accept-Language: الإنجليزية أو التركية أو الألمانية أو الإسبانية أو الفرنسية أو العربية أو اليابانية؛ وفي غير ذلك بالإنجليزية)، أما message_en فبالإنجليزية دائمًا. الرموز: unauthorized، key_revoked، key_expired، insufficient_scope، api_access_disabled، invalid_parameter، not_found، rate_limited.
  • 600 طلب في الساعة لكل مفتاح. تحمل الاستجابات X-RateLimit-Limit وX-RateLimit-Remaining وX-RateLimit-Reset؛ وعند تجاوز الحد تتلقى الرمز 429 مع Retry-After.
  • تقبل نقاط النهاية طلبات GET من أي مصدر (CORS، بلا ملفات تعريف ارتباط). المفتاح الموجود في المتصفح مكشوف للجميع، لذا يُفضَّل استخدامه على الخادم متى أمكن؛ فهو يقرأ المحتوى المنشور فقط ولا يستطيع تغيير أي شيء. وإذا اشتبهت في تسرّبه، يُرجى إلغاؤه وإنشاء مفتاح جديد.