Emisso Connect

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 responde 400 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 GET de esta página no escriben en la bitácora: no ejecutan ninguna tool. Solo execute audita.

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.

CampoTipoNotas
mode"create" | "reauth"create (default) emite una conexión nueva; reauth reemplaza la credencial de una existente y exige connection_id.
connector_codestringUna fuente conectable: sii, bci_pyme, banco_security, bice_empresas, bch_empresas. El enlace queda fijado a esa fuente.
connection_idstringObligatorio en reauth. Una conexión de otra organización degrada al mismo 400 validation_error: nunca un 404 que confirme que existe.
allowed_originsstring[]Hasta 5 orígenes https exactos, sin path ni comodín. Allowlist del iframe y destino del postMessage.
redirect_uristringhttps, 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

CampoTipoNotas
periodosstring[]Opcional. Cada uno con forma AAAA-MM; sin él se encola el mes en curso. Máximo 24 períodos por petición.
alcancesstring[]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 rutaQué hace
POST /v1/webhooksCrea un endpoint. La única respuesta que muestra el secret.
GET /v1/webhooksLista 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-secretRota el secret y devuelve el nuevo, una única vez.
GET /v1/webhooks/{id}/deliveriesLas entregas del endpoint. ?limit= entre 1 y 100, default 50.
POST /v1/webhooks/{id}/deliveries/{deliveryId}/resendReenví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

CampoTipoNotas
urlstringhttps y resoluble a una dirección pública; si no, 422 invalid_webhook_url.
event_typesstring[]Opcional. Vacío = todos los eventos.
connection_idstringOpcional. 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.

SuperficieCódigos que vas a ver
Toda la API401 unauthorized (falta o falla el bearer, con challenge WWW-Authenticate) · 503 service_unavailable (falla transitoria nuestra, reintenta) · 413 payload_too_large · 400 malformed_request
Tools404 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
Sincronizaciones403 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ón400 validation_error (incluye connection_id ajeno o inconsistente con mode) · 429 rate_limited con Retry-After
Webhooks404 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.

On this page