Autenticación
API keys con permisos explícitos para tu backend, OAuth 2.1 para clientes MCP: cómo se autentica cada llamada al gateway.
Connect tiene dos superficies de entrada, REST (/api/v1/*) y MCP (/mcp), y las dos autentican antes de resolver cualquier otra cosa: el método, el content-type y, sobre todo, la existencia de la tool. Quien llama sin credencial válida no puede distinguir «esa tool no existe» de «existe pero no tienes acceso». En un gateway multitenant, esa asimetría protege el catálogo de cada organización.
API keys
La credencial para llamar desde tu backend es una API key: prefijo connect_sk_, enviada como Authorization: Bearer. Se crean en connect.emisso.ai/api-keys, con nombre, permisos y vencimiento opcional.
El valor completo aparece una sola vez, al crearla. La base guarda su sha256 y un prefijo visible (connect_sk_7Qf3…) para nombrarla en la lista; si la pierdes no hay forma de recuperarla. Rotar es el mismo gesto: la opción «rotar» del dashboard crea un reemplazo con los mismos permisos y te ofrece revocar la anterior justo después de copiar el valor nuevo.
Con la clave en la mano, una llamada autenticada:
curl -X POST https://connect.emisso.ai/api/v1/tools/core.timestamp.now/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{"input": {"timezone": "America/Santiago"}}'Respuesta (200):
{
"data": {
"iso": "2026-08-07T14:32:11.412Z",
"unix": 1786113131,
"timezone": "America/Santiago"
},
"meta": {
"request_id": "req_...",
"tool_id": "core.timestamp.now",
"plane": "action",
"latency_ms": 12,
"audit_status": "recorded"
}
}Toda respuesta trae este envelope {data, meta}, y el meta.request_id identifica la fila que la llamada dejó en la bitácora. Ahí la clave aparece como lo que es: una API key autentica como agente (actor_type: "agent"), y su id viaja aparte en la columna credential_id. La acción de un agente nunca se atribuye al humano que creó la clave.
Scopes
Cada clave lleva scopes, la lista de permisos que puede ejercer. Se otorgan por conector, no por tool: todas las tools de un sistema comparten su scope, y una tool nueva de un conector que ya usas reutiliza el permiso existente en vez de estrenar uno (si lo estrenara, toda clave anterior despertaría con un 403 inexplicable). El vocabulario completo:
| Scope | Qué permite |
|---|---|
sii:read | Compras y ventas (RCV), boletas, guías de despacho, boletas de honorarios y el respaldo XML de los documentos emitidos |
bci_pyme:read | Saldos y movimientos del Banco BCI |
banco_security:read | Saldos, movimientos, transferencias y nóminas del Banco Security |
bice_empresas:read | Saldos y movimientos del Banco BICE |
bch_empresas:read | Saldos, movimientos y cartolas del Banco de Chile |
banco_estado:read | Saldos y movimientos de BancoEstado |
previred:read | Planillas pagadas, cotizaciones por trabajador, deuda previsional y certificados de cotizaciones |
indicadores:read | UF, dólar, euro, IPC y UTM |
core:read | Diagnóstico: hora del servidor |
echo:read | Diagnóstico: eco |
conexiones:read | Ver qué sistemas están conectados, si ya sincronizaron y qué herramientas ofrecen |
conexiones:write | Emitir enlaces de un solo uso donde una persona entrega sus credenciales reales del banco o del SII |
notta:read | Ver, listar y descargar las facturas y notas ya emitidas |
notta:write | Emitir facturas y notas ante el SII a nombre de la empresa de esta conexión, y reenviar por correo un documento ya emitido. Un documento emitido no se borra: solo se corrige con una nota de crédito |
Tres reglas gobiernan la lista:
- Todo es explícito. El formulario emite exactamente los permisos que marcaste; ni
*ni comodines por conector (sii:*) se aceptan. Puedes otorgar hoy el scope de un sistema que aún no conectaste: queda inerte hasta que la conexión exista, y conectar el sistema después no obliga a re-acuñar la clave. - Vacío significa cero autoridad, nunca «todos los permisos». Por eso una clave sin ningún permiso marcado no se puede crear; si construyes sobre este modelo, esa es la trampa a evitar.
- Lo que no termina en
:reades sensible. Hoy son dos:conexiones:write, que emite enlaces donde una persona entrega credenciales reales, ynotta:write, que emite facturas y notas ante el SII a nombre de la empresa de esta conexión y reenvía las ya emitidas al correo que se le indique (un documento emitido no se borra, solo se corrige con una nota de crédito). El dashboard aísla a los dos, cada uno en su propio contenedor de advertencia.
Una llamada que exige un scope que la clave no tiene responde 403 scope_not_granted, con su suggested_fix en el envelope (catálogo de errores).
scope no es alcance
scope es el permiso de ejecución («¿puedes llamar esto?»). alcance es un módulo de datos del plano read («¿qué subconjunto sincroniza esta conexión?»). Son ejes independientes: sii:read es un scope; rcv y boletas son alcances. La distinción completa está en Los dos planos.
OAuth 2.1 para clientes MCP
Un cliente MCP interactivo (Claude, ChatGPT, un agente de terceros) no maneja una API key estática, así que /mcp es además un OAuth 2.1 Resource Server y el enrolamiento ocurre solo. Al agregar https://connect.emisso.ai/mcp en el cliente:
- El cliente descubre los metadatos del recurso protegido (
/.well-known/oauth-protected-resource) y, desde ahí, el authorization server. - Se registra en caliente en
/register(Dynamic Client Registration): sin coordinación previa y sin client secret, porque solo se admiten clientes públicos. - Abre
/authorizecon PKCE S256 obligatorio; una persona inicia sesión, elige la organización y aprueba. - Canjea el código en
/tokeny recibe un access tokenconnect_at_y un refresh tokenconnect_rt_.
El parámetro resource del flujo (RFC 8707) liga el token a la audiencia de este gateway, y cada llamada la verifica: el token no sirve contra ningún otro recurso. Y autentica como usuario: la bitácora registra actor_type: "user" con la identidad de quien aprobó, en vez de un agente anónimo.
El challenge
Una llamada a /mcp sin bearer no revela nada del catálogo; responde el challenge estándar:
curl -i -X POST https://connect.emisso.ai/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'Respuesta (cabeceras recortadas):
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://connect.emisso.ai/.well-known/oauth-protected-resource"
{"jsonrpc":"2.0","id":null,"error":{"code":-32001,"message":"unauthorized"}}Ese header le dice al cliente dónde iniciar el flujo OAuth. El mismo challenge protege /api/v1/*. La falla queda registrada en security_events con el prefijo de la credencial; a la bitácora no llega, porque una falla de autenticación no tiene organización que la reciba.
Discovery y challenge no se mezclan
/.well-known/oauth-protected-resource (RFC 9728) es un documento de descubrimiento público que siempre responde 200, sin autenticación. El challenge 401 + WWW-Authenticate vive solo en /mcp y /api/v1/*. Un cliente que recibiera un 401 en el endpoint de metadatos no podría completar el flujo nunca.
Cuándo usar cuál
Las dos superficies aceptan las dos credenciales; la elección real es quién custodia el secreto.
API key (connect_sk_) | OAuth 2.1 (connect_at_ / connect_rt_) | |
|---|---|---|
| Caso natural | Tu backend, scripts y agentes propios, sobre REST o MCP | Un cliente MCP interactivo que una persona conecta |
| Cómo nace | La creas tú en el dashboard | El cliente se registra solo (DCR) y una persona aprueba |
| Autoridad | Los scopes explícitos que marcaste | Los permisos que pide el cliente, acotados por tu rol en la organización elegida al aprobar |
| Actor en la bitácora | agent | user |
| Vida | Hasta revocarla, o el vencimiento que le pongas | El access token vence y el cliente lo renueva con el refresh token |
Próximos pasos
- Tu primera llamada: el quickstart con un dato real, sin conexión de por medio.
- Conectar un sistema: del enlace de conexión a la primera sincronización.
- MCP: las dos meta-tools y el recorrido que hace un agente.