API de control
La referencia REST de todo lo que no es una tool: catálogo, conexiones, sesiones, sincronizaciones y webhooks.
Ejecutar una tool es siempre POST /v1/tools/{id}/execute, y su contrato completo vive en la referencia y en el OpenAPI. Alrededor de eso el gateway expone una superficie de control: qué puedes llamar, qué tienes conectado, en qué va una sincronización y a dónde se entregan los eventos. Esta página documenta esa superficie completa.
Estos endpoints no están en openapi.json
Los contratos OpenAPI publicados cubren la ejecución de tools. Los endpoints de control de esta página todavía no entran en esos archivos; mientras tanto, esta página es su referencia.
Convenciones
La base es https://connect.emisso.ai/api/v1. Salvo GET /v1/health, GET /v1 y GET /v1/openapi.json, todos los endpoints exigen Authorization: Bearer connect_sk_… y responden 401 unauthorized con el challenge WWW-Authenticate si el token falta o no sirve. Si la infraestructura de autenticación está caída, la respuesta es 503 service_unavailable (retryable): una caída nuestra nunca se disfraza de «tu clave está mala».
Reglas que aplican a toda la superficie:
- Todo cuerpo de petición está limitado a 256 KB: por encima responde
413 payload_too_large, y un JSON inválido responde400 malformed_request. - Toda respuesta de éxito envuelve el resultado en
{ "data": … }; todo error viene como{ "error": { "code", "message", "request_id", "suggested_fix" } }con el código del catálogo de errores. - Los
GETde esta página no escriben en la bitácora: no ejecutan ninguna tool. Soloexecuteaudita.
Descubrimiento
GET /v1/health
Sin credencial. Dice si el gateway está arriba y cuántas tools tiene el registro.
curl https://connect.emisso.ai/api/v1/health{ "data": { "status": "ok", "registry": { "tools": 29 } } }GET /v1
Sin credencial. El puntero raíz del API; a diferencia del resto, responde sin el envelope data.
curl https://connect.emisso.ai/api/v1{
"data_url": "/api/v1",
"openapi": "/api/v1/openapi.json",
"docs": "https://connect.emisso.ai/docs"
}GET /v1/tools
El catálogo filtrado para tu organización: solo los conectores que tienes habilitados y solo lo que cubren los scopes de tu clave. Dos organizaciones ven listas distintas. Cada entrada trae la ficha completa, incluidos los JSON-Schema de entrada y salida y, cuando la tool lo declara, un example ejecutable:
curl https://connect.emisso.ai/api/v1/tools \
-H "Authorization: Bearer connect_sk_..."{
"data": [
{
"id": "sii.rcv.consultar",
"mcpName": "sii__rcv__consultar",
"connector": "sii", "resource": "rcv", "verb": "consultar",
"title": "Consultar RCV del SII",
"description": "…",
"plane": "action",
"reads": "rcv",
"scope": "sii:read",
"auth": "none",
"sensitive": true,
"version": 4,
"deprecated": false,
"annotations": { "readOnly": true, "destructive": false, "idempotent": true, "openWorld": false, "title": "Consultar RCV del SII" },
"inputSchema": { "…": "draft 2020-12" },
"outputSchema": { "…": "draft 2020-12" },
"example": { "input": { "…": "…" }, "output": { "…": "…" } }
}
],
"pagination": { "cursor": null, "hasMore": false }
}plane dice si la tool persiste; reads aparece solo en las tools de consulta y nombra el alcance que leen. El modelo completo está en sincronizar y consultar.
GET /v1/tools/{id}
La misma ficha, para un id concreto. Este endpoint no filtra por organización: devuelve la ficha de cualquier tool del catálogo, esté o no conectado su sistema, porque descubrir no es ejecutar. Un id inexistente responde 404 tool_not_found.
curl https://connect.emisso.ai/api/v1/tools/sii.rcv.consultar \
-H "Authorization: Bearer connect_sk_..."{ "data": { "id": "sii.rcv.consultar", "…": "la misma ficha de GET /v1/tools" } }GET /v1/connectors
Los conectores de uso general, con su estado de habilitación para tu organización. Los sistemas
conectables (SII y bancos) no aparecen aquí: este endpoint sirve solo el catálogo público. Para
obtener los códigos de conector sin adivinar, incluidos los conectables, usa la tool
conexiones.sistemas.listar, que además dice qué ya
está conectado.
curl https://connect.emisso.ai/api/v1/connectors \
-H "Authorization: Bearer connect_sk_..."{ "data": [{ "code": "core", "name": "Core", "plan": "free", "enabled": true }] }Este endpoint lista solo el catálogo público
/v1/connectors sirve los conectores de uso general (core, echo, indicadores, conexiones). Las fuentes private/tenant como el SII o los bancos no aparecen aquí aunque las tengas conectadas y aunque sus tools sí salgan en /v1/tools. Para ver lo que tu organización tiene conectado, usa /v1/connections o la tool conexiones.estado.consultar.
GET /v1/openapi.json
Sin credencial. El contrato OpenAPI 3.1 del catálogo público, servido por el propio runtime. El contrato del catálogo documentado completo (SII y bancos incluidos) es otro artefacto: /docs/openapi.json.
curl -s https://connect.emisso.ai/api/v1/openapi.json | jq .info{
"title": "Emisso Connect API",
"version": "0.1.0"
}Conexiones
GET /v1/connections
Tus conexiones, cada una la unidad de facturación. De aquí salen los conn_… que necesitas para el header X-Connect-Connection.
curl https://connect.emisso.ai/api/v1/connections \
-H "Authorization: Bearer connect_sk_..."{
"data": [
{
"id": "conn_4ly7hnmw2tigryuxfjkr4",
"connector": "sii",
"display_name": "SII prod",
"status": "active",
"plan": "paid",
"enabled_scopes": ["sii:read"]
}
],
"pagination": { "cursor": null, "hasMore": false }
}Los conectores open y platform nunca aparecen: no hay nada que conectar.
Si eres un agente, prefiere la tool
conexiones.estado.consultar responde lo mismo y además: qué alcances tiene activos cada conexión, en qué van sus sincronizaciones, qué tools puedes invocar sobre ella y datosListos, la respuesta a «¿ya hay algo que leer?». Por ser una tool, se llama igual por MCP que por REST.
Sesiones de conexión
El flujo hosted completo (incrustar el iframe, postMessage, el webhook connect_session.consumed, el modelo de amenaza) vive en el enlace hosted. Aquí, el contrato de los dos endpoints.
POST /v1/connect_sessions
Crea un enlace de un solo uso para que un tercero entregue credenciales de una fuente concreta, sin cuenta y sin acceso al dashboard.
| Campo | Tipo | Notas |
|---|---|---|
mode | "create" | "reauth" | create (default) emite una conexión nueva; reauth reemplaza la credencial de una existente y exige connection_id. |
connector_code | string | Una fuente conectable: sii, bci_pyme, banco_security, bice_empresas, bch_empresas. El enlace queda fijado a esa fuente. |
connection_id | string | Obligatorio en reauth. Una conexión de otra organización degrada al mismo 400 validation_error: nunca un 404 que confirme que existe. |
allowed_origins | string[] | Hasta 5 orígenes https exactos, sin path ni comodín. Allowlist del iframe y destino del postMessage. |
redirect_uri | string | https, sin fragmento y, si registraste orígenes, dentro de ese conjunto. |
El esquema es estricto: un campo desconocido responde 400 validation_error. La emisión está limitada a 60 enlaces por hora por organización; superado el tope, la respuesta es 429 rate_limited con Retry-After. El enlace vence a las 24 horas en create y a la 1 hora en reauth, y admite 5 intentos reales de credencial.
curl -X POST https://connect.emisso.ai/api/v1/connect_sessions \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{
"mode": "create",
"connector_code": "sii",
"allowed_origins": ["https://app.tu-producto.cl"],
"redirect_uri": "https://app.tu-producto.cl/conexiones/listo"
}'Respuesta (201):
{
"data": {
"id": "cs_7f3ab9c1d2e4f5061728",
"mode": "create",
"connector_code": "sii",
"connection_id": null,
"token_prefix": "connect_cs_AbC…",
"allowed_origins": ["https://app.tu-producto.cl"],
"redirect_uri": "https://app.tu-producto.cl/conexiones/listo",
"status": "pending",
"attempts": 0,
"max_attempts": 5,
"expires_at": "2026-08-08T12:00:00.000Z",
"consumed_at": null,
"resulting_connection_id": null,
"created_at": "2026-08-07T12:00:00.000Z",
"url": "https://connect.emisso.ai/c/connect_cs_..."
},
"meta": { "request_id": "req_..." }
}El token aparece una sola vez
El token en claro viaja solo en esta respuesta: la base guarda su sha256 y un prefijo visible para nombrarlo en una lista. Si lo pierdes, emite otra sesión y revoca la anterior.
GET /v1/connect_sessions
Lista las sesiones con su token_prefix, su estado (pending, consumed, revoked, expired) y sus intentos; nunca el token. Acepta ?connection_id=conn_… para acotar a una conexión.
curl "https://connect.emisso.ai/api/v1/connect_sessions?connection_id=conn_..." \
-H "Authorization: Bearer connect_sk_..."{
"data": [
{
"id": "cs_7f3ab9c1d2e4f5061728",
"mode": "reauth",
"connector_code": "sii",
"connection_id": "conn_4ly7hnmw2tigryuxfjkr4",
"token_prefix": "connect_cs_AbC…",
"allowed_origins": [],
"redirect_uri": null,
"status": "consumed",
"attempts": 1,
"max_attempts": 5,
"expires_at": "2026-08-07T13:00:00.000Z",
"consumed_at": "2026-08-07T12:14:09.000Z",
"resulting_connection_id": "conn_4ly7hnmw2tigryuxfjkr4",
"created_at": "2026-08-07T12:00:00.000Z"
}
],
"pagination": { "cursor": null, "hasMore": false }
}Sincronizaciones
conexion.sincronizar como tool corre en el momento y bloquea hasta terminar; en un banco eso ronda los 90 segundos por login. Esta vía asíncrona encola el trabajo y lo consulta después, y es la recomendada para varios períodos o para no sostener una petición HTTP larga.
POST /v1/connections/{conn}/syncs
| Campo | Tipo | Notas |
|---|---|---|
periodos | string[] | Opcional. Cada uno con forma AAAA-MM; sin él se encola el mes en curso. Máximo 24 períodos por petición. |
alcances | string[] | Opcional. Sin él se toman todos los alcances habilitados de la conexión. Cada uno debe estar habilitado en esa conexión. |
curl -X POST https://connect.emisso.ai/api/v1/connections/conn_.../syncs \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{"periodos":["2026-08"],"alcances":["rcv"]}'Respuesta (202), un trabajo por período encolado:
{ "data": { "job_ids": ["sjb_..."] }, "meta": { "request_id": "req_..." } }Un periodo mal formado se rechaza con 400 validation_error antes de ocupar un cupo. La cola admite hasta 50 trabajos pendientes por organización (429 too_many_pending), y un período que ya tiene un trabajo en vuelo responde 409 connection_sync_in_progress.
GET /v1/syncs/{id}
El estado del trabajo. status es uno de queued, running, succeeded, failed o partial.
curl https://connect.emisso.ai/api/v1/syncs/sjb_... \
-H "Authorization: Bearer connect_sk_..."{
"data": {
"job_id": "sjb_...",
"status": "succeeded",
"targets": ["rcv"],
"outcomes": [{ "alcance": "rcv", "status": "ok", "recordsSynced": 12 }],
"alcance": "rcv",
"periodo": "2026-08",
"trigger": "manual",
"attempts": 1,
"records_synced": 12,
"last_error": null,
"created_at": "2026-08-06T17:11:30.000Z",
"updated_at": "2026-08-06T17:11:44.526Z"
}
}outcomes desglosa el resultado por alcance cuando el trabajo terminó bien (cada entrada con status ok, partial o failed); en un trabajo fallido o aún en curso viene null. Si prefieres que te avisen en vez de sondear, los eventos sync.* llegan por webhook firmado.
Webhooks
La firma de las entregas, los cuerpos de cada evento y las políticas de reintento están en webhooks. El índice de la superficie:
| Método y ruta | Qué hace |
|---|---|
POST /v1/webhooks | Crea un endpoint. La única respuesta que muestra el secret. |
GET /v1/webhooks | Lista tus endpoints. |
GET /v1/webhooks/{id} | Un endpoint puntual. |
PATCH /v1/webhooks/{id} | Modifica url, event_types o enabled. |
DELETE /v1/webhooks/{id} | Deshabilita el endpoint. Es un apagado, no un borrado: reaparece con enabled: false. |
POST /v1/webhooks/{id}/roll-secret | Rota el secret y devuelve el nuevo, una única vez. |
GET /v1/webhooks/{id}/deliveries | Las entregas del endpoint. ?limit= entre 1 y 100, default 50. |
POST /v1/webhooks/{id}/deliveries/{deliveryId}/resend | Reenvía una entrega puntual. |
Los eventos suscribibles son sync.succeeded, sync.failed, sync.partial y connect_session.consumed. Un event_types vacío suscribe a todos.
POST /v1/webhooks
| Campo | Tipo | Notas |
|---|---|---|
url | string | https y resoluble a una dirección pública; si no, 422 invalid_webhook_url. |
event_types | string[] | Opcional. Vacío = todos los eventos. |
connection_id | string | Opcional. Acota el endpoint a los eventos de una conexión; debe ser de tu organización. |
curl -X POST https://connect.emisso.ai/api/v1/webhooks \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://app.tu-producto.cl/hooks/connect","event_types":["sync.succeeded","sync.failed"]}'Respuesta (201):
{
"data": {
"id": "whk_...",
"url": "https://app.tu-producto.cl/hooks/connect",
"event_types": ["sync.succeeded", "sync.failed"],
"connection_id": null,
"enabled": true,
"disabled_at": null,
"last_success_at": null,
"created_at": "2026-08-07T12:00:00.000Z",
"secret": "whsec_..."
},
"meta": { "request_id": "req_..." }
}Guarda el secret al recibirlo: las lecturas posteriores (GET /v1/webhooks, GET /v1/webhooks/{id}) devuelven el endpoint sin él.
GET /v1/webhooks/{id}/deliveries
curl "https://connect.emisso.ai/api/v1/webhooks/whk_.../deliveries?limit=2" \
-H "Authorization: Bearer connect_sk_..."{
"data": [
{
"id": "whd_...",
"event_type": "sync.succeeded",
"status": "delivered",
"attempts": 1,
"response_status": 200,
"error": null,
"created_at": "2026-08-07T12:10:00.000Z",
"updated_at": "2026-08-07T12:10:01.000Z"
},
{
"id": "whd_...",
"event_type": "sync.failed",
"status": "failed",
"attempts": 6,
"response_status": 500,
"error": "HTTP 500",
"created_at": "2026-08-06T09:00:00.000Z",
"updated_at": "2026-08-06T18:02:11.000Z"
}
],
"pagination": { "cursor": null, "hasMore": false }
}status es uno de pending, delivering, delivered o failed.
POST /v1/webhooks/{id}/deliveries/{deliveryId}/resend
Reencola una entrega concreta, por ejemplo después de arreglar el receptor.
curl -X POST https://connect.emisso.ai/api/v1/webhooks/whk_.../deliveries/whd_.../resend \
-H "Authorization: Bearer connect_sk_..."{ "data": { "resent": true } }Ejecutar una tool
Es una sola forma: POST /v1/tools/{id}/execute con el cuerpo { "input": { … } }, el header X-Connect-Connection: conn_… cuando el conector tiene conexión, y la respuesta { "data": …, "meta": { "request_id", "tool_id", "plane", "latency_ms", "audit_status" } }. La ficha de cada tool, con su ejemplo, está en la referencia.
Errores frecuentes por superficie
Todos salen del catálogo y traen request_id más suggested_fix.
| Superficie | Códigos que vas a ver |
|---|---|
| Toda la API | 401 unauthorized (falta o falla el bearer, con challenge WWW-Authenticate) · 503 service_unavailable (falla transitoria nuestra, reintenta) · 413 payload_too_large · 400 malformed_request |
| Tools | 404 tool_not_found (el id no existe, o el conector no está habilitado para tu organización) · 400 validation_error · 403 scope_not_granted · 428 connection_credential_required |
| Sincronizaciones | 403 connection_disabled (la conexión no existe en tu organización o no está activa) · 403 alcance_not_enabled · 409 connection_sync_in_progress · 429 too_many_pending · 404 sync_job_not_found |
| Sesiones de conexión | 400 validation_error (incluye connection_id ajeno o inconsistente con mode) · 429 rate_limited con Retry-After |
| Webhooks | 404 webhook_endpoint_not_found · 404 webhook_delivery_not_found · 422 invalid_webhook_url · 403 feature_not_in_plan |
Los 404 de esta tabla nunca distinguen «no existe» de «existe en otra organización»: esa ambigüedad es deliberada.