Desarrolladores
Lleva tu contenido a tu propio sitio
El contenido aprobado en CoText y publicado en el canal Tu propio sitio web llega a tu sitio de dos formas: CoText envía a tu sitio un webhook firmado al publicar, o tu sitio obtiene el contenido de la API de lectura. Puedes usar ambas. Zapier, Make y n8n usan las mismas vías.
Webhooks y verificación de firma
Al añadir el canal, introduces la URL del webhook de tu sitio (solo https). CoText genera un secreto de firma (whsec_…) y lo muestra una sola vez. Cada solicitud se firma en el formato Standard Webhooks, así que puedes usar tal cual las bibliotecas existentes (Node, Python, PHP, Go…).
webhook-id: el ID del evento. No cambia entre reintentos; no proceses un ID que ya hayas gestionado.webhook-timestamp: segundos Unix. Rechaza las solicitudes con más de 5 minutos de antigüedad (ataques de repetición).webhook-signature:v1,<base64>. El texto firmado es${webhook-id}.${webhook-timestamp}.${raw-body}, el algoritmo es HMAC-SHA256 y la clave son los bytes del valor base64 que sigue al prefijowhsec_. Durante las 24 horas siguientes a la rotación de un secreto llegan dos firmas separadas por un espacio.
Verifica la firma sobre el cuerpo sin procesar, antes de analizar el JSON:
import { createHmac, timingSafeEqual } from "node:crypto";
// secret: el valor "whsec_…" que CoText muestra una sola vez al añadir el 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();
// Mientras se rota un secreto, el encabezado puede traer dos firmas separadas por un espacio: basta con que coincida una.
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);
});
}Cuerpo
{
"type": "article.published", // article.updated · article.unpublished · ping
"timestamp": "2026-10-16T09:30:00.000Z",
"data": {
"article": {
"id": "7c0e…", // ID de contenido de CoText: inserta o actualiza según este valor
"title": "Guía para preparar café",
"slug": "guia-para-preparar-cafe",
"status": "published", // única fuente de verdad: "published" | "draft"
"excerpt": "…", "meta_description": "…", "focus_keyword": "…",
"tags": ["café"], "category": "Guía", "content_type": "guide",
"language": "es", "language_name": "Español", "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": "…"
}
}
}- Inserta o actualiza según
data.article.id(upsert); un eventoarticle.unpublishedsolo incluye elid. - El tipo de evento es orientativo; consulta
data.article.statuspara conocer el estado de la entrada. El orden de entrega no está garantizado. - Idiomas:
languagees el código del idioma del contenido (tr,en,de,fr,es;nullsi CoText no lo reconoce) ylanguage_name, el nombre guardado. Las versiones en distintos idiomas de un mismo contenido comparten untranslation_group_id(el ID del original del grupo);translationsenumera las demás versiones ya publicadas, con sus direcciones (elarticle_iddel original es igual atranslation_group_id; úsalo parax-default). Construye con ello tus enlaceshreflang. Una versión publicada más tarde no reenvía las anteriores; la API de lectura siempre tiene la lista completa. - Imagen de portada:
featured_imagees{ url, alt, width, height, mime, credit, ai }onull.urles una dirección pública permanente (muéstrala directamente o copia el archivo). Sicreditno esnull, muéstralo junto a la imagen; en las imágenes generadas con IA,aiestrue. - Datos estructurados:
structured_dataes una lista de objetos JSON-LD de schema.org: unBlogPostingy, si el texto tiene una sección de preguntas frecuentes (un h2 seguido de preguntas h3 con respuestas en párrafos), unFAQPage. Coloca cada uno en el head de la página dentro de<script type="application/ld+json">. - Devuelve 2xx (opcionalmente con
{ "url": "https://…" }para indicar la dirección de la entrada). Devuelve 401 si la firma no es válida. 410 significa "endpoint eliminado" y no se reintenta; 408, 429 y 5xx se reintentan; las redirecciones no se siguen.
API de lectura
El propietario del espacio de trabajo crea una clave en Configuración > Claves de API (ctx_live_…). La clave se muestra una sola vez, al crearla; CoText solo guarda un hash. Envía el encabezado Authorization: Bearer ctx_live_… en cada solicitud.
La API solo devuelve contenido publicado en el canal Tu propio sitio web, en la versión enviada a ese canal: las ediciones hechas en CoText después de publicar que aún no se han aprobado y enviado no aparecen, y el contenido despublicado del canal sale de la lista.
GET /api/v1/articles: lista. Parámetros:limit(1–100, 20 por defecto),cursor(elnext_cursorde la respuesta anterior),updated_since(ISO 8601, inclusivo),order(desc|asc, segúnupdated_at),channel(ID del canal),language(código del idioma del contenido:tr,en,de,fr,es).- Cada elemento incluye
language(código),language_name,translation_group_idytranslations: sus otras versiones de idioma que sirve esta API, como{ language, slug, url }, sea cual sea el filtrochannelolanguage. Úsalas para los enlaceshreflangy un selector de idioma. featured_image: la portada de la versión publicada, con el mismo formato que en el webhook ({ url, alt, width, height, mime, credit, ai }), onull.structured_data: la misma lista JSON-LD de schema.org que en el webhook (BlogPostingy, si hay preguntas frecuentes,FAQPage).GET /api/v1/articles/{slug}: un solo contenido; también funciona con el ID del contenido en lugar del slug.GET /api/v1/openapi.json: la definición OpenAPI 3.1 (no requiere clave).
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": "es", "language_name": "Español", "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
}Para sincronizar, usa order=asc&updated_since=<última sincronización> y recorre las páginas hasta que next_cursor esté vacío. Para enterarte del contenido eliminado o despublicado, escucha el webhook (article.unpublished) o descarga la lista completa de vez en cuando.
MCP: conecta tu asistente de IA
CoText es un servidor MCP (Model Context Protocol, Streamable HTTP): Claude, Cursor y otros clientes MCP pueden planificar, generar, editar, aprobar y programar contenido en tu espacio de trabajo. Todo pasa por el flujo de CoText: voz de marca, reglas editoriales aprendidas, créditos, versiones y aprobación ligada a la versión.
El propietario crea una clave con el acceso MCP: asistente de IA en Ajustes > Claves de API (content:write). El asistente trabaja con permisos de editor, a nombre de quien creó la clave; si esa persona deja de ser propietaria o editora, la clave deja de funcionar. Dirección: https://cotext.net/api/mcp. En Claude Code:
claude mcp add --transport http cotext https://cotext.net/api/mcp \
--header "Authorization: Bearer ctx_live_…"- Herramientas:
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 generación va a la cola:
generate_articlesdevuelve un lote al instante y el asistente lo sigue conget_batch. La aprobación pide tres confirmaciones y la revisión actual; la publicación solo envía la versión aprobada. - Cada escritura aparece en el registro de actividad como asistente de IA con el prefijo de la clave. Las ediciones de texto del asistente se guardan como una versión aparte y CoText no aprende reglas de marca de ellas. Revoca la clave para cortar el acceso al instante.
- En claude.ai, ChatGPT y otras aplicaciones compatibles con conectores MCP, añade
https://cotext.net/api/mcpcomo conector: la aplicación te envía a CoText, eliges uno o varios espacios de trabajo y permites el acceso; el asistente puede trabajar después en cada una de sus marcas (list_workspaces, brand_id) (OAuth 2.1 con PKCE; sin clave que copiar). Las conexiones aparecen en Ajustes > Claves de API y se pueden desconectar allí.
Errores y límites
- Cuerpo del error:
{ "error": { "code": "…", "message": "…", "message_en": "…" } }.messageestá en el idioma de la solicitud (encabezadoAccept-Language: inglés, turco, alemán, español, francés, árabe o japonés; en otro caso, inglés);message_ensiempre está en inglés. Códigos:unauthorized,key_revoked,key_expired,insufficient_scope,api_access_disabled,invalid_parameter,not_found,rate_limited. - 600 solicitudes por hora y clave. Las respuestas incluyen
X-RateLimit-Limit,X-RateLimit-RemainingyX-RateLimit-Reset; si superas el límite, recibes 429 yRetry-After. - Los endpoints aceptan solicitudes
GETdesde cualquier origen (CORS, sin cookies). Una clave en el navegador es visible para todos, así que úsala en el servidor siempre que puedas; solo lee contenido publicado y no puede cambiar nada. Si crees que se ha filtrado, revócala y crea una nueva.