# AGENTS.md: programar contra la API de heysteff

Guía para agentes de código (Claude Code, Cursor, Codex y similares) que integran un sistema con heysteff. Si eres una persona, la documentación completa está en https://developers.heysteff.com/docs/.

## Fuentes de verdad

- API completa en texto plano: https://app.heysteff.com/api/v1/llms-full.txt
- Especificación OpenAPI 3.1: https://app.heysteff.com/api/v1/openapi.json
- Documentación: https://developers.heysteff.com/docs/ (en inglés: https://developers.heysteff.com/en/docs/)
- Skill para agentes de código (formato de Claude Code): https://developers.heysteff.com/skills/heysteff/SKILL.md

Para instalar la skill en Claude Code:

```sh
mkdir -p ~/.claude/skills/heysteff && curl -fsSL https://developers.heysteff.com/skills/heysteff/SKILL.md -o ~/.claude/skills/heysteff/SKILL.md
```

Para probar desde la terminal, la CLI (0.7.0) se instala con `curl -fsSL https://developers.heysteff.com/install.sh | bash`. Para conectarla corre `heysteff login`: abre el navegador y la persona autoriza ahí (elige espacio, modo `sk_live_` o `sk_test_` y permisos), así que nunca le pides que copie una key. En SSH o un contenedor, `heysteff login --no-browser` imprime la URL para que la persona la abra. En CI no hay login: usa `HEYSTEFF_API_KEY` (o `heysteff setup --api-key "$HEYSTEFF_API_KEY" --no-open --skip-test`).

Lee `llms-full.txt` antes de escribir código: los campos, enums y scopes de ahí son exactamente los que acepta la API. No inventes endpoints: la API pública expone rutas bajo `https://app.heysteff.com/api/v1/` y, para código que ya existe contra la Cloud API de Meta, la API compatible bajo `https://app.heysteff.com/api/meta/whatsapp/<versión>/` (ver abajo).

## Autenticación

