開発者向け
コンテンツを自社サイトへ
CoTextで承認され、自社サイトチャネルに公開されたコンテンツは、2つの方法でサイトに届きます。公開時にCoTextがサイトへ署名付きWebhookを送信する方法と、サイト側が読み取りAPIからコンテンツを取得する方法です。両方を併用することもできます。Zapier、Make、n8nも同じ方法を使います。
Webhookと署名の検証
チャネルを追加するときに、サイトのWebhook URL(httpsのみ)を入力します。CoTextは署名シークレット(whsec_…)を生成し、一度だけ表示します。すべてのリクエストはStandard Webhooks形式で署名されるため、既存のライブラリ(Node、Python、PHP、Goなど)をそのまま使用できます。
webhook-id:イベントIDです。再試行時も同じ値のため、処理済みのIDは処理しないでください。webhook-timestamp:Unix秒です。5分より古いリクエストは拒否してください(リプレイ攻撃対策)。webhook-signature:v1,<base64>。署名対象のテキストは${webhook-id}.${webhook-timestamp}.${raw-body}、アルゴリズムはHMAC-SHA256、キーはwhsec_接頭辞の後に続くbase64値のバイト列です。シークレットのローテーション後24時間は、スペース区切りで2つの署名が届きます。
JSONを解析する前に、生の本文で署名を検証します:
import { createHmac, timingSafeEqual } from "node:crypto";
// secret:チャネルの追加時にCoTextが一度だけ表示する「whsec_…」の値
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();
// シークレットのローテーション中は、ヘッダーにスペース区切りで2つの署名が含まれる場合があります。いずれか1つが一致すれば十分です。
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のコンテンツID:これをキーに挿入または更新
"title": "コーヒーの淹れ方ガイド",
"slug": "coffee-brewing-guide",
"status": "published", // 唯一の信頼できる情報源:"published" | "draft"
"excerpt": "…", "meta_description": "…", "focus_keyword": "…",
"tags": ["コーヒー"], "category": "ガイド", "content_type": "guide",
"language": "ja", "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。CoTextが認識できない場合はnull)、language_nameは保存されている言語名です。同じコンテンツの各言語版はtranslation_group_id(グループの元になったコンテンツのID)を共有します。translationsには、すでに公開されているほかの言語版がURLとともに列挙されます(元のコンテンツのarticle_idはtranslation_group_idと同じで、x-defaultに使用します)。これをもとにhreflangリンクを作成してください。後から公開された言語版によって、先に公開された言語版が再送されることはありません。完全なリストは読み取りAPIで常に取得できます。 - カバー画像:
featured_imageは{ url, alt, width, height, mime, credit, ai }またはnullです。urlは恒久的な公開アドレスです(そのまま表示するか、ファイルをコピーしてください)。creditがnullでない場合は画像と一緒に表示してください。AIで生成した画像ではaiがtrueになります。 - 構造化データ:
structured_dataはschema.orgのJSON-LDオブジェクトのリストです。BlogPostingが1つ、本文にFAQセクション(h2の下に質問のh3と段落の回答)がある場合はFAQPageも含まれます。それぞれをページのheadに<script type="application/ld+json">として配置してください。 - 2xxを返してください(投稿のURLを伝える場合は
{ "url": "https://…" }を付けることもできます)。署名が無効な場合は401を返します。410は「エンドポイント削除」を意味し、再試行されません。408、429、5xxは再試行されます。リダイレクトは追跡されません。
読み取りAPI
ワークスペースのオーナーが設定 › APIキーでキー(ctx_live_…)を作成します。キーは作成時に一度だけ表示され、CoTextはそのハッシュのみを保存します。すべてのリクエストにAuthorization: Bearer ctx_live_…ヘッダーを付けて送信してください。
APIが返すのは、自社サイトチャネルで公開中のコンテンツのみで、そのチャネルに送信されたバージョンです。公開後にCoTextで行った編集のうち、まだ承認・送信されていないものは表示されません。チャネルで公開を取り消したコンテンツはリストから外れます。
GET /api/v1/articles:一覧。クエリ:limit(1〜100、デフォルト20)、cursor(前のレスポンスのnext_cursor)、updated_since(ISO 8601、指定日時を含む)、order(desc|asc、updated_at順)、channel(チャネルID)、language(コンテンツの言語コード:tr、en、de、fr、es)。- 各アイテムには
language(コード)、language_name、translation_group_id、translationsが含まれます。translationsは、このAPIが提供するほかの言語版を{ language, slug, url }の形式で示し、channelやlanguageのフィルターに関係なく返されます。hreflangリンクや言語切り替えに使用してください。 featured_image:公開されたバージョンのカバー画像。webhookと同じ形式({ url, alt, width, height, mime, credit, ai })、またはnullです。structured_data:webhookと同じschema.orgのJSON-LDリスト(BlogPosting、FAQセクションがあればFAQPageも)。GET /api/v1/articles/{slug}:単一のコンテンツ。スラッグの代わりにコンテンツIDも使用できます。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": "ja", "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:AIアシスタントを接続
CoText は MCP サーバー(Model Context Protocol、Streamable HTTP)です。Claude、Cursor などの MCP クライアントが、ワークスペースでコンテンツの計画、生成、編集、承認、公開予約を行えます。すべて CoText のフローを通ります:ブランドボイス、学習した編集ルール、クレジット、バージョン、バージョンに紐づく承認。
オーナーが 設定 > APIキー で MCP:AIアシスタント のアクセス権を持つキーを作成します(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で進行を確認します。承認には3つの確認と最新のリビジョンが必要で、公開されるのは承認済みのバージョンだけです。 - 書き込み操作はすべて、キーのプレフィックス付きで AIアシスタント としてアクティビティ履歴に表示されます。アシスタントによる本文の編集は別のバージョンとして保存され、CoText はそこからブランドルールを学習しません。アクセスをすぐに止めるにはキーを無効にしてください。
- claude.ai、ChatGPT など MCP コネクタに対応したアプリで、
https://cotext.net/api/mcpをコネクタとして追加します。アプリが CoText に移動させるので、1つ以上のワークスペースを選んでアクセスを許可してください。アシスタントはその各ブランドで作業できます(list_workspaces、brand_id)(PKCE 付き OAuth 2.1。コピーするキーは不要)。接続は 設定 > 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。 - キーごとに1時間あたり600リクエスト。レスポンスには
X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Resetが含まれます。上限を超えると429とRetry-Afterが返されます。 - エンドポイントは、あらゆるオリジンからの
GETリクエストを受け付けます(CORS、Cookieなし)。ブラウザー上のキーは誰でも見られるため、可能な限りサーバー側で使用してください。キーは公開中のコンテンツを読み取るだけで、何も変更できません。漏えいした可能性がある場合は、無効にして新しいキーを作成してください。