- Cada solicitud lleva `Authorization: Bearer sk_live_...`.
- Para probar sin un número propio usa una key `sk_test_` del sandbox: solo le escribe a los teléfonos unidos al sandbox del espacio (https://developers.heysteff.com/docs/sandbox).
- Lee la key de una variable de entorno (por ejemplo `HEYSTEFF_API_KEY`). Nunca la escribas en el código, en un commit, en un log ni en un ejemplo.
- Nunca llames a la API desde el navegador ni desde una app móvil: la key da acceso al workspace completo. Las llamadas van desde un servidor.
- Cada operación exige un scope (`messages:write`, `contacts:read`, …). Un 403 casi siempre significa que a la key le falta el scope: pídele a la persona que lo agregue, no busques un rodeo.

## Escrituras

- Manda `Idempotency-Key` en todo POST que la acepte, con una clave por operación lógica (por ejemplo `pedido-1042-en-camino`), no una por intento. En `/v1/charges` y `/v1/refunds` es obligatoria.
- Reintenta solo errores 429, 409 `idempotency_conflict` y 5xx, con backoff exponencial y la misma `Idempotency-Key`. Un 400, 402 o 422 no se arregla reintentando.
- Los montos van en unidades menores como enteros (`1990` = 19,90). Los teléfonos, en formato internacional (`+56912345678`).
- Fuera de la ventana de 24 horas de WhatsApp solo se pueden mandar plantillas aprobadas (`content_type: "template"`).
- Botones y listas van con `content_type: "interactive"` (nativos en WhatsApp Cloud, quick replies en Instagram y Messenger, texto numerado en SMS, email y WhatsApp por QR; la respuesta dice `degraded: true` cuando el canal no tiene el formato). La respuesta del cliente llega en `message:received` con `interactive_reply: { id, title }`. También existen `content_type: "location"` y `"reaction"`. Botones, listas y ubicaciones exigen la ventana de 24 horas (409 `wa_window_closed` / `meta_window_closed`).
- Solo en WhatsApp Cloud: `content_type: "flow"` manda un WhatsApp Flow (formulario; la respuesta llega en `message:received` con `flow_response`), `"voice_call"` un botón de llamar y `"call_permission_request"` pide permiso para llamar. En otro canal responden 422 `whatsapp_cloud_only`. Los Flows se gestionan con `/v1/whatsapp-flows` y las llamadas con `/v1/calls` y `/v1/whatsapp-numbers/{id}/calling` (eventos `call.started`, `call.ended`, `call.missed`).
- `GET /v1/messages/{id}` da el estado de un mensaje y `POST /v1/messages/{id}/read` manda el recibo de lectura. No consultes el estado en bucle: suscríbete a `message.delivered`, `message.read` y `message.failed`.
- Los webhooks y las plantillas de WhatsApp también se gestionan por API (`POST/PATCH/DELETE /v1/webhooks`, `POST /v1/templates`, `DELETE /v1/templates/{name}`, scopes `webhooks:write` y `templates:write`). El secreto de un webhook se devuelve una sola vez, al crearlo: guárdalo en tu gestor de secretos.

## Webhooks

- Verifica `X-Heysteff-Signature` (`v1=` + HMAC-SHA256 hex con el secreto del webhook sobre `${timestamp}.${body}`) contra el cuerpo crudo, con comparación en tiempo constante.
- Rechaza entregas cuyo `X-Heysteff-Timestamp` difiera en más de 300 segundos.
- Responde 2xx rápido y procesa después. Deduplica por `X-Heysteff-Idempotency-Key` (el `id` del evento: igual en reintentos y en todos tus webhooks).
- Un webhook que falla mucho (40 entregas o más en 15 minutos con 85 % o más fallidas) queda en pausa con los eventos en cola: reactívalo con `PATCH /v1/webhooks/{id}` `{ "is_active": true }`.
- `batch: { wait_seconds, max_size }` agrupa los `message:received` rápidos de una conversación en un solo POST con `events: [...]` y `X-Heysteff-Batch: true`.
- Con el SDK 0.7.0 el receptor completo es `createWebhookHandler({ secret, onEvent })` (Next.js, Hono, Workers; `.toExpress()` para Express). `WebhookEvent` es una unión tipada por `event`.
- Para probar el receptor en local sin túnel: `heysteff listen --forward-to http://localhost:3000/webhooks/heysteff` reenvía los eventos reales del espacio, firmados con un secreto local `whsec_local_…` que muestra al iniciar (o con el de `--secret`). El receptor local tiene que usar ese secreto.
- Eventos en vivo sin URL pública: `GET /v1/events/stream` (Server-Sent Events, scope `events:read`, se reanuda con `Last-Event-ID`) o `heysteff.events.stream()` en el SDK.
- Para depurar una entrega que falló, lee `GET /v1/webhooks/{id}/deliveries?status=failed` (scope `webhooks:read`): trae `http_status`, `duration_ms`, `error` y un extracto de la respuesta. Arreglado tu servidor, `POST /v1/webhook-deliveries/{id}/retry` (scope `webhooks:write`) la vuelve a mandar con el mismo `X-Heysteff-Delivery-Id`.
- Vector de prueba: secreto `whsec_test_123`, timestamp `1730000000`, cuerpo `{"event":"message:received","data":{"id":"msg_1"}}`, firma esperada `v1=7fc6ea897401575af79e1b73b304bd864c05f1fef25dca2a0e8ffbf9b0d18ab4`.

## Errores

Todo error tiene la forma `{ "error": { "code", "message", "details"? } }`. Usa `code` para decidir en el código y muestra `message` en los logs.

## Configurar el workspace con IA

Para configurar el agente, los canales, los flujos o las campañas (no para integrar un sistema), usa el servidor MCP de heysteff en `https://mcp.heysteff.com/mcp`, que autentica con OAuth en nombre de una persona dueña del workspace, nunca con una API key; el workspace se elige al autorizar. Los comandos para Claude Code, Codex y Cursor están en https://developers.heysteff.com/docs/agentes-de-codigo. En Claude Code, el plugin `heysteff` lo deja listo: `/plugin marketplace add heysteff-com/agent-plugins` y `/plugin install heysteff@heysteff`. Las skills `heysteff-integrate`, `heysteff-automate` y `heysteff-observe` se instalan con `npx skills add heysteff-com/agent-skills`.

## Paquetes y repos

Usa los paquetes oficiales en vez de escribir un cliente HTTP propio. Todos son MIT. El SDK, la CLI, las plantillas y las skills leen la key de `HEYSTEFF_API_KEY`; `@heysteff/connect` corre en el navegador y nunca recibe la key. Tabla completa: https://developers.heysteff.com/docs/integraciones#paquetes. Repos públicos: https://github.com/heysteff-com.

- `@heysteff/sdk`: SDK de TypeScript (Node 20+, Deno, Bun, workers). Instalar: `npm install @heysteff/sdk`. https://www.npmjs.com/package/@heysteff/sdk
- `@heysteff/cli`: el comando `heysteff`. Instalar: `npm install -g @heysteff/cli`. https://www.npmjs.com/package/@heysteff/cli
- `create-heysteff`: plantillas `inbox`, `agent` y `send`. Instalar: `npm create heysteff@latest`. https://www.npmjs.com/package/create-heysteff
- `@heysteff/chat-adapter`: adapter del Chat SDK de Vercel. Instalar: `npm install chat @heysteff/chat-adapter @chat-adapter/state-memory`. https://www.npmjs.com/package/@heysteff/chat-adapter
- `@heysteff/connect`: links de conexión en un popup. Instalar: `npm install @heysteff/connect`. https://www.npmjs.com/package/@heysteff/connect
- `@heysteff/n8n-nodes-heysteff`: nodos de n8n. Instalar: `npm install @heysteff/n8n-nodes-heysteff`. https://www.npmjs.com/package/@heysteff/n8n-nodes-heysteff · https://github.com/heysteff-com/n8n-nodes-heysteff
- `omnichannel-inbox`: plantilla de bandeja en Next.js. Instalar: `npm create heysteff@latest mi-bandeja -- --template inbox`. https://github.com/heysteff-com/omnichannel-inbox
- `ai-agent-starter`: plantilla de agente de IA. Instalar: `npm create heysteff@latest mi-agente -- --template agent`. https://github.com/heysteff-com/ai-agent-starter
- `agent-skills`: skills para agentes de código. Instalar: `npx skills add heysteff-com/agent-skills`. https://github.com/heysteff-com/agent-skills
- `agent-plugins`: plugin de Claude Code, Cursor y Codex. Instalar: `/plugin marketplace add heysteff-com/agent-plugins`. https://github.com/heysteff-com/agent-plugins

## Agente de IA

Un espacio developer trae el agente de IA de heysteff, apagado al crearlo. Antes de escribir un loop propio de webhook → modelo → respuesta, pregunta si la persona prefiere encender el agente. Se configura con `GET/PATCH /v1/agent` (scopes `agent:read` / `agent:write`): `enabled` del espacio, `channels` y `customers` con `true`, `false` o `null` (heredar), proveedor, modelo, instrucciones y temperatura. La key del proveedor se guarda con `PUT /v1/agent/keys` leyéndola de una variable de entorno; nunca la escribas en el código. Sin key, el agente usa el cupo de prueba del plan; sin key ni cupo no responde y llega `agent.failed` con `ai_quota_exhausted`, que no se arregla reintentando. Custom tools en `/v1/agent/tools`, hallazgos en `GET /v1/insights`. Guía: https://developers.heysteff.com/docs/agente

## Migrar desde otro proveedor

Si el código usa Twilio, 360dialog, YCloud o Kapso, la tabla de equivalencias de endpoints, webhooks y firmas está en https://developers.heysteff.com/docs/migrar (en inglés: https://developers.heysteff.com/en/docs/migrate). No traduzcas el formato de la Cloud API de Meta (`messaging_product`, `type`, `text.body`) a ciegas: heysteff usa `channel`, `to`, `content` y `content_type`.

## Código que ya usa la Cloud API de Meta

Si el código llama a `graph.facebook.com/<versión>/{phone_number_id}/messages` (directo o con un SDK de la Cloud API), no lo reescribas: cambia solo la URL base a `https://app.heysteff.com/api/meta/whatsapp/v21.0` (cualquier `v<n>.<n>`) y el token a `Authorization: Bearer sk_live_...`. El `phone_number_id` es el mismo. Diferencias que sí importan:

- `messages[0].id` de la respuesta es el id de heysteff (UUID), no el wamid; los webhooks de estado traen ese mismo id. Los entrantes traen su wamid. Donde la API pide un id (cita, reacción, leído) sirven los dos.
- Para el receptor de webhooks, crea el webhook con `format: "meta"` (`POST /v1/webhooks`): llega el payload de la Cloud API firmado con `X-Hub-Signature-256` (`sha256=` + HMAC-SHA256 del cuerpo crudo) con el secreto del webhook, no con el app secret de Meta. No hay handshake `hub.challenge`.
- Los errores tienen la forma de Graph (`error.code`, `error_subcode`, `fbtrace_id`) y además `error.heysteff_code`.
- No existen las rutas de administración de la cuenta de Meta (registro del número, verificación en dos pasos, suscripciones). Guía: https://developers.heysteff.com/docs/meta-compatible.
