# 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](/docs/referencia) y en el [OpenAPI](/docs/api). 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.
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 [#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](/docs/operar/errores).
* Los `GET` de esta página no escriben en la bitácora: no ejecutan ninguna tool. Solo `execute` audita.
## Descubrimiento [#descubrimiento]
### `GET /v1/health` [#get-v1health]
Sin credencial. Dice si el gateway está arriba y cuántas tools tiene el registro.
```bash
curl https://connect.emisso.ai/api/v1/health
```
```json
{ "data": { "status": "ok", "registry": { "tools": 29 } } }
```
### `GET /v1` [#get-v1]
Sin credencial. El puntero raíz del API; a diferencia del resto, responde sin el envelope `data`.
```bash
curl https://connect.emisso.ai/api/v1
```
```json
{
"data_url": "/api/v1",
"openapi": "/api/v1/openapi.json",
"docs": "https://connect.emisso.ai/docs"
}
```
### `GET /v1/tools` [#get-v1tools]
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:
```bash
curl https://connect.emisso.ai/api/v1/tools \
-H "Authorization: Bearer connect_sk_..."
```
```json
{
"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](/docs/conceptos/sincronizar-consultar).
### `GET /v1/tools/{id}` [#get-v1toolsid]
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`.
```bash
curl https://connect.emisso.ai/api/v1/tools/sii.rcv.consultar \
-H "Authorization: Bearer connect_sk_..."
```
```json
{ "data": { "id": "sii.rcv.consultar", "…": "la misma ficha de GET /v1/tools" } }
```
### `GET /v1/connectors` [#get-v1connectors]
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`](/docs/referencia/conexiones/sistemas-listar), que además dice qué ya
está conectado.
```bash
curl https://connect.emisso.ai/api/v1/connectors \
-H "Authorization: Bearer connect_sk_..."
```
```json
{ "data": [{ "code": "core", "name": "Core", "plan": "free", "enabled": true }] }
```
`/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` [#get-v1openapijson]
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`](https://connect.emisso.ai/docs/openapi.json).
```bash
curl -s https://connect.emisso.ai/api/v1/openapi.json | jq .info
```
```json
{
"title": "Emisso Connect API",
"version": "0.1.0"
}
```
## Conexiones [#conexiones]
### `GET /v1/connections` [#get-v1connections]
Tus conexiones, cada una la unidad de facturación. De aquí salen los `conn_…` que necesitas para el header `X-Connect-Connection`.
```bash
curl https://connect.emisso.ai/api/v1/connections \
-H "Authorization: Bearer connect_sk_..."
```
```json
{
"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.
`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 [#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](/docs/operar/enlace-hosted). Aquí, el contrato de los dos endpoints.
### `POST /v1/connect_sessions` [#post-v1connect_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.
```bash
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`):
```json
{
"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 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` [#get-v1connect_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.
```bash
curl "https://connect.emisso.ai/api/v1/connect_sessions?connection_id=conn_..." \
-H "Authorization: Bearer connect_sk_..."
```
```json
{
"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 [#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` [#post-v1connectionsconnsyncs]
| 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. |
```bash
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:
```json
{ "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}` [#get-v1syncsid]
El estado del trabajo. `status` es uno de `queued`, `running`, `succeeded`, `failed` o `partial`.
```bash
curl https://connect.emisso.ai/api/v1/syncs/sjb_... \
-H "Authorization: Bearer connect_sk_..."
```
```json
{
"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 [#webhooks]
La firma de las entregas, los cuerpos de cada evento y las políticas de reintento están en [webhooks](/docs/operar/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` [#post-v1webhooks]
| 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. |
```bash
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`):
```json
{
"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` [#get-v1webhooksiddeliveries]
```bash
curl "https://connect.emisso.ai/api/v1/webhooks/whk_.../deliveries?limit=2" \
-H "Authorization: Bearer connect_sk_..."
```
```json
{
"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` [#post-v1webhooksiddeliveriesdeliveryidresend]
Reencola una entrega concreta, por ejemplo después de arreglar el receptor.
```bash
curl -X POST https://connect.emisso.ai/api/v1/webhooks/whk_.../deliveries/whd_.../resend \
-H "Authorization: Bearer connect_sk_..."
```
```json
{ "data": { "resent": true } }
```
## Ejecutar una tool [#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](/docs/referencia).
## Errores frecuentes por superficie [#errores-frecuentes-por-superficie]
Todos salen del [catálogo](/docs/operar/errores) 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.
---
# Changelog
> Qué cambió en el catálogo y qué garantiza que construir encima sea seguro.
Esta página registra los cambios visibles del catálogo y de la plataforma. Antes de la lista, la garantía que gobierna todos los cambios.
## La garantía de estabilidad [#la-garantía-de-estabilidad]
Los ids de tool son **estables para siempre**. `sii.rcv.consultar` significa hoy lo que va a significar en cinco años, y el código que escribas contra un id publicado no se rompe con un deploy nuestro.
* Un cambio **incompatible** nunca muta la tool publicada: se acuña un **id nuevo** y se bumpea `version` (un entero monotónico que viaja en la ficha de cada tool).
* Un cambio **aditivo** (un campo nuevo en la salida, un parámetro opcional) conserva el id y bumpea `version`. Tu código existente sigue corriendo igual.
* `deprecated: true` en la ficha significa que la tool **sigue funcionando** pero tiene sucesora: migra con calma, sin fecha límite sorpresa.
La excepción es una y estrecha: retirar una tool y reutilizar su id para otra de distinta clase se permite solo cuando la bitácora confirma que ese id no tenía consumidores reales, y el cambio queda argumentado por escrito. Ha ocurrido una vez, y está en la tabla de abajo marcada como el único «Sí» de la columna «¿Rompe?».
## Cambios [#cambios]
| Fecha | Qué cambió | ¿Rompe? | Docs |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -------------------------------------------------------------------------------------- |
| 2026-08-16 | Los dos documentos de descubrimiento OAuth (`/.well-known/oauth-authorization-server` y `/.well-known/oauth-protected-resource`) dejan de publicar `scopes_supported: ["*"]` y pasan a listar los permisos reales del catálogo. Los consentimientos ya otorgados no cambian: el próximo consentimiento que un cliente pida recibe la autoridad que permite el rol de quien aprueba. El documento deja de sugerir el comodín; un owner o admin lo puede seguir otorgando, explícito o por omisión, igual que antes. | No | [Autenticación](/docs/empezar/autenticacion) |
| 2026-08-06 | La superficie MCP anuncia solo las dos meta-tools: `tools/list` devuelve `search_docs` y `execute`, con cualquier catálogo y cualquier organización. Los nombres nativos (`sii__rcv__consultar`) siguen despachando por `tools/call` como puerta de compatibilidad no anunciada. | No | [El servidor MCP](/docs/agentes/mcp) |
| 2026-08-06 | `indicadores.valor.actual` y `indicadores.valor.consultar` pasan a versión 2: la primera suma `antiguedadDias` (0 = el valor es de hoy, positivo = atrasado, negativo = fechado en el futuro) y la segunda suma `fechaSolicitada` y `esArrastre`. Campos nuevos en la salida, ids intactos. | No | [indicadores.valor.actual](/docs/referencia/indicadores/valor-actual) |
| 2026-07-31 | Las tools de consulta de `bci_pyme` y `banco_security` dejan de abrir sesión en vivo contra el banco: ahora leen el plano ya sincronizado, igual que las del SII y BICE. Mismos ids re-acuñados para otra clase de tool, con bump de `version`; la bitácora confirmó cero consumidores del contrato anterior antes de re-acuñarlos. | Sí | [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar) |
| 2026-07-30 | SII: nuevo alcance `boletas_honorarios` (boletas de honorarios electrónicas, emitidas y recibidas) y la tool `sii.boletas_honorarios.consultar`. | No | [sii.boletas\_honorarios.consultar](/docs/referencia/sii/boletas_honorarios-consultar) |
| 2026-07-30 | Nuevo conector `conexiones`: `sistemas.listar`, `enlace.crear` y `estado.consultar`. Un agente puede acuñar el enlace para conectar un sistema y consultar su estado sin salir de MCP. | No | [Conectar un sistema](/docs/empezar/conectar) |
| 2026-07-28 | SII: nuevo alcance `guias` (guías de despacho, DTE 52) y la tool `sii.guias.consultar`. El SII solo retiene el detalle de los últimos 6 meses, así que la historia se construye sincronizando de forma continua, no con un backfill. | No | [sii.guias.consultar](/docs/referencia/sii/guias-consultar) |
## Cómo enterarse [#cómo-enterarse]
Esta página y el índice [`/llms.txt`](https://connect.emisso.ai/llms.txt) reflejan el estado vigente; la referencia por tool y los contratos OpenAPI se regeneran con cada deploy desde el mismo registro que sirve las llamadas, así que no pueden quedarse atrás. Si automatizas contra Connect, `GET /v1/tools` te da la `version` y el flag `deprecated` de cada tool en runtime, sin esperar a que alguien lea una página.
---
# Emisso Connect
> Un endpoint para los sistemas chilenos. Conecta tu backend o tu agente con el SII, los bancos de empresa y los indicadores del Banco Central, con auditoría completa de cada llamada.
Emisso Connect es el puente entre tu software (o tu agente) y los sistemas chilenos: SII, bancos de
empresa e indicadores del Banco Central. Declaras qué empresa autorizó qué, y Connect se encarga del
login, la sincronización y la trazabilidad.
## El modelo mental, en cuatro piezas [#el-modelo-mental-en-cuatro-piezas]
| Pieza | Qué es |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Tool** | Cada capacidad del catálogo, con id estable `sistema.recurso.verbo` (por ejemplo `sii.rcv.consultar`). Se llama igual por REST, SDK o MCP. |
| **Conexión** | Una empresa que autorizó un sistema: `conn_…`. La credencial la entrega la persona por un enlace de un solo uso y queda cifrada en el vault, sin pasar nunca por tu código. La conexión es la empresa: toda tool de un sistema conectable exige `connectionId`, incluso si tienes una sola. |
| **Dos pasos para leer** | `sincronizar` hace el login real y guarda los datos; `consultar` lee lo ya guardado, al instante. El porqué está en [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar). |
| **Bitácora** | Cada llamada deja exactamente una fila auditable con su `request_id`, la misma fila con que se factura. Está en [connect.emisso.ai/bitacora](https://connect.emisso.ai/bitacora). |
## Elige tu camino [#elige-tu-camino]
* [**Tu primera llamada**](/docs/empezar/primera-llamada): la UF de hoy por REST, SDK o MCP, sin conectar nada. Cerca de 2 minutos.
* [**Conecta tu primera empresa**](/docs/empezar/conectar): enlace hosted, clave del SII, primera sincronización y primera lectura. Cerca de 10 minutos.
* [**Instala el servidor MCP**](/docs/agentes/mcp): dos herramientas, todo el catálogo, para Claude, ChatGPT o Cursor.
* [**Referencia**](/docs/referencia): las tools del catálogo con sus contratos, ejemplos y errores.
Todo este sitio existe en markdown: el índice está en [`/llms.txt`](/llms.txt), cada página en
`/docs/.md`, el contrato completo en [`/docs/openapi.json`](/docs/openapi.json). O conéctate
directo por MCP: `https://connect.emisso.ai/mcp`. El inventario completo está en
[Recursos máquina-legibles](/docs/agentes/recursos).
---
# Política de privacidad
> Qué datos trata Emisso Connect, cómo los protege, con quién los comparte y cómo se eliminan.
**Última actualización:** 17 de agosto de 2026
Emisso Connect es un intermediario técnico entre el software de una empresa y los sistemas chilenos con
los que necesita hablar: el Servicio de Impuestos Internos, Previred y los bancos. Este documento
describe qué datos pasan por nuestra infraestructura, cómo los protegemos y qué puedes exigirnos.
Está escrito para que se entienda, no para cubrirnos. Cuando algo todavía no está implementado, lo
decimos.
***
## Quiénes somos [#quiénes-somos]
Emisso SpA, con domicilio en Chile, opera Emisso Connect en `connect.emisso.ai`.
Para efectos de la Ley 19.628 sobre protección de la vida privada y de su reforma (Ley 21.719), somos
**responsables del tratamiento** respecto de los datos de tu cuenta, y **encargados del tratamiento**
respecto de los datos que sincronizamos por instrucción tuya desde el SII, Previred o tu banco: esos
datos son de tu empresa y nosotros los procesamos en tu nombre.
**Contacto:** [hola@emisso.ai](mailto:hola@emisso.ai)
***
## Qué datos tratamos [#qué-datos-tratamos]
### Datos de tu cuenta [#datos-de-tu-cuenta]
Correo electrónico, nombre de la organización y, si postulaste a la lista de espera, los datos que
entregaste en ese formulario. Se usan para darte acceso y para contactarte sobre el servicio.
### Credenciales de acceso a sistemas externos [#credenciales-de-acceso-a-sistemas-externos]
Cuando conectas el SII, Previred o un banco, nos entregas la clave con que ese sistema te identifica.
* Se **cifra con AES-256-GCM** antes de guardarse, con una clave de cifrado propia de tu organización.
* **Nunca viaja a un modelo de lenguaje**, no aparece en el resultado de ninguna herramienta y no se
escribe en ningún registro.
* Se usa **exclusivamente** para iniciar sesión en ese sistema cuando tú pides una sincronización.
* Es **revocable al instante** desde el panel.
* El acceso que pedimos es **de solo lectura** en el SII, Previred y los bancos.
Si prefieres no entregarnos la clave directamente, el widget de conexión permite que la persona dueña
de la credencial la ingrese ella misma mediante un enlace de un solo uso, sin que pase por tu sistema
ni por el nuestro más allá de ese formulario.
### Datos sincronizados desde esos sistemas [#datos-sincronizados-desde-esos-sistemas]
Documentos tributarios, saldos y movimientos bancarios, cotizaciones previsionales: lo que
específicamente hayas habilitado. Los identificadores de contribuyente (RUT) y las razones sociales se
guardan **cifrados por columna**, y los montos y fechas en claro para poder consultarlos y sumarlos.
### Registro de actividad [#registro-de-actividad]
Cada llamada a una herramienta deja **una fila de bitácora** con qué herramienta se ejecutó, cuándo,
por parte de quién, cuánto tardó y si tuvo éxito. Esa bitácora es también la base de la facturación.
**Del contenido de la llamada solo guardamos una huella criptográfica**, no los datos: un resumen
irreversible que permite verificar que dos llamadas fueron idénticas sin conservar lo que decían.
### Lo que NO hacemos [#lo-que-no-hacemos]
* No vendemos datos, ni los cedemos con fines comerciales o publicitarios.
* No usamos los datos de tu empresa para entrenar modelos.
* No accedemos a tus datos sincronizados salvo que nos lo pidas para resolver un problema.
* No recogemos datos de tus conversaciones con Claude ni con ningún otro agente.
***
## Cómo protegemos los datos [#cómo-protegemos-los-datos]
**Aislamiento entre empresas, impuesto por la base de datos.** Cada fila lleva la organización a la que
pertenece y hay políticas de seguridad a nivel de fila que impiden verla desde otra. El servicio se
conecta con un rol que **no puede saltarse** esas políticas y que **no tiene permiso de borrado** sobre
ninguna tabla. Un error de programación que olvidara filtrar por organización devolvería cero filas, no
las de otro cliente.
**Cifrado en dos niveles.** Cada organización tiene su propia clave de datos, y esa clave está a su vez
cifrada con una clave maestra guardada aparte. Cada valor cifrado queda atado a su fila: un dato copiado
a otra organización o a otra fila no se puede descifrar.
**Registro inalterable.** La bitácora es de solo escritura: el servicio no tiene permiso para modificar
ni borrar una fila ya escrita.
**Autenticación.** El acceso programático usa claves de API con permisos acotados. Para agentes usamos
OAuth 2.1 con registro dinámico de clientes y PKCE.
***
## Con quién compartimos datos [#con-quién-compartimos-datos]
Solo con proveedores de infraestructura necesarios para operar, y únicamente lo necesario:
| Proveedor | Para qué | Dónde |
| --------- | -------------------------------------------------- | -------------- |
| Supabase | Base de datos y autenticación | Estados Unidos |
| Vercel | Ejecución de la aplicación | Estados Unidos |
| Notta | Emisión de documentos tributarios, solo si la usas | Chile |
Los sistemas a los que te conectas (SII, Previred, tu banco) reciben tu credencial porque esa es
justamente la operación que nos pides. Cada uno tiene sus propias políticas.
**Transferencia internacional:** nuestra infraestructura está en Estados Unidos, así que los datos se
tratan fuera de Chile. Al usar el servicio aceptas esa transferencia.
***
## Cuánto tiempo conservamos los datos [#cuánto-tiempo-conservamos-los-datos]
* **Credenciales:** hasta que las revoques. Al revocarlas, el material cifrado se sobrescribe y la
credencial queda marcada como revocada.
* **Datos sincronizados:** mientras la conexión exista. Al eliminar una conexión desde el panel, se
eliminan también sus datos sincronizados.
* **Bitácora:** se conserva como registro de auditoría y respaldo de la facturación.
**Con transparencia:** todavía **no** tenemos un proceso automático de eliminación por antigüedad. La
eliminación ocurre cuando tú borras una conexión o nos lo pides. Si necesitas un plazo de retención
específico, escríbenos y lo acordamos por escrito.
***
## Tus derechos [#tus-derechos]
Puedes pedirnos **acceder** a tus datos, **rectificarlos**, **eliminarlos**, **oponerte** a su
tratamiento y **portarlos** en un formato legible por máquina. Buena parte la puedes ejercer sola desde
el panel: ver tus conexiones, revocar credenciales y eliminar conexiones con sus datos.
Para lo demás, escríbenos a [hola@emisso.ai](mailto:hola@emisso.ai). Respondemos dentro de 30 días.
***
## Incidentes de seguridad [#incidentes-de-seguridad]
Si ocurre una vulneración que afecte tus datos, te avisaremos **sin demora indebida**, con qué pasó, qué
datos se vieron afectados y qué estamos haciendo.
Si encuentras una vulnerabilidad, escríbenos a [hola@emisso.ai](mailto:hola@emisso.ai). No tomaremos
acciones legales contra quien la reporte de buena fe y nos dé un plazo razonable para corregirla.
***
## Menores de edad [#menores-de-edad]
El servicio está dirigido a empresas. No lo ofrecemos a menores de 18 años ni recogemos sus datos a
sabiendas.
***
## Cambios [#cambios]
Si cambiamos esta política de forma sustancial, avisaremos por correo a las organizaciones activas antes
de que entre en vigor. La fecha del encabezado siempre indica la última versión.
---
# SDK de TypeScript
> En camino a npm. El contrato de @emisso/connect, el cliente tipado y sus reglas de reintento, documentados para cuando se publique.
`pnpm add @emisso/connect` aún no funciona fuera de este repositorio. Hoy la integración real es HTTP
directo: los mismos endpoints de [tu primera llamada](/docs/empezar/primera-llamada) y de la
[API de control](/docs/api-control), con `Authorization: Bearer connect_sk_…`. Esta página documenta el
contrato del SDK tal como existe en el código, para que la migración sea directa cuando se publique.
`@emisso/connect` es el SDK oficial en TypeScript para Connect: habla REST por debajo (los mismos endpoints de [tu primera llamada](/docs/empezar/primera-llamada)) y expone encima un **accessor tipado**, generado desde el registro de tools, sin escribir a mano los tipos de cada `connector.resource.verb`.
## Instalación [#instalación]
```bash
pnpm add @emisso/connect
```
Requiere Node 20 o superior (usa `fetch` nativo; puedes reemplazarlo con `opts.fetch`).
## Primeros pasos [#primeros-pasos]
```ts
import { createClient } from "@emisso/connect";
const connect = createClient({ apiKey: "connect_sk_..." });
const now = await connect.tools.core.timestamp.now({ timezone: "America/Santiago" });
// { iso: "2026-07-21T15:04:00.000Z", unix: 1784646240, timezone: "America/Santiago" }
```
La API key es la misma que creas desde `/api-keys` en el dashboard: el SDK no introduce un modelo de credenciales distinto. El detalle de autenticación y scopes está en [Autenticación](/docs/empezar/autenticacion).
## `createClient(options)` [#createclientoptions]
| Opción | Tipo | Default | Notas |
| ---------------- | --------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey` | `string` | | Tu clave `connect_sk_…`. Provee esta **o** `apiKeyProvider`. |
| `apiKeyProvider` | `() => string \| Promise` | | Fuente asíncrona de la clave (por ejemplo un vault). |
| `baseUrl` | `string` | `https://connect.emisso.ai/api/v1` | Se preserva el subpath; se recorta el `/` final. |
| `maxAttempts` | `number` | `3` | Intentos totales por llamada, el primero incluido. Aplica **solo a GET**. |
| `onUnauthorized` | `() => Promise` | | Seam de refresh OAuth: ante un 401 se llama una vez para obtener un token nuevo y se reintenta la llamada (cualquier verbo). |
| `fetch` | `typeof fetch` | `globalThis.fetch` | Reemplaza la implementación de fetch. |
| `clientTraceId` | `string` | | Trace id por defecto para toda llamada; se sobreescribe por llamada desde `CallOptions`. Ver [Trazabilidad](#trazabilidad-clienttraceid). |
Los errores de configuración (falta `fetch`, falta `apiKey`/`apiKeyProvider`) se lanzan de forma síncrona desde el propio `createClient(...)`, antes de cualquier llamada.
## Accessor tipado [#accessor-tipado]
Cada tool del registro queda disponible como `connect.tools...(input?, opts?)`, con el tipo de `input`/`output` derivado del registro. No hay `any` en el camino:
```ts
// connect.tools...(input?, opts?)
const uf = await connect.tools.indicadores.valor.actual({ codigo: "UF" });
// { codigo: "UF", fecha: "2026-08-08", valor: 39487.23, unidad: "CLP", antiguedadDias: -1 }
// .withResponse(...) devuelve el envelope completo { data, requestId, meta, pagination }
const res = await connect.tools.core.timestamp.now.withResponse();
console.log(res.requestId, res.data);
// escape hatch genérico, útil si el id de la tool es dinámico
const data = await connect.tools.execute("core.timestamp.now", {});
```
La llamada por defecto (sin `.withResponse`) devuelve directamente el `data` de salida. Para el `request_id`, `meta` o `pagination` completos, usa `.withResponse(...)`. El catálogo completo de tools, con la ficha de cada una, vive en [la referencia](/docs/referencia).
## Selección de conexión [#selección-de-conexión]
Toda tool de un conector **con conexión** (el SII, los bancos) exige `connectionId` en `opts`. Siempre, incluso si tienes una sola conexión de ese sistema: no hay resolución implícita.
```ts
await connect.tools.sii.conexion.sincronizar(
{ periodo: "2026-06", alcances: ["rcv"] },
{ connectionId: "conn_..." },
);
```
Sin él la llamada falla con `400 validation_error` y un `suggestedFix` que dice de dónde sacar el id.
La conexión **es la empresa**: dos conexiones de un banco son dos RUT distintos. Una elección implícita que hoy acierta porque hay una sola, mañana (al conectar la segunda empresa) acierta distinto sin que nadie haya cambiado una línea. Es exactamente el tipo de cambio silencioso que no queremos en plata ajena.
Los conectores **sin** conexión quedan exentos, porque no hay nada que elegir: `core`, `indicadores` y `conexiones` no tienen fila en `connections`. `conexiones.enlace.crear` es el caso que lo hace obvio: todavía no existe la conexión que crearía.
Para obtener los `conn_…` de tu organización, pregúntaselos al propio gateway:
```ts
const { conexiones } = await connect.tools.conexiones.estado.consultar({});
for (const cx of conexiones) {
console.log(cx.id, cx.sistema, cx.nombre, cx.alcances, cx.datosListos);
}
```
`datosListos` responde «¿ya puedo leer?»: un listado vacío no es lo mismo que «no hay nada», puede ser que todavía no sincronizó. El porqué de esa separación está en [sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
`opts: CallOptions = { signal?, connectionId?, clientTraceId? }`. `connectionId` viaja como el header `X-Connect-Connection`; `signal` es un `AbortSignal` estándar para cancelar la llamada; `clientTraceId` se explica abajo.
## Trazabilidad: `clientTraceId` [#trazabilidad-clienttraceid]
Una API key autentica como **un** agente. Si tu aplicación sirve a varias personas con la misma clave, la bitácora las ve a todas como el mismo actor y «quién pidió esto» queda sin respuesta. `clientTraceId` es la vía para decirlo:
```ts
const connect = createClient({ apiKey, clientTraceId: "miapp:tenant-42" });
// o por llamada, que gana sobre el del cliente
await connect.tools.sii.rcv.consultar({ limit: 10 }, { connectionId: "conn_...", clientTraceId: "miapp:tenant-42:hilo-9" });
```
Viaja como el header `x-client-trace-id` y se guarda en `bitacora_execution.client_trace_id`, en su propia columna y explícitamente no confiable: nunca se confunde con el `request_id` que emite el servidor. El SDK lo sanea antes de mandarlo: descarta caracteres de control y recorta a 200 caracteres, para que un valor construido concatenando texto de un tercero no pueda inyectar cabeceras.
## Errores [#errores]
`ConnectError` se lanza para fallas a nivel del gateway (una respuesta HTTP no-ok) y para errores de configuración del cliente. Los de configuración salen del propio `createClient(...)`, así que el `try/catch` de abajo, alrededor de una llamada a una tool, no los cubre:
```ts
import { ConnectError } from "@emisso/connect";
try {
await connect.tools.core.timestamp.now();
} catch (e) {
if (e instanceof ConnectError) {
console.error(e.code, e.status, e.requestId, e.suggestedFix);
}
}
```
`code` es un valor del [catálogo de errores](/docs/operar/errores) compartido (`@emisso/contracts`), o `"transport_error"` cuando el cuerpo del error no trae un `error.code` parseable. Todo error del gateway trae `requestId` y un `suggestedFix` pensado para que un agente LLM pueda actuar sobre él directamente. Cuando el servidor adjunta detalle estructurado (por ejemplo los issues campo a campo de un `validation_error`), llega en `e.details`.
Una falla de red que nunca llega al gateway (DNS, conexión rechazada, una request abortada) se propaga como el rechazo nativo de `fetch`; **no** se envuelve en un `ConnectError`.
## Reintentos [#reintentos]
Los `GET` se reintentan hasta `maxAttempts` cuando el error es retryable según el catálogo. Si la respuesta trae `Retry-After` (en segundos o como fecha HTTP), se respeta; si no, el backoff es exponencial, con un techo de 8 segundos entre intentos.
Toda llamada a una tool es un `POST` (`execute`), y el SDK **nunca la reintenta automáticamente**: una acción regulada (un giro bancario, una declaración SII) no puede arriesgarse a un doble filing. La única excepción es el refresh OAuth: un único 401→refresh→reintento vía `onUnauthorized`, para cualquier verbo, fuera del presupuesto de `maxAttempts`.
Un `Idempotency-Key` viaja en cada llamada como preparación a futuro (hoy es un no-op en el servidor) y se mantiene **estable** a través del reintento por refresh OAuth: cuando el servidor active el almacén de idempotencia, un POST reintentado tras renovar el token deduplicará contra su primer intento en vez de ejecutarse dos veces.
## Tipos exportados [#tipos-exportados]
Además de `createClient` y `ConnectError`, el paquete exporta `ClientOptions`, `ConnectClient` (el tipo del cliente ya construido, útil para pasarlo entre funciones) y `ConnectToolIO` (el mapa `id → { input, output }` de todas las tools, el mismo del que se deriva el accessor).
---
# Servidor MCP
> Connect es MCP-nativo, con dos herramientas que no cambian nunca, search_docs para descubrir y execute para correr, sobre el catálogo completo.
El servidor vive en `https://connect.emisso.ai/mcp`: remoto, HTTP, con OAuth 2.1. No hay paquete que
instalar. Anuncia dos herramientas, siempre las mismas, con cualquier catálogo: el catálogo completo
viaja por dentro, sin inflar el contexto de tu agente.
## Conéctalo a tu cliente [#conéctalo-a-tu-cliente]
### Claude Code [#claude-code]
```bash
claude mcp add --transport http connect https://connect.emisso.ai/mcp
```
### Claude.ai [#claudeai]
Ajustes, **Conectores**, *Agregar conector personalizado*, y pega `https://connect.emisso.ai/mcp`. El
navegador abre el consentimiento OAuth de Connect: inicias sesión con tu cuenta y autorizas la
organización.
### Cursor y otros clientes [#cursor-y-otros-clientes]
```json
{ "mcpServers": { "connect": { "url": "https://connect.emisso.ai/mcp" } } }
```
La autorización es OAuth 2.1 con registro dinámico de clientes y PKCE. Tu agente actúa como tú, dentro
de tu organización, y su acceso se corta al instante desde
[Agentes](https://connect.emisso.ai/agentes): la revocación cierra todas sus sesiones y deja de
autenticar en la petición siguiente. El detalle del flujo está en
[Autenticación](/docs/empezar/autenticacion).
## Las dos herramientas [#las-dos-herramientas]
| Herramienta | Qué hace |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `search_docs` | Qué existe. Sin argumentos devuelve el catálogo entero agrupado por sistema, con `conectado` por sistema y `disponible` por tool. Con `{ "tool": "sii.rcv.consultar" }` devuelve la ficha completa con su esquema de salida y un ejemplo ejecutable. Llámala primero. |
| `execute` | Correr una tool: `{ tool, params, connectionId }`. `params` es obligatorio (`{}` si no toma argumentos). `connectionId` es obligatorio en todo sistema conectable: la conexión es la empresa, y no hay elección implícita ni con una sola. |
Un catálogo completo como tools nativas quema la ventana de contexto del agente y cambia de forma con
cada conector nuevo. Dos meta-tools la mantienen constante. Es el mismo argumento con el que Stripe
migró su servidor MCP a `stripe_api_search` y compañía; Connect llegó a este diseño primero.
## El recorrido que tu agente va a seguir [#el-recorrido-que-tu-agente-va-a-seguir]
1. `search_docs` para ver los sistemas y qué está `conectado`.
2. `execute { "tool": "conexiones.estado.consultar", "params": {} }` para obtener los `connectionId`
y el campo `datosListos`.
3. `execute` de la tool de negocio con su `connectionId`: por ejemplo el RCV de julio.
4. Si un sistema aparece con `conectado: false`, el camino es `conexiones.enlace.crear`: el agente
crea el enlace y **se lo muestra al humano** con el dominio visible. La clave la entrega la
persona, nunca el agente.
## Prompt de arranque [#prompt-de-arranque]
Para darle Connect a un agente con las reglas ya aprendidas:
```text
Tienes Emisso Connect (https://connect.emisso.ai/mcp): el puente a los sistemas chilenos de esta
organización (SII, bancos, indicadores). Reglas:
1. Parte siempre por search_docs. Sin argumentos trae el catálogo entero agrupado por sistema;
con {tool: ""} trae la ficha completa y un ejemplo ejecutable.
2. En todo sistema conectable, connectionId es obligatorio: sácalo de conexiones.estado.consultar.
La conexión es la empresa; no hay elección implícita ni cuando existe una sola.
3. Leer son dos pasos: .conexion.sincronizar escribe (login real contra el sistema, puede
tardar hasta 90 segundos en bancos) y .consultar lee lo ya guardado. Una lista vacía
puede ser un período sin sincronizar: revisa datosListos en conexiones.estado.consultar antes
de concluir que no hay nada.
4. Si un sistema aparece con conectado: false, crea un enlace con conexiones.enlace.crear y
muéstralo con su dominio completo visible, explicando quién lo pidió y para qué. Nunca lo
presentes como un aviso del banco ni del SII.
5. Ante un error, sigue el suggested_fix que viene en la respuesta; no reintentes lo que no es
reintentable. Si no reconoces el error, reporta el request_id.
```
## Qué hace solo y qué no [#qué-hace-solo-y-qué-no]
| Acción | ¿La hace solo? |
| ------------------------------------------------------------------ | ----------------------------------------------------------------- |
| Descubrir el catálogo, consultar estados, leer datos sincronizados | Sí |
| Disparar una sincronización a pedido | Sí, con paciencia: un login bancario tarda cerca de 90 segundos |
| Crear el enlace de conexión | Sí, pero la clave la entrega el humano en la página del enlace |
| Ver o tocar una credencial | Nunca. El vault es inaccesible por diseño, también para el agente |
## Compatibilidad [#compatibilidad]
`tools/call` con un nombre nativo (`sii__rcv__consultar`, la codificación con doble guion bajo) sigue
despachando como puerta de compatibilidad, aunque `tools/list` ya no lo anuncie. Si tu cliente guardó
esos nombres, siguen funcionando.
## Próximos pasos [#próximos-pasos]
* [Recursos máquina-legibles](/docs/agentes/recursos): llms.txt, markdown por página y el OpenAPI completo.
* [Conecta tu primera empresa](/docs/empezar/conectar): el flujo que tu agente va a iniciar.
---
# Recursos máquina-legibles
> Todo el sitio existe en markdown y contratos: pega una URL y descubre el API entero.
Esta documentación tiene dos audiencias, y la segunda no navega: carga todo de una vez. Cada página de `/docs` existe también como markdown limpio, el catálogo completo existe como contrato OpenAPI 3.1 y el servidor MCP se describe solo. Pega cualquiera de estas URLs en el contexto de un agente y el agente tiene el API entero sin scrapear HTML.
## Los artefactos [#los-artefactos]
| URL | Qué contiene | Cuándo usarlo |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| [`/llms.txt`](https://connect.emisso.ai/llms.txt) | El índice de todas las páginas, con el título y la descripción de cada una. | Para orientarse: es corto y lista qué más pedir. |
| [`/llms-full.txt`](https://connect.emisso.ai/llms-full.txt) | El corpus completo: todas las páginas concatenadas como texto plano. | Para cargar la documentación entera en un contexto grande, de una sola vez. |
| `/docs/{ruta}.md` | Cada página de `/docs` como markdown limpio: agrégale `.md` a la URL. El markdown de `/docs/empezar/conectar` está en [`/docs/empezar/conectar.md`](https://connect.emisso.ai/docs/empezar/conectar.md), y el de la portada en [`/docs.md`](https://connect.emisso.ai/docs.md). | Para leer una página puntual sin el HTML del sitio, o para mandarle a alguien el link del markdown. |
| `/llms.mdx/{ruta}` | Exactamente la misma respuesta que la fila de arriba, en la ruta propia que existía antes del sufijo. | Si ya tienes esta ruta escrita en algún script. Para todo lo demás alcanza el sufijo `.md`. |
| [`/docs/openapi.json`](https://connect.emisso.ai/docs/openapi.json) | El contrato OpenAPI 3.1 del catálogo documentado completo, SII y bancos incluidos, con los JSON-Schema de entrada y salida de cada tool. | Para generar clientes, validar formas o alimentar un agente que razona sobre contratos. |
| [`/api/v1/openapi.json`](https://connect.emisso.ai/api/v1/openapi.json) | El feed público que sirve el propio runtime. Más angosto: solo los conectores de uso general. | Cuando importa lo que el gateway expone sin autenticar, no el catálogo entero. |
| [`https://connect.emisso.ai/mcp`](https://connect.emisso.ai/mcp) | El servidor MCP. Anuncia dos meta-tools, `search_docs` y `execute`; el catálogo completo vive detrás de ellas. | Para operar, no para leer: es la superficie de ejecución. El detalle está en [el servidor MCP](/docs/agentes/mcp). |
Los dos contratos OpenAPI cubren la ejecución de tools. Los endpoints de control (conexiones, sincronizaciones, webhooks) todavía no entran en esos archivos; su referencia es [la API de control](/docs/api-control).
## Tres formas de pedir la misma página [#tres-formas-de-pedir-la-misma-página]
El markdown de una página se pide de tres maneras, y las tres devuelven el mismo cuerpo porque salen de la misma fuente.
1. **El sufijo `.md`.** Cualquier URL de `/docs` sirve su markdown si le agregas `.md`. Es la convención que ya usan la documentación de Anthropic, la de Stripe y la del AI SDK, así que un agente que la conoce no necesita leer esta página para descubrirla. Además es la única forma que produce una URL propia: se puede pegar en un chat y quien la abra ve el markdown.
2. **La cabecera `Accept: text/markdown`** sobre la URL normal de la página. Sirve al agente que no construye URLs y ya tiene el link HTML. Claude Code, Cursor y OpenCode mandan esa cabecera en todo pedido, así que reciben markdown sin configurar nada.
3. **La ruta `/llms.mdx/{ruta}`**, que es donde vive el generador. Las dos formas de arriba reescriben a esta.
Las respuestas markdown salen con `X-Robots-Tag: noindex`. La página HTML tiene el mismo contenido y es la que queremos en los resultados de búsqueda: es la que está en el sitemap y la que se declara canónica. Sin ese encabezado habría tres copias exactas compitiendo con ella.
## Copiar como Markdown [#copiar-como-markdown]
Cada página de `/docs` trae el botón «Copiar como Markdown». En vez de serializar el HTML, copia el espejo real `/llms.mdx/{ruta}` de esa página, el mismo que consume un agente por HTTP. Una sola fuente para la persona que pega la página en un chat y para la máquina que la fetchea.
El `` de cada página declara además un `alternates` con tipo `text/markdown` apuntando a ese espejo, así que un agente que solo tiene la URL HTML puede descubrir la versión markdown sin conocer la convención de rutas.
## Prompt de arranque [#prompt-de-arranque]
Para entregarle Connect a un agente, acompaña el servidor MCP con este bloque:
```text
Tienes Emisso Connect (https://connect.emisso.ai/mcp): el puente a los sistemas chilenos de esta
organización (SII, bancos, indicadores). Reglas:
1. Parte siempre por search_docs. Sin argumentos trae el catálogo entero agrupado por sistema;
con {tool: ""} trae la ficha completa y un ejemplo ejecutable.
2. En todo sistema conectable, connectionId es obligatorio: sácalo de conexiones.estado.consultar.
La conexión es la empresa; no hay elección implícita ni cuando existe una sola.
3. Leer son dos pasos: .conexion.sincronizar escribe (login real contra el sistema, puede
tardar hasta 90 segundos en bancos) y .consultar lee lo ya guardado. Una lista vacía
puede ser un período sin sincronizar: revisa datosListos en conexiones.estado.consultar antes
de concluir que no hay nada.
4. Si un sistema aparece con conectado: false, crea un enlace con conexiones.enlace.crear y
muéstralo con su dominio completo visible, explicando quién lo pidió y para qué. Nunca lo
presentes como un aviso del banco ni del SII.
5. Ante un error, sigue el suggested_fix que viene en la respuesta; no reintentes lo que no es
reintentable. Si no reconoces el error, reporta el request_id.
```
Las cinco reglas responden a comportamiento real del gateway: la regla 2 evita el error `validation_error` con que el gateway rechaza una llamada sin conexión, la 3 evita concluir «no hay datos» sobre una caché todavía vacía y la 5 aprovecha que cada error del catálogo viaja con un `suggested_fix` accionable.
## Qué hace solo un agente y qué no [#qué-hace-solo-un-agente-y-qué-no]
| Acción | ¿La completa solo? | Por qué |
| ----------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Descubrir el catálogo (`search_docs`) | Sí | Incluye los sistemas todavía no conectados, con el camino para conectarlos. |
| Leer datos sincronizados (`.consultar`) | Sí | Lee del plano ya persistido; no abre sesión contra el sistema externo. |
| Sincronizar (`.conexion.sincronizar`) | Sí | Usa la credencial ya vinculada en el vault; el agente solo dispara el trabajo. |
| Conectar un sistema nuevo (`conexiones.enlace.crear`) | Solo la mitad | El agente acuña el enlace, pero la clave la entrega el humano en esa página. El enlace vence en una hora y sirve una sola vez. |
| Ver una credencial | Nunca | El vault es inaccesible también para el agente: la credencial se resuelve del lado del servidor y jamás entra al contexto del modelo ni a un resultado de tool. |
La última fila es una propiedad del diseño, no una restricción de permisos que alguien pueda relajar: ningún resultado de tool contiene material de credencial, y la bitácora guarda solo un digest del input.
## Próximos pasos [#próximos-pasos]
* [El servidor MCP](/docs/agentes/mcp): las dos meta-tools, el recorrido completo y cómo se conecta un cliente.
* [Conectar un sistema](/docs/empezar/conectar): el flujo del enlace visto del lado humano.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): qué separa el paso que escribe del que lee.
* [Errores](/docs/operar/errores): el catálogo completo, con `suggested_fix` por código.
---
# Sincronizar conexión BancoEstado
> Inicia sesión en BancoEstado Empresas y persiste los alcances pedidos (saldos, movimientos) para un período, en UNA sola sesión (un login, un logout). Es el ÚNICO camino que trae datos del banco: las tools de consulta leen lo ya guardado. `saldos` es una foto del momento, no del período, así que sólo se sincroniza pidiendo el período corriente. Puede tardar cerca de un minuto, y mientras corre el titular no va a poder entrar al portal: BancoEstado admite una sola sesión activa por usuario.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión BancoEstado Empresas
> Prueba las credenciales de la conexión contra BancoEstado Empresas haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar movimientos de BancoEstado
> Lee los movimientos de BancoEstado YA sincronizados de esta conexión, del más reciente al más antiguo. Lectura pura: NO contacta al banco ni dispara una sincronización, así que si falta un período usa 'banco_estado.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud SIN signo, 'type' dice si sale ('credit') o entra ('debit') plata según el libro del banco (al revés de como se lee una cartola), y 'display' es ese monto ya formateado a la chilena con su signo. 'saldo' es el saldo arrastrado: es un balance, no lleva 'type' y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar saldos de BancoEstado
> Lee los saldos de BancoEstado YA sincronizados de esta conexión, del más reciente al más antiguo. Lectura pura: NO contacta al banco ni dispara una sincronización, así que si nunca se sincronizó devuelve una lista vacía. Para traer datos nuevos usa 'banco_estado.conexion.sincronizar' primero. Los saldos son una foto POR DÍA y vienen como NÚMERO conservando su signo: un sobregiro es negativo, y un saldo no lleva 'type' porque no es una operación. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual y nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Sincronizar conexión Banco Security
> Sincroniza los alcances solicitados (transferencias, nóminas, saldos, movimientos) para un período en una sola sesión (un login, un logout). `saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente. `movimientos` cubre cualquier período: el conector elige solo la cartola que corresponde (la del mes en curso o la histórica) y las dos escriben la misma tabla, así que un mismo movimiento traído por las dos NO se duplica.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión Banco Security
> Prueba las credenciales de la conexión contra Banco Security haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar movimientos de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco. Devuelve los movimientos de cuenta corriente guardados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM). Exige el alcance 'movimientos' habilitado. Sin 'periodo' devuelve todos los períodos sincronizados. Si el período nunca se sincronizó, devuelve una lista vacía (lo que NO significa que no haya movimientos): usa 'banco_security.conexion.sincronizar' primero. El banco sirve el mes en curso y los meses ya cerrados por dos cartolas distintas, pero eso es interno: las dos escriben esta misma caché con la misma identidad por movimiento, así que un mes de solape NO aparece duplicado y los resultados se pueden sumar sin miedo. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar los pagos de una nómina de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco. Devuelve las LÍNEAS DE PAGO de UNA nómina: el 'idNomina' es obligatorio y sale de 'banco_security.nominas.consultar'. Exige el alcance 'nominas' habilitado (el mismo que las cabeceras). Las líneas salen en orden ascendente de 'linea'. Si esa nómina nunca se sincronizó, devuelve una lista vacía. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar nóminas de pago de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco. Devuelve SOLO las CABECERAS de las nóminas de pago masivas guardadas de esta conexión, de la más reciente a la más antigua, filtrables por período (AAAA-MM). Exige el alcance 'nominas' habilitado. Cada cabecera trae 'numRegistros' para que dimensiones antes de pedir el detalle: las líneas de pago se piden aparte con 'banco_security.nomina_pagos.consultar' pasándole el 'idNomina' de la cabecera (hay nóminas de más de mil líneas, por eso no vienen aquí). Si el período nunca se sincronizó, devuelve una lista vacía: usa 'banco_security.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar saldos de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco. Devuelve los saldos guardados de esta conexión (contable, disponible y provisorio), con el snapshot más reciente primero. Exige el alcance 'saldos' habilitado. Si nunca se sincronizó, devuelve una lista vacía (eso NO significa que la empresa no tenga cuentas); usa 'banco_security.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los más recientes que hay guardados. Los tres saldos vienen como NÚMERO ya normalizado (antes eran la celda cruda del banco, '$ 12.345.678'), y conservan su signo: un sobregiro es negativo. Un saldo no lleva 'type': no es una operación. 'ultimaLecturaEn' dice cuándo se leyó esa fila del banco: si es vieja, la conexión puede estar pausada. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas. reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar transferencias de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco. Devuelve las transferencias TEF guardadas de esta conexión, de la más reciente a la más antigua, filtrables por período (AAAA-MM) y por dirección. Exige el alcance 'transferencias' habilitado. Omitir 'direccion' trae enviadas y recibidas juntas; el campo 'direction' de cada fila las distingue ('issued' = enviada, 'received' = recibida). Si el período nunca se sincronizó, devuelve una lista vacía: usa 'banco_security.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. 'numeroTransaccion' sí es texto (tiene 14 dígitos y no entra en un entero de 32 bits). 'ultimaLecturaEn' dice cuándo se leyó esa fila del banco. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas. reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar cartolas emitidas de Banco de Chile
> Lee las cartolas (extractos mensuales) ya sincronizadas de esta conexión, la más reciente primero, filtrables por período de búsqueda (AAAA-MM) y por cuenta. Lectura pura: NO contacta al banco ni dispara una sincronización. Para traer datos nuevos, usa 'bch_empresas.conexion.sincronizar' primero. Una cartola es un OBJETO propio, no una vista de 'movimientos': sus saldos de apertura y cierre pueden no cuadrar exactamente con la suma de movimientos del mismo mes porque el extracto encadena por fecha contable y el feed vivo por fecha del movimiento. 'numeroCartola' es TEXTO siempre (convertirlo a número pierde ceros a la izquierda). Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas; reenvía ese valor tal cual, nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Sincronizar conexión Banco de Chile
> Sincroniza los alcances solicitados (saldos, movimientos, cartolas) para un período en una sola sesión de portal (un login, un logout). `saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente. `cartolas` son los extractos MENSUALES ya emitidos por el banco, un objeto propio que NO alimenta `movimientos`.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión Banco de Chile
> Prueba las credenciales de la conexión contra Banco de Chile haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar movimientos de Banco de Chile
> Lee los movimientos ya sincronizados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM) y por cuenta. Lectura pura: NO contacta al banco ni dispara una sincronización. Si el período nunca se sincronizó, devuelve una lista vacía, que NO significa que no haya movimientos. Para traer datos nuevos, usa 'bch_empresas.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola), y 'display' es ese monto ya formateado a la chilena con su signo. 'saldoContable' es un balance: no lleva 'type' y conserva su propio signo. 'id' es la huella estable con la que se guardó el movimiento: el mismo movimiento visto en dos sincronizaciones solapadas trae el mismo 'id'. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas; reenvía ese valor tal cual, nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar saldos de Banco de Chile
> Lee los saldos ya sincronizados de esta conexión, con el snapshot más reciente primero. Lectura pura: NO contacta al banco ni dispara una sincronización. Si nunca se sincronizó, devuelve una lista vacía. Para traer datos nuevos, usa 'bch_empresas.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los saldos más recientes que hay guardados. Los tres saldos vienen como NÚMERO y conservan su signo. Un saldo no lleva 'type' (no es una operación). 'saldoContable' puede venir null en filas sincronizadas antes del 2026-08-11, que es cuando se empezó a leer; desde entonces trae el saldo contable real, que difiere del disponible por retenciones y cheques en canje. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas; reenvía ese valor tal cual, nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Sincronizar conexión BCI
> Sincroniza los alcances solicitados (saldos, movimientos) para un período en una sola sesión (un login, un logout). `saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión BCI
> Prueba las credenciales de la conexión contra BCI haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar movimientos de BCI
> Lee la caché ya sincronizada; NO contacta al banco. Devuelve los movimientos guardados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM) y por cuenta. Exige el alcance 'movimientos' habilitado. Si el período nunca se sincronizó, devuelve una lista vacía, que NO significa que no haya movimientos; usa 'bci_pyme.conexion.sincronizar' primero. 'completo' dice si el último sync de ESE período trajo todo: BCI corta en 1000 movimientos por cuenta y mes, y 'completo: false' significa que faltan filas. Sin filtro de 'periodo' vale null, o sea «no se sabe». Ojo con las correcciones del banco: un movimiento corregido entra como fila NUEVA en vez de reemplazar a la anterior, así que ante dos filas del mismo movimiento vale la de 'ultimaLecturaEn' mayor. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. 'saldoContable' es un balance: no lleva 'type' y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar saldos de BCI
> Lee la caché ya sincronizada; NO contacta al banco. Devuelve los saldos guardados de esta conexión (contable, disponible, 9AM y retención), con el snapshot más reciente primero. Exige el alcance 'saldos' habilitado. Si nunca se sincronizó, devuelve una lista vacía: eso NO significa que la empresa no tenga cuentas. Para traer datos nuevos usa 'bci_pyme.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los más recientes que hay guardados. Los cuatro saldos vienen como NÚMERO ya normalizado y conservan su signo: un sobregiro es negativo. Un saldo no lleva 'type' (no es una operación). 'ultimaLecturaEn' dice cuándo se leyó esa fila del banco: si es vieja, la conexión puede estar pausada. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Sincronizar conexión BICE
> Sincroniza los alcances solicitados (saldos, movimientos) para un período en una sola sesión de portal. `saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión BICE
> Prueba las credenciales de la conexión contra BICE haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar movimientos de BICE Empresas
> Lee los movimientos ya sincronizados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM), por cuenta y por 'type' (el eje credit/debit del libro del banco: 'debit' para los abonos, 'credit' para los cargos). Lectura pura: NO contacta al banco ni dispara una sincronización. Si el período nunca se sincronizó, devuelve una lista vacía, que NO significa que no haya movimientos. Para traer datos nuevos, usa 'bice_empresas.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola), y 'display' es ese monto ya formateado a la chilena con su signo. 'saldoContable' es un balance: no lleva 'type' y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas. reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar saldos de BICE Empresas
> Lee los saldos ya sincronizados de esta conexión, con el snapshot más reciente primero. Lectura pura: NO contacta al banco ni dispara una sincronización. Si nunca se sincronizó, devuelve una lista vacía. Para traer datos nuevos, usa 'bice_empresas.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los saldos más recientes que hay guardados. Los dos saldos vienen como NÚMERO y conservan su signo: un sobregiro es negativo. Un saldo no lleva 'type' (no es una operación). Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Crear un enlace para conectar un sistema
> Crea un enlace de un solo uso donde la persona entrega sus credenciales del sistema para conectarlo. Devuelve el enlace SIEMPRE con su dominio completo visible y explicando quién lo pidió y para qué; nunca lo presentes como un aviso del banco ni del SII. La credencial se cifra en el vault de esta misma organización y no la ve nadie más, tampoco tú. El enlace vence y sirve una sola vez. Después de que la persona lo complete, usa 'conexiones.estado.consultar' para saber si ya hay datos.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar el estado de las conexiones
> Dice en qué va cada conexión: si la credencial quedó vinculada, qué sincronizaciones corrieron y si YA HAY DATOS para consultar ('datosListos'). Úsala después de que la persona complete un enlace, y antes de intentar leer: un listado vacío no significa que no haya nada, puede ser que todavía no sincronizó. La primera sincronización de un sistema con navegador puede tardar cerca de un minuto. El campo 'herramientas' trae los ids que ya puedes invocar; si tu cliente MCP todavía no los muestra en su lista, invócalos con la herramienta 'execute' pasando el id en 'tool'.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Listar los sistemas que se pueden conectar
> Devuelve el catálogo de sistemas chilenos que esta organización puede conectar (bancos, SII) con el estado de cada uno: si ya está conectado, sus conexiones, los módulos de datos que ofrece y las herramientas que quedan disponibles al conectarlo. Úsala SIEMPRE antes de crear un enlace, para obtener el código exacto del sistema, no lo adivines. Si un sistema aparece con 'conectado' en false, el camino es 'conexiones.enlace.crear'.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Hora del servidor
> Devuelve la marca de tiempo actual del servidor (ISO-8601 UTC, epoch Unix) y la zona horaria solicitada. Prueba el camino E2E; no requiere credenciales.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Reflejar mensaje
> Devuelve el texto recibido junto con su longitud. Conector de ejemplo que prueba que el patrón del registry generaliza; no requiere credenciales.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# API REST
> Una página interactiva por operación, generada del contrato OpenAPI del catálogo documentado.
Cada tool del catálogo se ejecuta con el mismo verbo: `POST /api/v1/tools/{id}/execute`. Esta sección
tiene una página interactiva por operación, con su esquema, sus ejemplos y un cliente de prueba.
El contrato completo en formato OpenAPI 3.1 está en [`/docs/openapi.json`](/docs/openapi.json). El
detalle conceptual de cada tool (alcances, errores, ejemplos comentados) vive en la Referencia de la
barra lateral.
---
# Serie histórica del indicador
> Devuelve la serie de valores de un indicador entre dos fechas (AAAA-MM-DD), en orden ascendente, acotada por 'limite' (tope duro 1000), leída del almacén de referencia global.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Valor actual del indicador
> Devuelve el ÚLTIMO valor disponible de un indicador económico chileno (UF, DÓLAR, EURO, IPC, UTM), leído del almacén de referencia global; no consulta fuentes externas en tiempo real. 'último disponible' NO es lo mismo que 'el de hoy', y hay que mirar 'fecha' antes de usar el número: la UF y la UTM se publican por ADELANTADO, así que su fecha puede ser futura; y si la ingesta se atrasa, la fecha queda en el pasado. 'antiguedadDias' resuelve las dos de una vez: 0 = es el de hoy, positivo = días de atraso, negativo = está fechado en el futuro.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Valor del indicador a una fecha
> Devuelve el valor de un indicador vigente a una fecha dada (AAAA-MM-DD) CON ARRASTRE: si esa fecha no tiene dato propio (un fin de semana, un feriado, o una fuente que no se ha actualizado) devuelve el último valor anterior. La 'fecha' de la respuesta puede ser DISTINTA de la solicitada, y 'esArrastre' lo marca: false = ese día tiene dato propio; true = el valor corresponde a la 'fecha' devuelta, que es anterior. Pedir una fecha futura devuelve el último valor conocido con 'esArrastre: true', no un error. Todo se lee del almacén de referencia global.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión Notta
> Prueba las credenciales de la conexión contra Notta haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar un DTE
> Consulta el estado actual de un DTE en Notta por su id. Es el seguimiento del flujo asíncrono que abre notta.dte.emitir: el estado avanza de 'queued' a 'EPR' (aceptado por el SII) o a un rechazo terminal (RFR/RCT/RSC), y en ese caso sii_glosa trae el motivo que dio el SII. Devuelve también folio, montos calculados y ambiente SII.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Descargar el XML o el PDF de un DTE
> Devuelve el XML firmado o el PDF de un DTE como base64. Para consumo programático (REST/SDK). En conversación prefiere notta.dte.reenviar: el base64 de un PDF es inmanejable en chat. Un documento recién emitido todavía no está firmado: hasta que lo esté, la descarga falla de forma reintentable.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Emitir DTE (factura o nota)
> Emite un DTE ante el SII vía Notta: factura afecta (33), exenta (34), nota de débito (56) o nota de crédito (61, que exige references[] al documento original, y este solo puede ser una factura 33 o 34). Cada item declara si es exento y su monto_item (cantidad × precio_unitario, ya con el descuento de la línea aplicado); los TOTALES del documento (neto, exento, IVA, total) los calcula Notta. Las NOTAS (56/61) exigen además rut_emisor (el RUT de la empresa de esta conexión, que Notta no deriva en esa ruta) y la nota de débito (56) exige nd_reason; a cambio, no llevan forma_pago ni descuento_global. La emisión es ASÍNCRONA: esta llamada devuelve el documento con folio asignado y estado 'queued'; haz el seguimiento con notta.dte.consultar hasta EPR (aceptado) o un rechazo. Si un intento anterior falló por transporte o timeout, revisa los documentos RECIENTES con notta.dte.listar antes de reintentar, para no duplicar. Con correo_receptor, Notta envía el PDF+XML al receptor cuando el SII acepta.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Listar los DTEs recientes
> Lista los DTEs MÁS RECIENTES emitidos por esta empresa en Notta (del más nuevo al más antiguo). El API de Notta NO ofrece filtros por tipo, fecha, estado ni receptor: solo un límite de cuántos traer, así que si buscas uno concreto pide más documentos y descarta tú los que no son. Úsalo como red antes de reintentar una emisión que falló por transporte o timeout: si el documento ya aparece entre los recientes, no lo vuelvas a emitir. Devuelve menos campos que notta.dte.consultar (sin neto, IVA ni ambiente): para el detalle completo consulta por id.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Reenviar un DTE por correo
> Reenvía el PDF y el XML de un DTE ya emitido al correo del receptor. Sin correo_receptor usa el que ya tiene guardado el documento; con él, lo envía a esa dirección y la recuerda. Es el camino conversacional para entregar un documento: no descarga nada, lo envía. No emite ni modifica el DTE.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar certificados de cotizaciones de Previred
> Lee los certificados oficiales de cotizaciones ya emitidos para esta conexión, uno por trabajador. Es el documento que Previred firma y que una persona pide para probar lo que se le cotizó: 'certificadoUrl' es un enlace firmado para descargarlo. Hay un certificado VIGENTE por trabajador, que cada sincronización reemplaza, y cubre la ventana máxima que Previred admite terminando en el período sincronizado; 'periodoDesde' y 'periodoHasta' dicen cuál es. Si lo que buscas son los montos y no el documento, 'previred.cotizaciones.consultar' los tiene sin descargar nada. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Sincronizar conexión Previred
> Sincroniza los alcances solicitados (planillas, cotizaciones, deuda, f301) para un período en una sola sesión de portal. Es el ÚNICO camino que trae datos de Previred: las tools '.consultar' leen lo que esto haya guardado. 'deuda' es el estado del momento y no del período, así que solo se sincroniza cuando se pide el período corriente. 'f301' trae el archivo de 106 campos con que la Dirección del Trabajo emite el Certificado F30-1 de ese período. 'certificados' emite el certificado oficial de cotizaciones de CADA trabajador y por eso es el alcance más caro: cuesta una petición al portal por persona. 'empresas' lista las empresas que la credencial administra y no cuesta ninguna petición: ese listado ya llega al iniciar sesión.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión Previred
> Prueba las credenciales de la conexión contra Previred haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar cotizaciones por trabajador de Previred
> Lee las cotizaciones ya sincronizadas de esta conexión, por trabajador, período e institución. Es la materia prima del «certificado de cotizaciones» que emite Previred: el certificado en sí es un PDF que se genera para el rango que se pida, así que aquí viven los HECHOS (quién cotizó cuánto, a qué institución, en qué mes) y no el documento. Un mismo trabajador y mes trae varias filas, una por institución. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar deuda previsional en Previred
> Lee la deuda previsional ya sincronizada de esta conexión, y responde la pregunta del mes: ¿está al día? Trae las dos mitades. 'dnp' son declaraciones sin pago, con su institución y sus cargos legales. 'por_pagar' son las nóminas cuyo plazo CORRE y aún no se pagan: ahí 'institucion' es "Todas" y solo viene 'montoTotal', porque el portal da un total por nómina sin desglosarlo. Recuerda el calendario: el plazo vence el día 13 del mes siguiente al de las remuneraciones. Ojo con 'montoTotal': Previred lo recalcula según la fecha en que efectivamente se pague, así que el valor guardado es el del momento de la sincronización (por eso cada fila trae 'observadoEn') y NO una cifra a la que uno pueda comprometerse. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar empresas de la credencial de Previred
> Lista las empresas que la credencial de esta conexión administra en Previred. Sirve para saber qué OTRAS empresas se podrían conectar con la misma clave, que es la pregunta típica de un contador con varias empresas a cargo. Ojo: cada conexión de Connect es UNA empresa, así que ver una empresa aquí no significa poder leer sus datos; para eso hay que crear su propia conexión. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar archivos para el F30-1 de Previred
> Lee los archivos ya sincronizados con que la Dirección del Trabajo emite el Certificado F30-1 de Cumplimiento de Obligaciones Laborales y Previsionales, el que una empresa contratista tiene que entregarle a su mandante para que le paguen. Cada fila es el archivo de 106 campos de un período y una nómina, y 'archivoUrl' es un enlace firmado para descargarlo y subirlo al sitio de la Dirección del Trabajo. Connect NO emite el certificado: entrega el archivo con que se pide. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar planillas pagadas de Previred
> Lee las planillas de cotizaciones ya sincronizadas de esta conexión, de la más reciente a la más antigua, filtrables por período (AAAA-MM) y por institución. Un pago de un período se abre en VARIAS planillas, una por cada institución previsional (AFP, Fonasa o Isapre, AFC, mutual, CCAF): por eso un mismo período trae varias filas y eso es lo normal, no una duplicación. Cada fila trae su 'folio', que es el identificador con que Previred la direcciona, y 'comprobanteUrl' cuando el PDF ya está descargado. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar boletas electrónicas del SII
> Lee el resumen diario de boletas electrónicas ya sincronizado para esta conexión, filtrado por período. Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, use 'sii.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar boletas de honorarios del SII
> Lee las boletas de honorarios electrónicas (BHE) ya sincronizadas para esta conexión, filtradas por período y/o perspectiva (emitidas = las que emitió esta empresa; recibidas = las que le emitieron, donde esta empresa es el agente retenedor). Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, usa 'sii.conexion.sincronizar' primero. El filtro tributario canónico es 'estado' distinto de 'S' sobre el código crudo: 'V' (anulación pendiente), 'R' y 'U' (observadas) siguen VIGENTES; solo 'S' está anulada: nunca filtres por 'estadoNormalizado' igual a 'vigente'. El 'estado' es el observado en la última sincronización del período, no el estado final: una BHE puede anularse, o revertir de anulación pendiente a vigente, hasta el 1 de marzo del año siguiente, y por petición administrativa sin plazo después. Resincroniza el período para refrescarlo; 'ultimaLecturaEn' dice cuándo se observó cada fila. La suma de 'retencion_receptor' es el insumo para cuadrar el F29 código 151, no el código 151: ese además incluye las retenciones por BTE y se imputa al mes del pago. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Sincronizar conexión SII
> Sincroniza los alcances solicitados (rcv, boletas, guias, boletas_honorarios, documentos) para un período en una sola sesión (un login, un logout).
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Verificar conexión SII
> Prueba las credenciales de la conexión contra el SII haciendo un login real (y su logout, a cargo del pipeline). No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar documentos respaldados del SII
> Lista los documentos tributarios (DTE) cuyo XML firmado ya se respaldó para esta conexión, filtrables por período, perspectiva y tipo de documento. Devuelve SOLO las columnas de cabecera: ni el XML ni el detalle de ítems viaja aquí. Para el detalle de UN documento (sus ítems con cantidad, unidad y precio, los giros y direcciones de emisor y receptor, y la forma de pago) usa 'sii.documentos.detallar' con el 'tipoDte', el 'folio' y el 'rutEmisor' de la fila correspondiente. Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'; para traer datos nuevos usa 'sii.conexion.sincronizar' primero. Este respaldo es lo que el RCV no tiene y no puede tener: el RCV dice qué documentos EXISTEN, este respaldo trae el documento. Sólo lo sirven las conexiones cuya credencial es la clave tributaria de una persona que representa a la empresa. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. Reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Detallar un documento respaldado del SII
> Devuelve UN documento tributario respaldado, con su detalle completo: los ítems (nombre, cantidad, unidad, precio unitario y monto), los giros, direcciones y comunas de emisor y receptor, y la forma de pago. El documento se identifica con las tres partes que lo hacen único ('tipoDte', 'folio' y 'rutEmisor'), y las tres salen de una fila de 'sii.documentos.consultar'. Lectura pura: NO contacta al SII, lee el XML que ya se respaldó y lo parsea en el momento. Si ese documento no está sincronizado, devuelve 'documento: null'. No es un error: es que no lo tenemos, así que sincroniza su período con 'sii.conexion.sincronizar' y vuelve a preguntar. El XML firmado sólo viaja si se pide 'incluirXml: true'; sin eso la respuesta trae el detalle ya estructurado, que es lo que casi siempre se necesita. Un ítem con 'cantidad', 'unidad' o 'precioUnitario' en null es un ítem que no los declaró (un flete, un descuento): no debe leerse como cero.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar guías de despacho del SII
> Lee las guías de despacho electrónicas (DTE 52) ya sincronizadas para esta conexión, filtradas por período y/o perspectiva (emitidas = las que emitió esta empresa; recibidas = las que le emitieron). Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, use 'sii.conexion.sincronizar' primero. Ojo: el SII solo conserva el detalle de guías de los últimos 6 meses, así que un período más viejo no se puede sincronizar aunque exista. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# Consultar RCV del SII
> Lee el Registro de Compra-Venta ya sincronizado para esta conexión, filtrable por período, perspectiva, tipo de documento (tipoDte) y estado del registro. Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, use 'sii.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
Referencia interactiva generada desde el contrato OpenAPI — ver el contrato completo, máquina-legible, en [/docs/openapi.json](/docs/openapi.json).
---
# La bitácora
> Cada llamada deja exactamente una fila inmutable: auditoría, correlación con soporte y fuente de verdad de la facturación.
Toda ejecución de una tool, entre por REST, por MCP, desde el dashboard o disparada por el scheduler, escribe exactamente **una** fila en la bitácora. La tabla es append-only por doble candado (ningún rol de aplicación tiene UPDATE ni DELETE, y no existe política que los permita), así que una fila escrita no se edita nunca. Sobre ese registro se apoyan tres cosas a la vez: la auditoría, el soporte y la facturación. No hay un contador de cobro separado que pueda divergir de lo que de verdad pasó.
## La anatomía de una fila [#la-anatomía-de-una-fila]
Las columnas que vas a usar, de la tabla real `bitacora_execution`:
| Columna | Qué guarda | Por qué importa |
| ------------------------------------------------ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `request_id` | El `req_…` de esta llamada, generado por el servidor | La llave de correlación: viaja en el `meta` de cada respuesta y dentro de cada error |
| `tool_id` / `connector_code` | Qué se ejecutó (`sii.rcv.consultar`) y de qué sistema | El grano de todo análisis de uso |
| `connection_id` | La conexión (`conn_…`); null en conectores sin conexión | Atribuye la llamada a una empresa concreta |
| `plane` | `action` o `read` | Distingue una consulta barata de una sincronización real ([Los dos planos](/docs/conceptos/planos)) |
| `surface` | `rest`, `mcp_meta`, `mcp_native`, `dashboard` o `hosted` | Por dónde entró la llamada |
| `actor_id` / `actor_type` | Quién actuó: `user`, `agent`, `system` o `connect_session` | La atribución (abajo) |
| `credential_id` | La credencial que autenticó (el id de la API key) | Separada del actor a propósito |
| `input_digest` | `sha256` del input canonicalizado y redactado | Compara inputs sin poder reconstruirlos |
| `result_status` / `error_code` | `ok` o `error`, y con qué código | Las fallas también dejan fila |
| `latency_ms` | Duración de la ejecución | El mismo valor que viaja en `meta.latency_ms` |
| `parent_request_id` | En MCP, la fila del delegado apunta a la del `execute` que lo invocó | Reconstruye la cadena meta-tool → tool del catálogo |
| `meter_category` / `billable_units` / `billable` | La clasificación de cobro | La facturación (abajo) |
| `created_at` | Cuándo | Junto a un `id` monotónico para paginar por cursor |
## Atribución: humano o agente, nunca confundidos [#atribución-humano-o-agente-nunca-confundidos]
Una API key autentica como **agente**: `actor_type: "agent"`, con el id de la clave como `actor_id`. Un token OAuth o una sesión del dashboard autentican como **usuario**. En ningún caso la acción de un agente se atribuye al humano dueño de la credencial, porque la fila diría una mentira y la bitácora existe para lo contrario. Por eso `credential_id` es una columna aparte: revocar una clave no reescribe la autoría de nada.
## El input no se guarda: se guarda su huella [#el-input-no-se-guarda-se-guarda-su-huella]
`input_digest` es `sha256(canonicalize(redact(input)))`. Antes de calcular el hash, todo campo con forma de secreto (token, password, credential, api key) se reemplaza por un marcador; después, el objeto se serializa con las llaves ordenadas, para que el mismo input produzca siempre el mismo digest. El resultado permite responder «¿estas dos llamadas llevaron el mismo input?» sin poder reconstruir el input. La credencial de una conexión ni siquiera llega aquí: se resuelve server-side y jamás aparece en un resultado, en un log ni en la bitácora.
## De un `request_id` a su fila [#de-un-request_id-a-su-fila]
Toda respuesta lleva `meta.request_id`, y todo error lo repite dentro del envelope junto al `suggested_fix`. Con ese `req_…` encuentras la fila en el dashboard, y es el dato que soporte te va a pedir. El `meta.audit_status` de la respuesta cierra el círculo: `recorded` significa que la fila quedó escrita en línea; `degraded`, que el registro cayó a un respaldo durable. Una acción que ya se ejecutó con éxito nunca se convierte en un `5xx` porque falló su auditoría; ese `5xx` invitaría a reintentar una acción regulada ya hecha.
## Qué se factura [#qué-se-factura]
La bitácora es la fuente de verdad de la facturación, y la columna `billable` la calcula la base de datos, no el código de aplicación: exige `result_status = 'ok'`, una categoría facturable y `billable_units > 0`. Las consecuencias prácticas:
* **Un error nunca factura.** `billable_units` queda en 0 en toda fila fallida.
* **`meter_category` clasifica cada llamada.** `action_call`, `read_sync` y `read_query` cuentan contra el plan; `meta` (las meta-tools de MCP), `internal` (conectores abiertos como `core` e `indicadores`) y `unclassified` son overhead y no cuentan.
* **Los conectores abiertos dejan fila igual**, con `billable_units: 0`: auditar y cobrar son ejes distintos.
* **Lo que haces en tu propio dashboard no gasta tu cuota.** Una llamada con `surface: "dashboard"` o `"hosted"` se audita idéntica a cualquier otra y factura 0: guardar tu credencial o paginar tus propios datos no puede consumir la cuota de tu integración.
## Lo que no está en la bitácora [#lo-que-no-está-en-la-bitácora]
Dos registros vecinos completan el cuadro. Las **fallas de autenticación** no tienen organización que las reciba (el token no resolvió a ningún tenant), así que van a `security_events`, que guarda el prefijo de la credencial y nunca el token completo. Y los **cambios de configuración** (crear una clave, deshabilitar una conexión, rotar una credencial) van a `audit_control`, el registro del plano de control, con su `before` y su `after`.
## Dónde verla [#dónde-verla]
En [connect.emisso.ai/bitacora](https://connect.emisso.ai/bitacora): las columnas Actor, Herramienta, Scope, Plano, Resultado, Latencia, request\_id y Fecha, con filtro por API key. Cada conexión tiene además su propia pestaña de bitácora, con las ejecuciones y los cambios de configuración que la tocaron.
## Próximos pasos [#próximos-pasos]
* [Autenticación](/docs/empezar/autenticacion): quién puede escribir estas filas y cómo.
* [Conexiones](/docs/conceptos/conexiones): la unidad a la que se atribuye cada llamada.
* [Errores](/docs/operar/errores): el catálogo completo, con `suggested_fix` por código.
---
# Conexiones
> La conexión es la empresa: la unidad con la que Connect autoriza, sincroniza y factura cada sistema externo.
Una **conexión** (id `conn_…`) une tu organización con un sistema concreto usando la credencial de **una** empresa: el SII de Comercial Aurora SpA, la cuenta bancaria de esa misma empresa. Es la unidad de autorización (sus tools autorizan contra ella), de sincronización (los datos persistidos cuelgan de ella) y de facturación. Una organización puede tener varias conexiones del mismo sistema: dos conexiones del SII son dos RUT distintos, cada una con su credencial, sus datos y su historial.
Los conectores de referencia (`core`, `indicadores`) y el de plataforma (`conexiones`) no tienen conexión: sus datos son globales o son el plano de control de Connect sobre sí mismo, y por eso sus tools no piden `connectionId`. Todo lo demás (SII, bancos) existe únicamente a través de una conexión.
## El ciclo de vida [#el-ciclo-de-vida]
`conexiones.estado.consultar` (y la pantalla [Conexiones](https://connect.emisso.ai/connections) del dashboard) muestra tres ejes por conexión: el estado de la conexión, el de su credencial y la cadencia de sincronización.
**Estado de la conexión:**
| Estado | Qué significa | Cómo se sale |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `active` | Operativa: sus tools autorizan y la sincronización programada corre. Una conexión creada desde el dashboard o el flujo hosted nace en este estado. | Deshabilitándola o eliminándola. |
| `disabled` | Apagada a mano. Toda tool de la conexión responde `403 connection_disabled`, y la conexión deja de devengar facturación desde ese momento. Los datos ya sincronizados no se tocan. | Reactivándola en el dashboard. Ambos cambios quedan auditados (`connection.enabled` / `connection.disabled`). |
| `pending` | Creada pero todavía no operativa. En la práctica es raro verla: los flujos de alta dejan la conexión activa de inmediato. | Completando el alta (entregar la credencial) o eliminándola. |
**Estado de la credencial:**
| Credencial | Qué significa | Cómo se sale |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `linked` | Vinculada y utilizable. `verificadaEn` registra la última verificación exitosa contra el sistema. | Es el estado normal. |
| `invalid` | El sistema rechazó el último login: la clave cambió o venció. A la tercera falla consecutiva, la sincronización programada se pausa sola. | Reconectando (abajo). Un login posterior que funciona también la devuelve a `linked`. |
| `revoked` | Anulada de forma explícita: el vault se niega a entregarla aunque siga cifrada en la base. | Entregando la credencial de nuevo. |
| `ausente` | La conexión no tiene credencial guardada. | Entregándola: por enlace de conexión o en los ajustes del dashboard. |
**Cadencia:** `off`, `daily`, `12h` o `6h`. Se configura en los ajustes de la conexión, y apagarla (`off`) está permitido siempre. Cuando el auto-pause de credencial la apaga, la cadencia anterior queda recordada: arreglar la clave la restaura sola, sin reconfigurar nada.
## `connectionId` es obligatorio, incluso con una sola conexión [#connectionid-es-obligatorio-incluso-con-una-sola-conexión]
Toda tool de un conector con conexión exige decir cuál: header `X-Connect-Connection` en REST, campo `connectionId` de `execute` en MCP, `opts.connectionId` en el SDK. No hay resolución implícita ni cuando existe exactamente una, y es deliberado: la conexión ES la empresa, y una elección implícita que hoy acierta porque hay una sola, mañana acierta distinto sin que nadie haya cambiado nada. Con dos conexiones de un banco, «la conexión» es ambigua entre dos RUT; con una, es una afirmación silenciosa que nadie escribió.
Omitirlo no cuesta una adivinanza, porque el error lo explica:
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.rcv.consultar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{"input": {"periodo": "2026-07"}}'
```
Respuesta (`400`):
```json
{
"error": {
"code": "validation_error",
"message": "'sii.rcv.consultar' requiere una conexión concreta.",
"suggested_fix": "Pasa el connectionId de la conexión de 'sii' (MCP: campo 'connectionId' de execute; REST: header 'X-Connect-Connection'). Lístalos con la tool 'conexiones.estado.consultar'.",
"request_id": "req_..."
},
"meta": {
"request_id": "req_...",
"tool_id": "sii.rcv.consultar",
"plane": "action",
"latency_ms": 2,
"audit_status": "recorded"
}
}
```
El id sale de `conexiones.estado.consultar` (o de `GET /v1/connections`). Para la historia que cuentan estas páginas, `conn_9tKfR2mQx4Vb` es la conexión SII de Comercial Aurora SpA (RUT 77.123.456-9).
## Reconectar [#reconectar]
Cuando la clave del sistema cambia o vence, la credencial queda `invalid` y el camino es **rotarla sobre la misma conexión**, nunca crear una segunda. Tres vías:
* **Por agente:** `conexiones.enlace.crear` con `{"sistema": "sii", "modo": "reconectar", "conexionId": "conn_9tKfR2mQx4Vb"}` devuelve un enlace de un solo uso para que la persona entregue la clave nueva. El enlace de reconexión vence en 1 hora: se emite con la persona presente.
* **Por API:** `POST /v1/connect_sessions` con `mode: "reauth"` y `connection_id` ([flujo hosted](/docs/operar/enlace-hosted)).
* **En el dashboard:** los ajustes de la conexión piden la credencial completa de nuevo. No hay prefill: el secreto se guarda como un solo blob cifrado y el dashboard no tiene camino de descifrado.
La rotación reemplaza el cifrado sobre la misma conexión y dispara la verificación contra el sistema (`conexion.verificar`); si el auto-pause había apagado la cadencia, arreglar la clave la restaura. El historial, los datos sincronizados y el `conn_…` no cambian.
## Deshabilitar y eliminar [#deshabilitar-y-eliminar]
**Deshabilitar** es el interruptor reversible: la conexión queda en `disabled`, sus tools responden `403 connection_disabled`, deja de devengar facturación y nada se borra. Sirve para pausar un sistema sin perder nada de lo sincronizado.
**Eliminar** borra de verdad. Se destruyen la credencial cifrada, los datos sincronizados de esa conexión, sus programaciones y trabajos de sincronización y sus webhooks. Sobreviven dos cosas, y con razón: la [bitácora](/docs/conceptos/bitacora) y el registro de cambios de configuración son append-only y son la fuente de verdad de la facturación, así que sus filas quedan con el `connection_id` apuntando a una conexión que ya no existe.
Recrear la conexión, incluso con el mismo nombre y la misma credencial, produce un `conn_` nuevo al que no se le puede re-adosar nada de lo anterior: el cifrado de los datos ata cada fila a la conexión original. Por eso la confirmación exige escribir el nombre exacto de la conexión, y solo un owner o admin puede hacerlo. Si hay una sincronización en vuelo, la operación responde `connection_busy` (reintentable) en vez de borrar debajo de un trabajo corriendo.
## Próximos pasos [#próximos-pasos]
* [Conectar un sistema](/docs/empezar/conectar): crear tu primera conexión de punta a punta.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): qué hacer con la conexión ya activa.
* [El flujo hosted](/docs/operar/enlace-hosted): pedirle la credencial a quien de verdad la tiene.
---
# Los dos planos
> Acción efímera y lectura persistente: qué declara cada tool y por qué leer un banco son dos pasos.
Toda tool del registry declara un **`plane`**: `action` o `read`. El plano es un eje de **persistencia de negocio**, no de comportamiento. No lo confundas con los behavior hints (`readOnly` / `destructive` / `idempotent` / `openWorld`), que son otro eje, declarado por separado en cada tool. `core.timestamp.now` es `plane: "action"` y a la vez `readOnly: true`: una tool de acción puede ser de solo lectura, porque lo que la hace «acción» es no persistir nada de negocio; si modifica o no datos es asunto de los hints.
## El plano `action` [#el-plano-action]
**Efímero.** Nada de negocio se persiste. Dentro del handler, `ctx.persist` es literalmente `null`, y escribirle es un error de tipo, no una regla de lint: el acceso a persistencia no existe, por construcción. Lo único que sobrevive a la llamada es la fila de la [bitácora](/docs/conceptos/bitacora), con su digest de auditoría.
Ejemplos de hoy: `core.timestamp.now`, `echo.message.reflect` y las tools de `indicadores` son todas `plane: "action"`. Consultan o calculan algo y responden; no queda una copia normalizada en la base de datos de Connect.
## El plano `read` [#el-plano-read]
**Persistente**, y reservado a **conectores de pago** (`plan: "paid"`). La regla se aplica al construir el registry: `plane: "read"` exige `plan: "paid"`, y declarar una tool de lectura persistente en un conector gratuito hace fallar el build.
Una tool de plano `read` sincroniza datos de un sistema externo (el SII, un banco) hacia un modelo normalizado propio, con historial, pensado para que un agente consulte sin golpear la fuente en cada llamada. Ninguno de los conectores gratuitos (`core`, `echo`, `indicadores`) usa el plano `read`: es el patrón de los conectores con [conexión](/docs/conceptos/conexiones).
## Leer son dos pasos, y solo el primero es `read` [#leer-son-dos-pasos-y-solo-el-primero-es-read]
Es la parte contraintuitiva, y conviene tenerla clara antes de la primera llamada contra un banco o el SII. Cada conector con plano `read` expone exactamente **dos clases** de tool de datos:
| | Qué hace | `plane` |
| -------------------------- | ------------------------------------------------------------------------------------------------- | -------- |
| **`conexion.sincronizar`** | Lo único que contacta al sistema externo y trae datos. Hace login, lee, normaliza y **persiste**. | `read` |
| **`.consultar`** | Lee de lo ya persistido. Nunca abre sesión, nunca toca el banco ni el SII. | `action` |
Sí: **la tool que escribe declara `read` y la que lee declara `action`**, al revés de la intuición. Se entiende al volver al eje: sincronizar alimenta el modelo persistido, por eso declara `read`; consultar la caché no persiste nada, por eso declara `action`.
En todos los conectores; nunca «pregunta en vivo». Si `sii.rcv.consultar` devuelve una lista vacía, lo que falta es la sincronización de ese período, no los documentos: corre `sii.conexion.sincronizar` primero y vuelve a consultar. Y al revés: consultar es barato y no gasta un login, así que puedes paginar sin miedo.
No existe una tercera clase. Una tool que consulte en vivo y no persista está prohibida por más natural que parezca: se llevaría un login completo (cerca de 90 segundos en los bancos; en BCI además quema la única sesión activa que el banco permite por usuario) sin dejar nada a cambio, y quien llama no tendría forma de distinguirla de una lectura barata.
Cuál es cuál lo dice el nombre, pero también el catálogo: `search_docs({ tool })` en MCP, o `GET /v1/tools/{id}` en REST, devuelven el `plane` de cada tool y, en las de consulta, qué **alcance** leen.
`plane` responde «¿persiste esto en Connect?». Los behavior hints responden «¿es segura de reintentar o de auto-ejecutar? ¿muta algo afuera?». Un cliente MCP usa los hints para decidir si puede llamar la tool sin confirmación humana; el pipeline usa `plane` para decidir si abre un buffer de persistencia. Nunca derives uno del otro.
## El modelo, en una llamada [#el-modelo-en-una-llamada]
Con la conexión SII de Comercial Aurora SpA ya sincronizada, una consulta responde al instante desde lo persistido:
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.rcv.consultar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input": {"periodo": "2026-07", "perspectiva": "ventas"}}'
```
Respuesta (`200`, recortada a un documento; la real trae más campos por documento y hasta `limit` filas por página):
```json
{
"data": {
"documentos": [
{
"tipoDte": 33,
"folio": 4712,
"rutEmisor": "77123456-9",
"rutReceptor": "76543210-3",
"razonSocial": "Constructora Los Robles Ltda",
"fechaEmision": "14/07/2026",
"montoNeto": 1250000,
"montoIva": 237500,
"montoTotal": 1487500,
"estado": "registro",
"periodo": "2026-07",
"perspectiva": "ventas"
}
],
"cursor": null,
"sincronizacion": {
"sincronizadoEn": "2026-08-06T03:15:42.000Z",
"completo": true
}
},
"meta": {
"request_id": "req_...",
"tool_id": "sii.rcv.consultar",
"plane": "action",
"latency_ms": 41,
"audit_status": "recorded"
}
}
```
La llamada no tocó al SII: `sincronizacion.sincronizadoEn` dice de cuándo son los datos, y la respuesta tardó milisegundos porque solo leyó el plano ya persistido. El recorrido completo (conectar, sincronizar, consultar y los estados intermedios) está en [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
---
# Sincronizar y consultar
> En Connect ninguna consulta pregunta en vivo al SII ni a un banco. Leer datos reales son dos pasos, y esta página explica por qué.
Una tool `sincronizar` abre la sesión contra el sistema externo, trae los datos y los persiste. Las
tools `consultar` leen lo persistido, al instante. Esta separación gobierna toda lectura de datos en
Connect y tiene consecuencias prácticas para tu integración.
```text
sincronizar (escribe) consultar (lee)
un login real, un logout ──► responde al instante
trae y guarda los alcances desde lo ya guardado
tarda lo que tarde el sistema barata, paginada, ilimitada
```
## Por qué no hay consultas en vivo [#por-qué-no-hay-consultas-en-vivo]
* **Los logins son caros y escasos.** Un login bancario tarda cerca de 90 segundos, y en BCI entrar
corta la sesión de quien esté dentro del portal. Una consulta en vivo por cada pregunta de un agente
dejaría al contador de tu cliente fuera del banco.
* **Los rate limits son del sistema externo, no tuyos.** Concentrar el contacto en `sincronizar` hace
controlable cuánto y cuándo se toca el SII o el banco.
* **Tus lecturas se vuelven predecibles.** Un `consultar` responde en milisegundos siempre, sin
depender de que el banco esté arriba a las 3 de la mañana.
Un id `recurso.consultar` significa «lee lo ya sincronizado» en todos los sistemas, sin excepción. No
existe, a propósito, una tool que consulte en vivo sin persistir. Y `conexion.verificar`, que sí abre
sesión, no devuelve ningún dato de negocio: solo confirma que la credencial sigue viva.
## Es normal que la primera consulta vuelva vacía [#es-normal-que-la-primera-consulta-vuelva-vacía]
Una lista vacía no significa que no haya datos: puede significar que ese período aún no se
sincroniza. Antes de concluir, mira las señales:
| Señal | Dónde | Qué te dice |
| ---------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `datosListos` | `conexiones.estado.consultar` | Si la conexión ya tiene algo que leer. Consúltalo después de conectar y antes de la primera lectura. |
| `sincronizacion` | en cada respuesta de `consultar` | La completitud del último sync del período pedido: `sincronizadoEn`, `completo`, cuántas filas quedaron `incompletos`. |
| `sincronizacion: null` | en cada respuesta de `consultar` | Dos causas, y ninguna invalida las filas devueltas: (a) no filtraste por `periodo`, así que no hay un sync único al que mirar; (b) ese período nunca se sincronizó. Un `null` junto a una lista con documentos es siempre (a). |
## Quién dispara la sincronización [#quién-dispara-la-sincronización]
| Camino | Cómo | Cuándo usarlo |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Programada | `cadencia` de la conexión: `daily`, `12h`, `6h` u `off` | El caso normal. Tus lecturas siempre encuentran datos frescos sin que nadie llame a nada. |
| A pedido, en línea | `sistema.conexion.sincronizar` vía `execute` | Cuando el llamador quiere esperar el resultado (un agente cerrando un mes, por ejemplo). |
| A pedido, asíncrona | `POST /v1/connections/{conn}/syncs` responde `202` con `job_ids`; el resultado llega por los webhooks `sync.succeeded` y `sync.failed` | Backends que no quieren mantener una request abierta un minuto. |
Dos syncs del mismo período no se pisan: hay un candado por conexión, y el segundo recibe
`connection_sync_in_progress` (409, reintentable). Basta esperar al primero.
## Próximos pasos [#próximos-pasos]
* [Conecta tu primera empresa](/docs/empezar/conectar): el recorrido completo, de punta a punta.
* [Guía del SII](/docs/sistemas/sii): qué trae cada alcance y cada cuánto conviene sincronizar.
* [Planos, scopes y alcances](/docs/conceptos/planos): el modelo completo detrás de esta regla.
---
# 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 [#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](https://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:
```bash
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`):
```json
{
"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](/docs/conceptos/bitacora). 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 [#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 `:read` es sensible.** Hoy son dos: `conexiones:write`, que emite enlaces donde una persona entrega credenciales reales, y `notta: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](/docs/operar/errores)).
`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](/docs/conceptos/planos).
## OAuth 2.1 para clientes MCP [#oauth-21-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:
1. El cliente descubre los metadatos del recurso protegido (`/.well-known/oauth-protected-resource`) y, desde ahí, el authorization server.
2. Se registra en caliente en `/register` (Dynamic Client Registration): sin coordinación previa y sin client secret, porque solo se admiten clientes públicos.
3. Abre `/authorize` con **PKCE S256** obligatorio; una persona inicia sesión, elige la organización y aprueba.
4. Canjea el código en `/token` y recibe un access token `connect_at_` y un refresh token `connect_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 [#el-challenge]
Una llamada a `/mcp` sin bearer no revela nada del catálogo; responde el challenge estándar:
```bash
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
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.
`/.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 [#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 [#próximos-pasos]
* [Tu primera llamada](/docs/empezar/primera-llamada): el quickstart con un dato real, sin conexión de por medio.
* [Conectar un sistema](/docs/empezar/conectar): del enlace de conexión a la primera sincronización.
* [MCP](/docs/agentes/mcp): las dos meta-tools y el recorrido que hace un agente.
---
# Conecta tu primera empresa
> De cero a leer el Registro de Compra-Venta de un cliente real, con un enlace de un solo uso, una sincronización y una consulta.
En este recorrido tu organización conecta a **Comercial Aurora SpA** (77.123.456-9) al SII: creas un
enlace, la persona entrega su clave tributaria en una página de Connect, sincronizas un período y lees
el resultado. Toma cerca de 10 minutos más el login del SII.
## Antes de empezar [#antes-de-empezar]
* Tu API key `connect_sk_…` con los scopes `conexiones:write` y `sii:read`
([connect.emisso.ai/api-keys](https://connect.emisso.ai/api-keys)).
* La clave tributaria **la entrega tu cliente, no tú**. No la pidas por correo ni la pegues en tu
código: para eso existe el enlace.
## 1. Crea el enlace de conexión [#1-crea-el-enlace-de-conexión]
### curl [#curl]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/conexiones.enlace.crear/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input": {"sistema": "sii"}}'
```
Salida esperada (`200`, campo `data`):
```json
{
"url": "https://connect.emisso.ai/c/mYw2kQ81xR4tPnZs",
"dominio": "connect.emisso.ai",
"sistema": "sii",
"sistemaNombre": "Servicio de Impuestos Internos",
"modo": "crear",
"expiraEn": "2026-08-07T15:32:11.000Z",
"intentosMaximos": 5,
"advertencia": "Este enlace pide credenciales de acceso al sistema. Muéstralo siempre con su dominio completo y di quién lo pidió y para qué. No lo presentes como un aviso del banco ni del SII."
}
```
### SDK TypeScript [#sdk-typescript]
```ts
const enlace = await connect.tools.conexiones.enlace.crear({ sistema: "sii" });
// enlace.url: compártela con tu cliente. enlace.expiraEn: cuándo vence.
```
### MCP [#mcp]
```json
{ "tool": "conexiones.enlace.crear", "params": { "sistema": "sii" } }
```
Siempre con su dominio completo visible, y explicando quién lo pidió y para qué. Nunca lo presentes
como un aviso del banco ni del SII: esa es exactamente la forma de un phishing, y tu cliente hace bien
en desconfiar. El enlace vence y sirve una sola vez.
## 2. Tu cliente entrega su clave, en nuestra página [#2-tu-cliente-entrega-su-clave-en-nuestra-página]
Al abrir el enlace, la persona ve una página de Connect con el nombre de tu organización y el sistema
a conectar, e ingresa el RUT de la empresa y su clave tributaria. La credencial viaja directo al
vault, cifrada para tu organización. No la ves tú, ni tu frontend, ni el agente que creó el enlace.
Si el login falla, la persona puede reintentar (cada intento reemplaza la credencial guardada). Si el
enlace venció, crea otro. El resultado te llega por dos vías: el webhook `connect_session.consumed`
(si tienes [webhooks](/docs/operar/webhooks) configurados) o consultando el estado, que es el paso
siguiente.
## 3. Confirma la conexión [#3-confirma-la-conexión]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/conexiones.estado.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input": {"sistema": "sii"}}'
```
Salida esperada (`200`, campo `data`, recortada):
```json
{
"conexiones": [{
"id": "conn_9tKfR2mQx4Vb",
"sistema": "sii",
"nombre": "Comercial Aurora SpA",
"estado": "active",
"credencial": "linked",
"verificadaEn": "2026-08-07T14:12:03.220Z",
"alcances": ["rcv", "boletas", "guias", "boletas_honorarios"],
"cadencia": "daily",
"trabajos": [],
"datosListos": false
}]
}
```
Guarda ese `conn_9tKfR2mQx4Vb`: es el `connectionId` que toda tool del SII te va a exigir, incluso
mientras tengas una sola conexión. Cuando conectes la segunda, el id va a ser lo único que las
distinga, y por eso no hay resolución implícita. El detalle del modelo está en
[Conexiones](/docs/conceptos/conexiones).
`datosListos: false` dice la verdad: la credencial quedó vinculada, pero todavía no hay nada que leer.
Falta un paso.
## 4. Sincroniza el primer período [#4-sincroniza-el-primer-período]
La sincronización abre una sesión real en el SII (un login, un logout) y persiste los alcances
solicitados en una sola pasada.
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input": {"periodo": "2026-07", "alcances": ["rcv"]}}'
```
Salida esperada (`200`, campo `data`, recortada):
```json
{
"periodo": "2026-07",
"results": [{
"alcance": "rcv",
"status": "ok",
"recordsSynced": 214,
"completo": true
}]
}
```
El mismo trabajo corre asíncrono por la [API de control](/docs/api-control):
`POST /v1/connections/conn_…/syncs` responde `202` con `job_ids` al instante, y el resultado llega por
el webhook `sync.succeeded`. Además, `cadencia: "daily"` ya deja este sync corriendo solo todos los
días.
## 5. Lee el RCV [#5-lee-el-rcv]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.rcv.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input": {"periodo": "2026-07", "perspectiva": "ventas"}}'
```
Salida esperada (`200`, campo `data`, recortada):
```json
{
"documentos": [
{ "tipoDte": 33, "folio": 4712, "rutReceptor": "76543210-3", "montoNeto": 1250000, "montoIva": 237500, "montoTotal": 1487500 }
],
"cursor": null,
"sincronizacion": { "sincronizadoEn": "2026-08-07T14:18:52.000Z", "completo": true }
}
```
La respuesta llega al instante porque lee lo que el paso 4 dejó persistido, no al SII. El shape
completo, con los 22 campos por documento y montos que cuadran al peso, está en la
[referencia de la tool](/docs/referencia/sii/rcv-consultar).
## Criterio de éxito [#criterio-de-éxito]
`conexiones.estado.consultar` ahora muestra `datosListos: true`, y `sii.rcv.consultar` devuelve
documentos con `sincronizacion.completo: true`. En tu
[bitácora](https://connect.emisso.ai/bitacora) quedaron las filas de cada paso, incluida la
sincronización con su latencia real.
## Próximos pasos [#próximos-pasos]
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): el modelo de dos pasos que acabas de usar, explicado entero.
* [Guía del SII](/docs/sistemas/sii): los cuatro alcances y sus verdades operativas.
* [Webhooks](/docs/operar/webhooks): entérate de cada sincronización sin hacer polling.
* [Conecta a tus clientes](/docs/operar/plataformas): si esto lo vas a repetir para muchos clientes finales desde tu producto.
---
# Tu primera llamada
> El valor de la UF de hoy por REST, SDK o MCP, en cerca de 2 minutos. No requiere conectar ningún sistema.
`indicadores` es un conector público y gratuito, así que sirve para probar el gateway completo
(autenticación, catálogo, bitácora) antes de tocar una credencial real. Esta página te lleva de una
API key recién creada a una respuesta con la UF de hoy y su fila de auditoría.
## Antes de empezar [#antes-de-empezar]
* Una cuenta en [connect.emisso.ai](https://connect.emisso.ai) con tu organización creada.
* Una API key: créala en [connect.emisso.ai/api-keys](https://connect.emisso.ai/api-keys) y copia el
secreto `connect_sk_…`. **Se muestra una sola vez.**
La UF también se sirve por el canal público, sin API key: `curl https://connect.emisso.ai/public/v1/indicadores/UF`.
Es la misma data, sin fila de bitácora. El detalle está en [Indicadores](/docs/sistemas/indicadores).
## 1. Pide la UF de hoy [#1-pide-la-uf-de-hoy]
Elige tu canal. Los tres llegan a la misma tool y dejan la misma auditoría.
### curl [#curl]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/indicadores.valor.actual/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input": {"codigo": "UF"}}'
```
Salida esperada (`200`):
```json
{
"data": {
"codigo": "UF",
"fecha": "2026-08-08",
"valor": 39487.23,
"unidad": "CLP",
"antiguedadDias": -1
},
"meta": {
"request_id": "req_8fk2mQpw31Zx",
"tool_id": "indicadores.valor.actual",
"plane": "action",
"latency_ms": 38,
"audit_status": "recorded"
}
}
```
### SDK TypeScript [#sdk-typescript]
```ts
import { createClient } from "@emisso/connect";
const connect = createClient({ apiKey: process.env.CONNECT_API_KEY! });
const uf = await connect.tools.indicadores.valor.actual({ codigo: "UF" });
console.log(uf.valor); // 39487.23, tipado como number
```
El accessor devuelve la `data` ya desenvuelta y tipada. Si necesitas el `meta` (por ejemplo el
`request_id`), usa `.withResponse()`. El detalle vive en la [guía del SDK](/docs/sdk).
### MCP [#mcp]
Con el servidor ya instalado ([cómo](/docs/agentes/mcp)), pídele a tu agente
«consulta el valor de la UF con Connect». Va a descubrir la tool con `search_docs` y a ejecutar:
```json
{ "tool": "indicadores.valor.actual", "params": { "codigo": "UF" } }
```
## 2. Lee la respuesta como corresponde [#2-lee-la-respuesta-como-corresponde]
`fecha` puede ser futura, porque la UF y la UTM se publican por adelantado. `antiguedadDias` lo
resuelve de una vez: `0` significa que el valor es de hoy, negativo que está fechado en el futuro
(normal en UF y UTM), positivo que la fuente está atrasada esa cantidad de días. Compáralo con tu
tolerancia antes de calcular plata con este número.
Guarda también el patrón del envelope. Todo éxito llega como `{ data, meta }`, y `meta.request_id` es
tu correlativo para la bitácora y para soporte.
## 3. Comprueba la bitácora [#3-comprueba-la-bitácora]
Abre [connect.emisso.ai/bitacora](https://connect.emisso.ai/bitacora). Deberías ver una fila nueva:
`indicadores.valor.actual`, resultado `ok`, tu API key como actor y la latencia registrada. Cada
llamada que hagas desde hoy, tuya o de tu agente, deja exactamente una fila como esta.
Con eso recorriste todo el gateway (autenticación, catálogo, ejecución y auditoría), el mismo camino
que sigue cada tool del SII y de los bancos; lo único que cambia es que esas exigen una conexión.
## Próximos pasos [#próximos-pasos]
* [Conecta tu primera empresa](/docs/empezar/conectar): el enlace hosted con el que tu cliente entrega su clave del SII.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
* [Autenticación](/docs/empezar/autenticacion): scopes de las API keys y OAuth para MCP.
---
# Enlace hosted de conexión
> Un enlace de un solo uso para que un tercero entregue la clave del SII o del banco: sin cuenta, sin acceso al dashboard y sin que la credencial pase por tu aplicación.
Quien tiene las credenciales de una fuente casi nunca es quien administra Emisso Connect: la clave del SII la tiene el contador, la del banco la tiene el dueño. El **enlace hosted** resuelve eso con una **sesión de conexión**: un enlace de un solo uso que permite entregar credenciales de **una fuente concreta**, para **una organización concreta**, sin crear una cuenta y sin dar acceso a nada más del dashboard.
El ciclo son cinco pasos: creas la sesión, envías el enlace, la persona entrega sus credenciales, tu aplicación se entera por `postMessage` (si lo incrustaste) o por `redirect_uri`, y además recibes un [webhook](/docs/operar/webhooks). Todo queda en la bitácora de la conexión.
## La vía sin código [#la-vía-sin-código]
Cuando no estás incrustando el widget en un producto, no necesitas la API: un agente conectado por [MCP](/docs/agentes/mcp) acuña el enlace en medio de la conversación con `conexiones.enlace.crear`, y por el SDK es una llamada tipada:
```ts
const enlace = await connect.tools.conexiones.enlace.crear({ sistema: "sii" });
```
Por MCP, la misma tool viaja dentro de la meta-tool `execute`:
```json
{ "tool": "conexiones.enlace.crear", "params": { "sistema": "sii" } }
```
Salida esperada, por cualquiera de los dos caminos:
```json
{
"url": "https://connect.emisso.ai/c/mYw2kQ81xR4tPnZs",
"dominio": "connect.emisso.ai",
"sistema": "sii",
"sistemaNombre": "Servicio de Impuestos Internos",
"modo": "crear",
"expiraEn": "2026-08-07T15:32:11.000Z",
"intentosMaximos": 5,
"advertencia": "Este enlace pide credenciales de acceso al sistema. Muéstralo siempre con su dominio completo y di quién lo pidió y para qué. No lo presentes como un aviso del banco ni del SII."
}
```
Este carril está deliberadamente recortado: el enlace vence en 1 hora (la persona está presente ahora), no acepta `allowed_origins` ni `redirect_uri` (los dos campos que lo volverían un vector de clickjacking y un open redirect en manos de un modelo de lenguaje) y tiene su propio tope de emisión por actor. La `advertencia` viaja ya escrita para que el agente la muestre tal cual.
Todo lo que sigue en esta página es la vía de integrador: `POST /v1/connect_sessions` con API key, que sí admite incrustar el widget y controlar el retorno.
## 1. Crear la sesión [#1-crear-la-sesión]
```bash
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`):
```json
{
"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-07-29T12:00:00.000Z",
"consumed_at": null,
"resulting_connection_id": null,
"created_at": "2026-07-28T12:00:00.000Z",
"url": "https://connect.emisso.ai/c/connect_cs_..."
},
"meta": { "request_id": "req_..." }
}
```
La `url` con el token en claro se devuelve **únicamente** en esta respuesta. La base guarda solo su `sha256` y un prefijo visible para nombrarlo en una lista, igual que una API key (`connect_sk_`). Si lo pierdes, emite otra sesión y revoca la anterior: no hay forma de recuperarlo.
| Parámetro | Qué hace |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode` | `create` (por defecto) emite una conexión nueva; `reauth` reemplaza la credencial de una conexión existente y exige `connection_id`. |
| `connector_code` | El código del conector, cualquiera de los private/tenant: `sii`, `bci_pyme`, `bice_empresas`, `banco_security`, `bch_empresas`. El enlace queda fijado a esa fuente: quien lo abre no elige otra. La lista viva la da `conexiones.sistemas.listar`. |
| `connection_id` | Obligatorio en `reauth`. Una conexión de otra organización es imposible de referenciar: lo impide una llave foránea compuesta en el esquema, no una validación de código. |
| `allowed_origins` | Hasta 5 orígenes `https` exactos, sin comodines ni path. Es la allowlist del iframe y el destino del `postMessage`. |
| `redirect_uri` | A dónde volver al terminar. Debe ser `https`, sin fragmento y, si registraste orígenes, pertenecer a ese conjunto. |
Un campo desconocido en el cuerpo devuelve `400 validation_error`: el esquema es estricto a propósito, porque un campo mal escrito es alguien creyendo que está configurando algo. La emisión está limitada por tasa (60 enlaces por hora y por organización); al excederla la respuesta es `429` con `Retry-After`. Los topes completos están en [límites](/docs/operar/limites).
**Vencimiento:** 24 horas en `mode: "create"` (margen para que el contador lo abra al día siguiente) y 1 hora en `mode: "reauth"` (se emite frente a una credencial ya rota, con la persona presente). **Intentos:** 5 por sesión; un formulario mal tipeado no gasta ninguno, solo cuenta un intento real contra la fuente. **Consumo:** solo al éxito, así que una clave equivocada no quema el enlace: se reintenta sin pedir otro.
`GET /v1/connect_sessions` lista las sesiones con su `token_prefix`, su estado y sus intentos; nunca el token. Acepta `?connection_id=conn_…` para acotar la lista a una [conexión](/docs/conceptos/conexiones).
También puedes emitir el enlace desde el dashboard, sin escribir código: en **Conexiones** para el modo `create`, y en los ajustes de una conexión para el modo `reauth`. Ahí mismo se **revoca**: la sesión pasa a `status: "revoked"`, igual que se revoca una API key, porque no hay borrado en ningún punto de este flujo. El enlace emitido desde el dashboard nace sin `allowed_origins`, así que no es incrustable: para el iframe usa la API.
## 2. Abrir el enlace [#2-abrir-el-enlace]
La persona abre `https://connect.emisso.ai/c/`, ve el nombre de tu organización y la fuente, entrega sus credenciales y recibe la confirmación. No hay registro, ni contraseña, ni acceso a ninguna otra pantalla.
Ante **cualquier** problema (token inexistente, vencido, ya consumido o con los intentos agotados) la página responde el **mismo 404**, sin cuerpo diferenciado. No es una molestia de diseño: cuatro respuestas distinguibles convertirían el enlace en un oráculo para quien barre tokens.
## 3. Incrustarlo en tu producto [#3-incrustarlo-en-tu-producto]
Con `allowed_origins` poblado, la página se puede incrustar. La cabecera `frame-ancestors` se construye por sesión desde esa lista, con `'none'` como valor por defecto cuando está vacía.
```html
```
Reglas: orígenes `https` exactos (`https://app.tu-producto.cl`, sin path ni comodín), máximo 5, y si no registras ninguno el iframe no carga.
## 4. Escuchar el resultado en el navegador [#4-escuchar-el-resultado-en-el-navegador]
Cuando el flujo llega a su estado final, la página emite un `postMessage` **hacia cada origen exacto** que registraste, nunca `"*"`. El mensaje no lleva la credencial ni el token:
```ts
window.addEventListener("message", (event) => {
if (event.origin !== "https://connect.emisso.ai") return; // verifica SIEMPRE el origen
const msg = event.data as { type: string; status: string; connection_id: string | null };
if (msg.type !== "emisso:connect") return;
if (msg.status === "connected") {
console.log("conexión lista:", msg.connection_id);
} else {
console.warn("terminó sin credencial; el enlace sigue vivo hasta que venza");
}
});
```
El mensaje es exactamente `{ type: "emisso:connect", status: "connected" | "failed", connection_id: string | null }` y nada más, y llega **una sola vez**: hay un único emisor.
Los dos estados son terminales, pero significan cosas distintas de lo que sugiere el nombre:
* `connected`: la credencial quedó vinculada y (si el conector declara una tool de verificación) verificada. `connection_id` trae el `conn_…`.
* `failed`: el flujo terminó **sin** credencial utilizable, típicamente porque la persona lo abandonó. `connection_id` es `null` siempre, incluso si un intento anterior alcanzó a crear la conexión: el anfitrión lee `status`, nunca la presencia del id.
Una clave rechazada por la fuente **no** es un estado terminal y no emite nada: el enlace sigue vivo y la persona reintenta en la misma pantalla, hasta agotar los 5 intentos.
El reintento **rota la credencial sobre la conexión que el enlace ya creó**, nunca crea una segunda. Eso vale también si la persona recarga la página o vuelve a abrir el enlace más tarde: el vínculo vive en la sesión, no en la pestaña. Por eso una sesión todavía `pending` puede traer `resulting_connection_id` con un `conn_…` ya poblado: significa «este enlace ya creó esta conexión y está rotando sobre ella», no que se haya consumido. Lo que marca el consumo sigue siendo `status: "consumed"` con su `consumed_at`.
Si registraste un `redirect_uri`, al llegar a ese estado final la página navega una sola vez (con `history.replace`, para que el botón «atrás» no lleve a un enlace ya consumido) agregando `status=connected|failed` a tu URL, y `connection_id` solo cuando hay conexión utilizable. El token nunca viaja en esa redirección; si tu `redirect_uri` traía un `token` propio, se elimina antes de navegar.
Si no registraste orígenes y no hay `redirect_uri`, no se emite ningún mensaje ni se navega: el resultado se consulta con `GET /v1/connections` o llega por webhook.
## 5. Recibir el webhook [#5-recibir-el-webhook]
Cuando el enlace se usa con éxito, Connect emite el evento **`connect_session.consumed`** por el mismo bus firmado que los eventos `sync.*` (`POST /v1/webhooks` para registrar un endpoint; la entrega trae `webhook-id`, `webhook-timestamp` y `webhook-signature`, y [la firma se verifica igual que siempre](/docs/operar/webhooks)).
```json
{
"type": "connect_session.consumed",
"timestamp": "2026-07-28T12:00:00.000Z",
"data": {
"session": { "id": "cs_7f3ab9c1d2e4f5061728" },
"connection": { "id": "conn_...", "connector": "sii" },
"verified": true,
"consumed_at": "2026-07-28T12:00:00.000Z"
}
}
```
`verified` distingue tres casos: `true` (la verificación contra la fuente corrió y pasó), `false` (corrió y falló; la conexión existe igual, con la credencial que se entregó) y `null` (el conector no declara una tool de verificación, así que no había nada que correr).
Si tu endpoint está caído, la conexión **igual quedó creada**: la entrega se reintenta con backoff y el resultado real está siempre en `GET /v1/connections`. Nunca hagas depender el alta de haber recibido el evento.
## 6. Auditoría [#6-auditoría]
Todo lo que hizo el enlace queda en la pestaña **Bitácora** de la conexión, atribuido con honestidad: las filas de configuración muestran `enlace de conexión (cs_…)` como actor (ni la persona que emitió el enlace, que no entregó nada, ni «sistema», que sería falso), y la verificación contra la fuente aparece en las ejecuciones porque pasa por el mismo `gateway.execute()` que cualquier otra llamada. Qué guarda cada fila y cómo leerla está en [la bitácora](/docs/conceptos/bitacora).
## Modelo de amenaza, en corto [#modelo-de-amenaza-en-corto]
| Riesgo | Qué lo acota |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fuerza bruta del token | 256 bits de entropía, búsqueda por igualdad exacta sobre índice único, tope de intentos, expiración corta, el mismo 404 en los cuatro fallos y un limitador de tasa por IP |
| Reuso del enlace | Un solo uso ganado por la base de datos, no por un chequeo previo en código |
| Clickjacking | `frame-ancestors` por sesión, orígenes exactos, sin comodines, `'none'` por defecto |
| Exfiltración por `postMessage` | `targetOrigin` siempre exacto; el mensaje nunca lleva la credencial ni el token |
| Redirector abierto | `redirect_uri` obligatoriamente `https`, sin fragmento, y dentro de `allowed_origins` cuando los registraste |
| Reenvío del enlace a un tercero | Expiración, un solo uso, tope de intentos y **notificación**: la fila en la bitácora más este webhook |
El cuadro completo de qué pasa con la credencial una vez entregada (cifrado, aislamiento, revocación) está en [seguridad](/docs/operar/seguridad).
---
# Manejo de errores
> Cada error de Connect llega con un código estable, una acción sugerida y un correlativo de soporte. Esta guía enseña a manejarlos; el catálogo completo vive en la referencia.
Todo error llega con la misma forma, pensada para que un humano o un agente sepan el paso siguiente
sin adivinar:
```json
{
"error": {
"code": "validation_error",
"message": "'sii.rcv.consultar' requiere una conexión concreta.",
"suggested_fix": "Pasa el connectionId de la conexión de 'sii' (MCP: campo 'connectionId' de execute; REST: header 'X-Connect-Connection'). Lístalos con la tool 'conexiones.estado.consultar'.",
"request_id": "req_5tYw2nRk88Ma"
},
"meta": { "request_id": "req_5tYw2nRk88Ma", "tool_id": "sii.rcv.consultar", "plane": "action", "latency_ms": 12, "audit_status": "recorded" }
}
```
## Las reglas [#las-reglas]
* **Decide por `code`, nunca por el texto.** `message` puede cambiar; los códigos no.
* **`suggested_fix` está escrito para ejecutarse.** Si tu agente recibe el error crudo, ya sabe qué
hacer.
* **`request_id` correlaciona con la [bitácora](/docs/conceptos/bitacora)** y es lo primero que pedirá
soporte. Guárdalo en tus logs.
* **Reintenta solo lo reintentable.** Cada código declara su semántica de reintento en el
[catálogo](/docs/referencia/errores). El SDK reintenta GET por su cuenta; los `execute` (POST) nunca
se reintentan solos: una acción regulada reintentada a ciegas se ejecuta dos veces.
## El handler de referencia [#el-handler-de-referencia]
```ts
import { ConnectError } from "@emisso/connect";
try {
await connect.tools.sii.rcv.consultar(input, { connectionId });
} catch (err) {
if (!(err instanceof ConnectError)) throw err;
switch (err.code) {
case "connection_credential_required":
// La clave cambió o fue revocada: pide reconectar con un enlace nuevo.
return pedirReconexion(connectionId);
case "connection_sync_in_progress":
// Reintentable: espera y vuelve a intentar.
return reintentarLuego();
default:
log.error({ code: err.code, requestId: err.requestId }, err.suggestedFix);
throw err;
}
}
```
## Los errores que vas a ver primero [#los-errores-que-vas-a-ver-primero]
| Código | HTTP | Reintentable | Qué significa y qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_credential_required` | 428 | no | La conexión no tiene credencial viva (clave cambiada o revocada). Enlace con `modo: "reconectar"`. |
| `connection_sync_in_progress` | 409 | sí | Ya corre un sync de ese período. Espera y reintenta. |
| `scope_not_granted` | 403 | no | La API key no tiene el scope de la tool. Revísala en [/api-keys](https://connect.emisso.ai/api-keys). Scopes vacíos significan cero autoridad, no comodín. |
| `validation_error` | 400 | no | El input no cumple el schema; `error.details` trae el detalle campo a campo. También cubre el `connectionId` ausente del ejemplo de arriba. |
| `tool_not_found` | 404 | no | Ese id no existe para tu organización. También cubre conectores no habilitados. |
| `connection_session_pending` | 409 | sí | Solo BICE: el portal exige navegador y la sesión aún no se acuña. Corre la sincronización de esa conexión o espera la programada. |
| `rate_limited` | 429 | sí | Superaste un límite. Respeta el backoff y reintenta. |
| `upstream_error` | 502 | sí | El SII o el banco fallaron al otro lado. Reintentar suele bastar. |
El catálogo completo, con los 34 códigos agrupados por dominio y una acción por cada uno, está en la
[referencia de errores](/docs/referencia/errores).
## Próximos pasos [#próximos-pasos]
* [Catálogo de errores](/docs/referencia/errores): los 34 códigos con qué significa y qué hacer.
* [Webhooks](/docs/operar/webhooks): los fallos de sincronización también llegan como evento firmado.
* [Límites y planes](/docs/operar/limites): los números detrás de 429 y 402.
---
# Límites
> El tamaño máximo de petición, la emisión de enlaces, la cola de sincronizaciones y el cupo de llamadas por conexión. Cada número de esta página existe como constante en el código que lo aplica.
Connect tiene pocos límites, y esta página los junta todos. Los números que leas aquí salen del código que los aplica; cuando un tope depende del plan y no de una constante, la prosa lo dice y el valor vigente está en el dashboard, en [connect.emisso.ai/billing](https://connect.emisso.ai/billing).
## Límites de petición [#límites-de-petición]
**Cuerpo de la petición: 256 KB.** Aplica a toda la superficie (`/v1/tools/{id}/execute` y las rutas de control). Se valida el `Content-Length` declarado y además se lee el cuerpo con tope, así que un header mentiroso no lo evade. Al excederlo la respuesta es `413 payload_too_large`.
**Emisión de enlaces de conexión: 60 por hora por organización.** Es el tope de `POST /v1/connect_sessions`, el que acota la emisión masiva de páginas con la marca de Connect. Pasado el tope, la respuesta es `429 rate_limited` con un header `Retry-After` en segundos.
**Carril agente: 5 enlaces por hora por actor.** La tool [`conexiones.enlace.crear`](/docs/referencia/conexiones) tiene un tope propio encima del de organización. Una conversación real necesita uno a tres enlaces; el tope deja margen para un reintento y acota lo que puede emitir un agente con el prompt comprometido.
## Sincronizaciones [#sincronizaciones]
El sistema externo (el portal del SII, el banco) es el recurso escaso, y los topes de esta sección existen para no quemarlo:
* **Candado por conexión.** Cada conexión admite una sola operación contra su sistema a la vez. Si el candado está tomado, la respuesta es `409 connection_busy`; es reintentable y el candado se suelta solo.
* **Un trabajo activo por conexión y período.** Encolar un período que ya tiene un trabajo en cola o corriendo devuelve `409 connection_sync_in_progress`. Sondea el trabajo en vuelo en lugar de encolar otro: puede que ya haya datos.
* **Cola de pendientes: 50 trabajos por organización** (encolados más corriendo), y **hasta 24 períodos por petición** de backfill. Superar cualquiera de los dos responde `429 too_many_pending`; deja terminar lo que corre y reintenta.
* **Techo por trabajo: 120 segundos.** Es el presupuesto de ejecución de una sincronización. Para rangos largos, la vía es asíncrona: `POST /v1/connections/{conn}/syncs` responde `202` con los `job_ids`, y el avance se sondea en `GET /v1/syncs/{id}` o llega por [webhook](/docs/operar/webhooks).
Las cadencias programadas disponibles por conexión son `off`, `daily`, `12h` y `6h`. Se configuran en los ajustes de cada conexión del dashboard; la sincronización a pedido convive con la programada bajo los mismos candados.
## Medición y plan [#medición-y-plan]
La fuente de la medición es la [bitácora](/docs/conceptos/bitacora): cada llamada deja exactamente una fila, con sus `billable_units`. No hay un contador paralelo que pueda divergir de lo auditado.
Qué cuenta contra el cupo de llamadas de una conexión:
* Solo llamadas **exitosas**, de las categorías que ejecutan trabajo real: acciones, sincronizaciones y consultas del plano persistido. Un error factura 0.
* El peso por llamada lo declara cada conector; el peso por defecto es 1.
* Los conectores `open` (`core`, `indicadores`) y el plano de control (`conexiones.*`) facturan 0 siempre: la fila de bitácora existe igual, con `billable_units` en cero.
* Lo que haces desde el dashboard o desde el flujo hosted no consume cuota: guardar tu propia credencial o paginar tus propios datos no puede gastar el presupuesto de tu integración.
**El cupo es de 2.000 llamadas por conexión al mes, y es el mismo número en los tres planes.** Corta por conexión, no por organización: cada conexión mide su propio período contra su propio techo, y cuando una lo alcanza sólo ESA conexión responde `429 quota_exceeded`. Las demás conexiones de la misma organización siguen operando sin cambios. **Subir de plan no mueve este techo**, porque no es un atributo del plan sino una constante del producto. Si tu organización tiene un cupo ampliado negociado, el vigente es el que aparece en [/billing](https://connect.emisso.ai/billing), junto al uso del período en curso.
Tres códigos distintos avisan que algo se frenó, y conviene no confundirlos:
| Código | Estado | Reintentable | Qué significa | Qué hacer |
| ------------------ | ------ | ------------ | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `rate_limited` | 429 | Sí | Superaste un tope de tasa puntual (por ejemplo, la emisión de enlaces). | Espera y reintenta con backoff; respeta `Retry-After` cuando viene. |
| `quota_exceeded` | 429 | No | Esta conexión alcanzó su cupo de llamadas del período. Tus demás conexiones siguen funcionando. | Espera el período siguiente o pide una ampliación. Subir de plan no cambia el cupo, y reintentar tampoco. |
| `billing_past_due` | 402 | No | La organización tiene un pago vencido. | Regulariza el pago en [/billing](https://connect.emisso.ai/billing). Reintentar no paga la deuda. |
`billing_past_due` pausa lo que trae datos nuevos del sistema externo (la sincronización, programada o a pedido) y la creación de conexiones. La lectura de lo ya persistido sigue abierta a propósito: esos datos son del cliente, ya están en Connect, y retenerlos como palanca de cobro sería retener información que le pertenece.
El plan contratado sí gobierna otras dos cosas, y ninguna es el cupo de llamadas: cuántas conexiones **incluye** antes de que las siguientes se cobren como add-on, y si incluye webhooks (Starter no los incluye; Pro y Max sí). La sincronización programada no es una diferencia de plan: los tres la incluyen. Ninguno de los dos topes anteriores es una constante del código; el número vigente de tu organización está en [/billing](https://connect.emisso.ai/billing). El panel, en la portada del dashboard, muestra además un total de llamadas de la organización a modo informativo: no tiene techo propio y no corta nada, y el corte real sigue siendo el de la conexión, arriba. Crear un endpoint de webhook sin la función responde `403 feature_not_in_plan`.
## Próximos pasos [#próximos-pasos]
* El catálogo completo de códigos, con qué significa y qué hacer en cada uno: [errores](/docs/operar/errores).
* Configurar los avisos de sincronización en lugar de sondear: [webhooks](/docs/operar/webhooks).
* Qué es una conexión y por qué es la unidad de facturación: [conexiones](/docs/conceptos/conexiones).
* La superficie de control que aplica estos topes: [API de control](/docs/api-control).
---
# Conecta a tus clientes
> El caso plataforma, un producto que conecta a muchos clientes finales, con el modelo de datos, el alta embebida y los topes que importan a escala.
Si tu producto (un ERP, un software de gestión, una plataforma vertical) necesita leer el SII o los
bancos de muchos clientes finales, este es tu mapa. El mecanismo es el mismo de
[Conecta tu primera empresa](/docs/empezar/conectar); lo que cambia a escala es el modelo de datos,
cómo identificas a cada cliente y contra qué topes vas a operar.
## El modelo: dos niveles, no tres [#el-modelo-dos-niveles-no-tres]
```text
organización (org_…) tu cuenta. Aquí viven tus API keys y tu facturación.
└── conexión (conn_…) un cliente tuyo en un sistema. La unidad de todo lo demás.
```
No existe una entidad «proyecto». Si vienes de un modelo cuenta → proyecto → enlace, el equivalente del
proyecto es la conexión, y el enlace de un solo uso es solo el permiso para entregar la credencial:
una vez consumido, lo que queda es la conexión. Una organización puede tener tantas conexiones del
mismo sistema como clientes tengas; con dos o más activas, toda llamada debe nombrar cuál con
`X-Connect-Connection` (omitirlo responde `validation_error` con la lista de candidatas en el
`suggested_fix`). El detalle del ciclo de vida está en [Conexiones](/docs/conceptos/conexiones).
## Identificar a tu cliente [#identificar-a-tu-cliente]
Las conexiones no tienen un campo `external_id` ni `metadata`, y su `display_name` lo autogenera el
servidor (`SII`, `SII 2`). **El mapeo cliente ↔ `conn_…` lo guardas tú**, y el momento de capturarlo
es el alta: el `connection_id` llega en el `postMessage` del iframe cuando la persona completa el
enlace, y en el webhook `connect_session.consumed` si tienes [webhooks](/docs/operar/webhooks)
configurados. Guarda ese par en tu base al recibirlo; después no hay endpoint para reconstruirlo
desde tu lado (solo `GET /v1/connections`, que lista ids y nombres autogenerados).
El aislamiento es por organización, no por conexión: una API key con `sii:read` puede leer cualquier
conexión del SII de tu organización, y no se puede acuñar una clave limitada a una sola conexión. Si
tu arquitectura exige muros entre clientes finales, el muro lo pones tú delante de Connect.
## El alta, dentro de tu producto [#el-alta-dentro-de-tu-producto]
`POST /v1/connect_sessions` con `allowed_origins` devuelve una URL de un solo uso que se monta en un
iframe de tu app: tu cliente escribe su clave en la pantalla de Connect sin salir de tu producto, la
credencial viaja directo al vault y el resultado te vuelve por `postMessage`, por `redirect_uri` y
por webhook. El contrato completo (orígenes, TTL, intentos, el modelo de amenaza) está en
[el enlace hosted](/docs/operar/enlace-hosted); el endpoint, en la
[API de control](/docs/api-control). Dos datos que importan a escala:
* La emisión de enlaces tiene tope por organización (60 por hora, `429` con `Retry-After`): un alta
masiva de clientes se planifica por tandas.
* Por el carril del enlace, la primera sincronización queda encolada sola al conectar. Por el
dashboard no: ahí el alta solo verifica la credencial.
## Cuando la credencial de un cliente se rompe [#cuando-la-credencial-de-un-cliente-se-rompe]
Te enteras por tres señales, en este orden si tienes webhooks: llega `sync.failed`, las llamadas de
esa conexión empiezan a responder `connection_credential_required` (428, con la sugerencia del SII o
del banco en el mensaje), y al tercer fallo de autenticación consecutivo la cadencia automática de esa
conexión se pausa sola y deja de encolar. La conexión sigue `active` y sus tools de consulta siguen
leyendo lo ya persistido; lo que se detiene es el reloj.
La reconexión es el mismo endpoint de enlaces con `"mode": "reauth"` y el `connection_id`: rota la
credencial sobre la misma conexión, sin duplicarla. Un detalle operativo: el enlace `reauth` no
reanuda la cadencia pausada; eso hoy se hace desde el dashboard (rotar la credencial o verificar la
conexión desde su pestaña de ajustes limpia la pausa y restaura la cadencia anterior).
## Operar la flota [#operar-la-flota]
Los topes que vas a tocar con decenas o cientos de conexiones, todos del código:
| Tope | Valor |
| -------------------------------------------------------------------- | -------------------------------- |
| Trabajos de sincronización encolados más corriendo, por organización | 50 (`429 too_many_pending`) |
| Períodos por petición de backfill | 24 |
| Ritmo de drenaje de la cola | hasta 10 trabajos cada 5 minutos |
| Trabajos simultáneos por conexión | 1, y uno solo activo por período |
| Enlaces de conexión por hora, por organización | 60 |
El ritmo de drenaje define tu techo de sincronización: alrededor de 120 trabajos por hora. Un
backfill grande de muchos clientes se reparte en tandas y se supervisa con los webhooks `sync.*` o
con `GET /v1/syncs/{id}`, no reencolando a ciegas.
Sobre la cuota: **encolar no consume cuota**; lo que cuenta como llamada es cada sincronización
ejecutada y cada página de consulta (el detalle de categorías está en
[Límites y planes](/docs/operar/limites)). El cupo es **por conexión**, así que la aritmética de una
flota grande no se multiplica por el número de conexiones: cada una mide su propio período contra el
mismo techo, y agregar clientes no acerca a los demás al corte. Lo que hay que dimensionar por
conexión es sincronizaciones diarias × páginas leídas. Subir de plan no mueve ese techo; si tu
cadencia lo roza, escríbenos desde [connect.emisso.ai/billing](https://connect.emisso.ai/billing) y
lo ampliamos para tu organización.
## Lo que todavía se hace solo desde el dashboard [#lo-que-todavía-se-hace-solo-desde-el-dashboard]
Hoy no hay endpoint para crear organizaciones, acuñar o rotar API keys, renombrar, deshabilitar o
eliminar una conexión, ni configurar la cadencia de sincronización. Si tu integración necesita
alguna de esas operaciones por API, escríbenos: saber qué falta primero es lo que ordena el roadmap.
## Próximos pasos [#próximos-pasos]
* [El enlace hosted](/docs/operar/enlace-hosted): el contrato completo del alta embebida.
* [API de control](/docs/api-control): sesiones, sincronizaciones y webhooks, endpoint por endpoint.
* [Webhooks](/docs/operar/webhooks): enterarte de cada alta y cada sync sin polling.
---
# Seguridad
> Dónde queda la clave del banco, cómo se cifra, cómo se aísla cada organización, qué guarda la auditoría y qué se puede revocar al instante.
Conectar el SII o un banco significa entregarle a Connect una credencial real, y evaluar eso exige respuestas concretas: dónde queda la clave, quién puede leerla, qué pasa si algo se compromete. Esta página responde en ese orden. Cada afirmación describe comportamiento del sistema, no intención.
## La credencial nunca te toca [#la-credencial-nunca-te-toca]
El diseño parte por sacar la credencial de tu camino. Con el [enlace hosted](/docs/operar/enlace-hosted), la persona que tiene la clave la escribe en una página de `connect.emisso.ai`, nunca en tu aplicación: tu producto emite el enlace y recibe el resultado (`connection_id`, estado), pero la clave no pasa por tu frontend ni por tu backend, y el contexto de tu agente tampoco la ve.
Del lado de Connect, la credencial se cifra para la organización dueña de la conexión y queda en el vault. En tiempo de llamada se resuelve del lado del servidor, en el momento exacto en que un conector la necesita para abrir sesión contra la fuente. De ahí no sale: no entra al contexto de un LLM ni aparece en un tool result, y ningún log la registra cruda. La misma regla cubre el material derivado de la credencial, como las sesiones de portal que algunos bancos exigen: se resuelven server-side y jamás viajan en una respuesta.
## Cifrado [#cifrado]
El modelo es envelope encryption por organización. Cada organización tiene su propia clave de datos (DEK); las credenciales se cifran con AES-256-GCM bajo esa DEK, y la DEK se guarda a su vez envuelta por una clave maestra (KEK) que no vive en la base de datos, sino en un vault administrado. El material cifrado lleva versión de clave, la costura que permite rotar sin re-cifrar a ciegas.
Hay una sola implementación de cifrado para todo el sistema: los campos sensibles del plano persistido y los secretos de webhook se sellan con el mismo tronco (la misma DEK por organización, el mismo sellado autenticado). Un secreto de webhook, igual que una credencial, solo se muestra al crearse o rotarse; ninguna lectura posterior lo devuelve.
## Aislamiento multitenant [#aislamiento-multitenant]
Toda fila de datos de negocio lleva su `organization_id`, y sobre eso corren dos guardias independientes:
1. **La aplicación filtra siempre por organización.** Es la guardia primaria: cada consulta lleva el filtro explícito.
2. **La base impone Row Level Security como respaldo, con default deny.** El contexto de organización se fija por transacción; si no se fijó, la política no calza con ninguna fila y la consulta devuelve cero resultados. Un filtro olvidado en el código degrada a «no ves nada», no a una fuga.
El runtime se conecta con un rol propio, sin privilegio de saltarse RLS y sin permiso de DELETE sobre las tablas; los roles administrativos con bypass no participan del camino de ejecución. En el dominio del SII y los bancos, el diseño trata una fuga entre organizaciones como un riesgo existencial; por eso las guardias son dos.
## Redacción y auditoría [#redacción-y-auditoría]
Toda llamada a una tool deja exactamente una fila en la [bitácora](/docs/conceptos/bitacora), con actor, conexión, resultado y latencia. Del input, la fila guarda solo un `input_digest`: el hash SHA-256 del input ya redactado, donde los campos con forma de secreto (token, password, credential y similares) se reemplazan antes de calcular el hash. El input crudo no se almacena.
La bitácora es append-only: no existe camino de UPDATE ni de DELETE sobre ella, ni siquiera para los roles del sistema. Los fallos de autenticación ocurren antes de conocer la organización, así que van a un registro de eventos de seguridad separado, nunca a la bitácora de un tenant. Y los [webhooks](/docs/operar/webhooks) siguen la misma disciplina de redacción: sus payloads se limitan a códigos del catálogo y conteos.
## Revocación [#revocación]
Cuando algo se compromete, lo que importa es cuánto tarda en dejar de funcionar:
* **Una API key** se revoca al instante desde el dashboard y deja de autenticar en la siguiente petición. Es el freno de emergencia: a propósito, cualquier miembro de la organización puede accionarlo, sin esperar a un admin.
* **El acceso de un agente MCP** se revoca desde [Agentes](https://connect.emisso.ai/agentes) y deja de autenticar en la siguiente petición: la revocación cierra todas las sesiones que esa persona abrió con ese cliente, incluido el refresh token, así que el agente no puede volver solo. Como con una API key, cualquier miembro de la organización puede accionarlo.
* **Un enlace de conexión** se revoca desde el dashboard; desde ese momento responde el mismo 404 que cualquier enlace inválido.
* **Una conexión** se deshabilita completa: sus tools dejan de resolver (`connection_disabled`) y la sincronización programada se detiene con ella.
* **Rotar la credencial de una conexión** invalida además la sesión de portal derivada de la clave anterior: la sesión vieja se expira en el acto, así que no queda material capaz de verificar en verde una credencial que ya no existe.
* **El secreto de un webhook** se rota con una ventana de 24 horas en que conviven ambos, para no coordinar despliegues bajo presión.
En todos los casos la revocación es un cambio de estado, nunca un borrado: el rastro de lo que esa pieza hizo queda íntegro en la auditoría.
## El modelo de amenaza del enlace [#el-modelo-de-amenaza-del-enlace]
El enlace hosted es la superficie más expuesta del sistema (una URL que recolecta claves bancarias), y por eso tiene su análisis propio. El resumen:
| Riesgo | Qué lo acota |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Fuerza bruta del token | 256 bits de entropía, tope de intentos, expiración corta y el mismo 404 para todos los fallos |
| Reuso del enlace | Un solo uso, garantizado por la base de datos y no por un chequeo previo en código |
| Clickjacking y exfiltración | `frame-ancestors` por sesión con orígenes exactos, y un `postMessage` que nunca lleva credencial ni token |
| Reenvío del enlace a un tercero | Expiración, tope de intentos y notificación: la fila en la bitácora más el webhook `connect_session.consumed` |
La tabla completa, con el ciclo de vida y la semántica de cada estado, está en [enlace hosted](/docs/operar/enlace-hosted).
## Próximos pasos [#próximos-pasos]
* El flujo que mantiene la clave fuera de tu aplicación, paso a paso: [enlace hosted](/docs/operar/enlace-hosted).
* Qué guarda exactamente cada fila de auditoría: [la bitácora](/docs/conceptos/bitacora).
* Qué es una conexión y cómo se administra su ciclo de vida: [conexiones](/docs/conceptos/conexiones).
* Los topes que protegen la superficie: [límites](/docs/operar/limites).
---
# Webhooks
> Un aviso firmado cuando una sincronización termina o un enlace de conexión se usa, sin sondear. El webhook nunca es load-bearing: el estado real siempre se puede consultar.
Connect entrega eventos por HTTP POST a los endpoints que registres: cada sincronización que termina y cada enlace de conexión que se consume. La alternativa es sondear [`GET /v1/syncs/{id}`](/docs/api-control), que funciona igual de bien, solo que pagando la espera con llamadas tuyas.
Una regla gobierna todo el diseño: **el webhook nunca es load-bearing**. Si tu endpoint está caído, la sincronización igual terminó y la conexión igual quedó creada; la entrega se reintenta sola y el estado real vive siempre en la API. Trata cada evento como un aviso de que hay algo que mirar, nunca como la fuente del dato.
## Eventos [#eventos]
| Evento | Cuándo dispara |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `sync.succeeded` | Un trabajo de sincronización terminó bien. Incluye los resultados parciales: el detalle por alcance viaja en `outcomes`. |
| `sync.failed` | El trabajo terminó en fallo terminal: un error no reintentable, o se agotaron los reintentos internos. |
| `sync.partial` | Reservado. Es suscribible, pero el pipeline actual no lo emite: un parcial llega como `sync.succeeded` con el detalle en `outcomes`. |
| `connect_session.consumed` | Un [enlace de conexión](/docs/operar/enlace-hosted) se usó con éxito y dejó una conexión creada o reautenticada. |
Cuerpo de un `sync.succeeded`:
```json
{
"type": "sync.succeeded",
"timestamp": "2026-08-07T09:00:12.000Z",
"data": {
"job_id": "sjb_k2Rw81QpLm3N",
"connection": { "id": "conn_9tKfR2mQx4Vb", "connector": "sii", "label": "Comercial Aurora SpA" },
"alcances": ["rcv", "boletas"],
"periodo": "2026-07",
"status": "succeeded",
"records_synced": 214,
"last_error": null,
"outcomes": [
{ "alcance": "rcv", "status": "ok", "records_synced": 180 },
{ "alcance": "boletas", "status": "ok", "records_synced": 34 }
],
"trigger": "scheduled",
"request_id": "req_..."
}
}
```
Un trabajo donde un alcance falló y otro no sigue siendo `sync.succeeded`: el trabajo hizo progreso real. La mezcla se lee en `outcomes`, donde cada alcance declara su propio `status` (`ok`, `partial` o `failed`) y, cuando falló, un `error` con un código del [catálogo](/docs/operar/errores):
```json
"outcomes": [
{ "alcance": "rcv", "status": "ok", "records_synced": 180 },
{ "alcance": "boletas", "status": "failed", "records_synced": 0, "error": "timeout" }
]
```
Algunos conectores agregan a un outcome los campos `incompletos` (cuántos registros quedaron a medio traer) y `completo` (si el período quedó cerrado); solo aparecen cuando el conector los reporta.
El `sync.failed` tiene la misma forma, con `records_synced` en `null`, `outcomes` vacío y el código del error en `last_error`:
```json
{
"type": "sync.failed",
"timestamp": "2026-08-07T09:05:30.000Z",
"data": {
"job_id": "sjb_...",
"connection": { "id": "conn_9tKfR2mQx4Vb", "connector": "sii", "label": "Comercial Aurora SpA" },
"alcances": ["rcv", "boletas"],
"periodo": "2026-07",
"status": "failed",
"records_synced": null,
"last_error": "upstream_error",
"outcomes": [],
"trigger": "scheduled",
"request_id": "req_..."
}
}
```
Tanto `last_error` como el `error` de cada outcome llevan códigos del catálogo, nunca texto libre: el payload de un webhook jamás incluye documentos sincronizados, campos de credencial ni mensajes crudos del sistema externo. El `request_id` es el mismo que quedó en la [bitácora](/docs/conceptos/bitacora), así que correlacionar el evento con su ejecución es una búsqueda exacta.
El cuarto evento avisa que un enlace hosted se completó:
```json
{
"type": "connect_session.consumed",
"timestamp": "2026-07-28T12:00:00.000Z",
"data": {
"session": { "id": "cs_7f3ab9c1d2e4f5061728" },
"connection": { "id": "conn_9tKfR2mQx4Vb", "connector": "sii" },
"verified": true,
"consumed_at": "2026-07-28T12:00:00.000Z"
}
}
```
`verified` admite tres valores: `true` cuando la verificación contra la fuente corrió y pasó; `false` cuando corrió y falló, aunque la conexión existe igual con la credencial entregada; `null` cuando el conector no declara una tool de verificación y no había nada que correr. El payload identifica qué enlace se usó y qué conexión resultó, y nada más: nunca el token del enlace ni ningún campo de lo que la persona escribió.
## Crear un endpoint [#crear-un-endpoint]
```bash
curl -X POST https://connect.emisso.ai/api/v1/webhooks \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.tu-producto.cl/hooks/connect",
"event_types": ["sync.succeeded", "sync.failed"],
"connection_id": "conn_9tKfR2mQx4Vb"
}'
```
Respuesta (`201`):
```json
{
"data": {
"id": "whk_...",
"url": "https://api.tu-producto.cl/hooks/connect",
"event_types": ["sync.succeeded", "sync.failed"],
"connection_id": "conn_9tKfR2mQx4Vb",
"enabled": true,
"disabled_at": null,
"last_success_at": null,
"created_at": "2026-08-07T12:00:00.000Z",
"secret": "whsec_..."
},
"meta": { "request_id": "req_..." }
}
```
`secret` se devuelve únicamente al crear el endpoint (y al rotarlo). Se guarda cifrado y ninguna lectura posterior lo incluye: `GET /v1/webhooks` devuelve el endpoint sin él. Si lo pierdes, rota el secreto.
Reglas del alta:
* `event_types` vacío u omitido suscribe a **todos** los eventos, presentes y futuros. Listar tipos explícitos congela la suscripción a esos.
* `connection_id` es opcional y acota el endpoint a los eventos de esa conexión. Debe ser una conexión de tu organización.
* La `url` debe ser `https` y resolver a una dirección pública. Direcciones internas o no resolubles se rechazan con `422 invalid_webhook_url`.
* Un endpoint solo recibe eventos **posteriores** a su creación; registrarlo no re-entrega historia.
* Los webhooks son una función de plan: sin ella el alta responde `403 feature_not_in_plan`.
El mismo alta existe sin código en el dashboard, en [connect.emisso.ai/webhooks](https://connect.emisso.ai/webhooks). Para editar por API: `PATCH /v1/webhooks/{id}` acepta `url`, `event_types` y `enabled`; `DELETE /v1/webhooks/{id}` deshabilita (nunca borra: las entregas históricas se conservan).
## Verificar la firma [#verificar-la-firma]
Toda entrega llega firmada con el esquema Standard Webhooks. Tres headers acompañan el cuerpo:
| Header | Contenido |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `webhook-id` | El id del evento (`whev_...`). Es **estable entre reintentos** y entre endpoints: úsalo como clave de deduplicación. |
| `webhook-timestamp` | El instante del intento de entrega, en segundos Unix. Cada reintento se firma de nuevo con el suyo. |
| `webhook-signature` | Una o más firmas separadas por espacio, cada una con la forma `v1,`. Hay más de una solo durante una rotación de secreto. |
Lo que se firma es la cadena `id.timestamp.body`, donde `body` son los bytes exactos del cuerpo recibido. El algoritmo: HMAC-SHA256, con clave igual al secreto sin su prefijo `whsec_` decodificado de base64; el digest viaja en base64 con el prefijo de versión `v1,`. Este verificador espeja el del servidor:
```ts
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(input: {
secret: string; // whsec_... tal como lo entregó POST /v1/webhooks
id: string; // header webhook-id
timestamp: number; // header webhook-timestamp, en segundos
rawBody: string; // el cuerpo crudo recibido, sin parsear ni re-serializar
header: string; // header webhook-signature
}): boolean {
// 1. Tolerancia de reloj: 300 segundos. Como cada reintento trae su propio
// timestamp, un reintento tardío nunca queda fuera de la ventana.
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - input.timestamp) > 300) return false;
// 2. La clave HMAC es el secreto sin el prefijo whsec_, decodificado de base64.
const key = Buffer.from(input.secret.slice("whsec_".length), "base64");
// 3. Se firma `${id}.${timestamp}.${body}`; el digest va en base64 tras "v1,".
const expected = Buffer.from(
`v1,${createHmac("sha256", key).update(`${input.id}.${input.timestamp}.${input.rawBody}`).digest("base64")}`,
);
// 4. El header puede traer varias firmas separadas por espacio (rotación de
// secreto): la entrega es válida si cualquiera calza, en tiempo constante.
return input.header.split(" ").some((candidate) => {
const c = Buffer.from(candidate);
return c.length === expected.length && timingSafeEqual(c, expected);
});
}
```
El cuerpo se envía como JSON canónico: claves ordenadas alfabéticamente, sin espacios. La firma cubre exactamente esos bytes. Si haces `JSON.parse` y vuelves a serializar antes de verificar, los bytes cambian y la firma deja de calzar. Lee el cuerpo crudo, verifica, y recién entonces parsea. Los ejemplos de esta página van indentados solo para leerse.
## Reintentos y reenvío [#reintentos-y-reenvío]
Una entrega cuenta como exitosa cuando tu endpoint responde `2xx` dentro de 10 segundos. Una redirección no se sigue y cuenta como rechazo. Ante un fallo, la política distingue dos clases:
* **Se reintenta** lo que puede sanar solo: errores de red, respuestas `5xx`, `408` y `429`.
* **Es terminal** cualquier otro `4xx`: tu endpoint está rechazando el evento y volver a golpear no lo arregla.
Los reintentos son hasta 6 intentos en total, con esperas crecientes tras cada fallo: 1 minuto, 5 minutos, 30 minutos, 2 horas y 6 horas. El bus de entregas corre cada minuto, así que cada espera es un piso, no un instante exacto. Con 5 fallos terminales consecutivos el endpoint se deshabilita solo (`enabled: false`, con `disabled_at` poblado); una entrega exitosa reinicia el contador, y rehabilitarlo es un `PATCH` con `{"enabled": true}` o un clic en el dashboard.
Las entregas de un endpoint se listan con su resultado:
```bash
curl "https://connect.emisso.ai/api/v1/webhooks/whk_.../deliveries?limit=5" \
-H "Authorization: Bearer connect_sk_..."
```
Respuesta (`200`):
```json
{
"data": [
{
"id": "whd_...",
"event_type": "sync.succeeded",
"status": "delivered",
"attempts": 1,
"response_status": 200,
"error": null,
"created_at": "2026-08-07T09:00:14.000Z",
"updated_at": "2026-08-07T09:00:15.000Z"
},
{
"id": "whd_...",
"event_type": "sync.failed",
"status": "failed",
"attempts": 6,
"response_status": 500,
"error": "HTTP 500",
"created_at": "2026-08-06T21:10:02.000Z",
"updated_at": "2026-08-07T05:41:10.000Z"
}
],
"pagination": { "cursor": null, "hasMore": false }
}
```
`status` recorre `pending`, `delivering`, `delivered` y `failed`; `limit` acepta de 1 a 100, con 50 por defecto. Una entrega que quedó en `failed` se reenvía a mano:
```bash
curl -X POST "https://connect.emisso.ai/api/v1/webhooks/whk_.../deliveries/whd_.../resend" \
-H "Authorization: Bearer connect_sk_..."
```
Respuesta (`200`):
```json
{ "data": { "resent": true } }
```
El reenvío vuelve a poner la entrega en `pending` con el contador de intentos en cero, y el bus la toma en su próxima pasada. El `webhook-id` no cambia: tu deduplicación lo verá como el mismo evento, que es exactamente lo que es.
## Rotar el secreto [#rotar-el-secreto]
```bash
curl -X POST https://connect.emisso.ai/api/v1/webhooks/whk_.../roll-secret \
-H "Authorization: Bearer connect_sk_..."
```
Respuesta (`200`):
```json
{
"data": { "secret": "whsec_..." },
"meta": { "request_id": "req_..." }
}
```
Durante las 24 horas siguientes conviven los dos secretos: cada entrega lleva en `webhook-signature` una firma por cada secreto vigente, separadas por espacio. El verificador de arriba ya lo contempla (acepta si cualquiera calza), así que la rotación no exige coordinar un despliegue: publica el secreto nuevo en tu endpoint dentro de la ventana y el viejo muere solo al vencer.
## Próximos pasos [#próximos-pasos]
* La superficie de control completa, con `connect_sessions`, syncs y salud: [API de control](/docs/api-control).
* El evento `connect_session.consumed` en su contexto, con el ciclo de vida del enlace: [enlace hosted](/docs/operar/enlace-hosted).
* Correlacionar un `request_id` con su ejecución: [la bitácora](/docs/conceptos/bitacora).
* Qué responde Connect cuando algo se rechaza y qué hacer en cada caso: [errores](/docs/operar/errores).
---
# BancoEstado Empresas
> Saldos y movimientos de cuenta corriente del portal de BancoEstado Empresas, servidos al instante desde la caché de Connect.
`banco_estado` sincroniza dos alcances del portal de BancoEstado Empresas hacia el plano persistido de Connect: los saldos de cuenta corriente y los movimientos de la cartola. El login usa los mismos tres datos con que se entra a la banca en línea; dos tools de consulta leen esa caché al instante. Cada llamada identifica a la empresa por el `conn_…` de su conexión (en REST, el header `X-Connect-Connection`).
## Una sola sesión activa por usuario [#una-sola-sesión-activa-por-usuario]
Es la particularidad que gobierna todo lo demás, y conviene saberla antes de conectar:
> BancoEstado admite **una sola sesión activa por usuario**. Mientras corre una sincronización no vas a poder entrar al portal, y si estás dentro del portal la sincronización va a fallar. Conviene dejarla agendada fuera del horario en que usas la banca en línea.
Cerrar el navegador no libera la sesión: el portal la mantiene abierta unos minutos más. Por eso Connect cierra sesión siempre al terminar, y por eso una sincronización que se cruza con una persona dentro del portal devuelve `409 connection_session_pending` en vez de un error genérico.
## Qué necesitas para conectar [#qué-necesitas-para-conectar]
La **clave de banca en línea de empresas** entra por el [enlace de conexión](/docs/empezar/conectar), en tres campos:
| Campo | Qué es |
| ----------------------- | ---------------------------------------------------- |
| RUT empresa/institución | La empresa que esta conexión va a leer. |
| RUT usuario | El RUT de la persona que inicia sesión en el portal. |
| Clave | Su clave de banca en línea de BancoEstado Empresas. |
Que los dos RUT vayan separados tiene una razón: quien entra al portal casi nunca es la empresa, y un mismo usuario puede acceder a varias. Connect usa ese acceso solo para leer, revocarlo desde el dashboard es inmediato, y la clave entra por el enlace de un solo uso, directo de quien la tiene, sin cruzar tu código.
El segundo factor de BancoEstado (BE Pass, BE Face) autoriza **operaciones**, como transferencias y nóminas, no la consulta: una conexión de solo lectura no lo dispara.
## Los alcances [#los-alcances]
| Alcance | Qué trae | Tool que lo lee |
| ------------- | -------------------------------------------------------------------------------------------- | ------------------------------------ |
| `saldos` | El saldo contable y el disponible de cada cuenta, más sus retenciones, como una foto por día | `banco_estado.saldos.consultar` |
| `movimientos` | Los movimientos de la cartola del período, con documento, glosa, oficina y saldo arrastrado | `banco_estado.movimientos.consultar` |
Cada conexión habilita sus alcances desde el dashboard; consultar uno apagado responde `403 alcance_not_enabled`.
## Sincronizar [#sincronizar]
Los dos alcances en la misma llamada comparten un único login contra el banco. Aquí eso no es una optimización: como el banco admite una sola sesión por usuario, un segundo login dentro del mismo trabajo sería un rechazo.
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_estado.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08","alcances":["saldos","movimientos"]}}'
```
Respuesta (recortada):
```json
{
"data": {
"periodo": "2026-08",
"results": [
{ "alcance": "saldos", "status": "ok", "recordsSynced": 2 },
{ "alcance": "movimientos", "status": "ok", "recordsSynced": 137 }
]
},
"meta": { "request_id": "req_...", "tool_id": "banco_estado.conexion.sincronizar", "plane": "read" }
}
```
Con un período ya cerrado, `saldos` devuelve cero con un `detalle` que lo explica: la foto del saldo existe solo para el período corriente, y ese cero no significa que la cuenta esté vacía. `movimientos` cubre cualquier período sincronizado.
El sync también se puede encolar por la [API de control](/docs/api-control) para no quedarse esperando el login; el resultado llega por [webhook](/docs/operar/webhooks).
## Verdades operativas [#verdades-operativas]
### Los saldos son una foto por día [#los-saldos-son-una-foto-por-día]
Cada fila de `saldos.consultar` es el saldo de una cuenta en un día (`observedDay`), con la hora en que el banco la reportó. Sincronizar dos veces el mismo día actualiza esa foto en vez de agregar otra. Las retenciones vienen desglosadas (a un día, a dos días, y el resto agrupado), más el total.
### Los montos son números, con la convención del libro del banco [#los-montos-son-números-con-la-convención-del-libro-del-banco]
`monto` es la magnitud **sin signo**; `type` dice si la plata sale (`credit`) o entra (`debit`) según el libro del banco, que invierte lo que una cartola muestra; y `display` trae el monto ya formateado a la chilena, con su signo. Es el mismo contrato de los otros tres bancos de Connect, así que un consumidor que ya lee uno lee este sin cambios.
`saldo` es distinto: es el saldo arrastrado tras el movimiento, o sea un balance. No lleva `type` y conserva su propio signo, porque un sobregiro es negativo.
### Dos cartolas del banco, una sola caché [#dos-cartolas-del-banco-una-sola-caché]
El banco sirve el mes en curso y los meses cerrados por dos rutas distintas de su portal. Eso es interno del conector: las dos escriben la misma tabla con la misma identidad por movimiento, así que un mes que aparezca en ambas no se duplica y los resultados se pueden sumar sin miedo. El campo `origen` de cada fila dice por cuál se trajo.
### El período se pide, no se guarda en la identidad [#el-período-se-pide-no-se-guarda-en-la-identidad]
Pedir el mismo movimiento con otro período no crea una fila nueva: el período es cómo lo pediste, no una propiedad del movimiento. Por eso re-sincronizar es seguro y nunca infla los totales.
## Errores que vas a ver [#errores-que-vas-a-ver]
| Código | Qué significa | Qué hacer |
| ------------------------------------ | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `409 connection_session_pending` | El banco no tiene una sesión viva para esta conexión, o ya hay una sesión abierta para ese usuario. | Si hay alguien dentro del portal, que cierre sesión (o espera unos minutos a que expire) y reintenta. Es la contracara de la sesión única. |
| `428 connection_credential_required` | La conexión no tiene una credencial viva: nunca se vinculó, venció o fue revocada. | Emite un enlace de reconexión y pide la clave de nuevo. No reintentes con la credencial anterior: BancoEstado bloquea la cuenta tras varios rechazos. |
| `409 connection_busy` | Otra operación tiene tomado el candado de esta conexión. | Espera unos segundos y reintenta: el candado se suelta solo. |
| `409 connection_sync_in_progress` | Ya corre una sincronización de esa conexión para ese período. | Espera a que termine, o consulta directamente: puede que ya haya datos. |
| `403 alcance_not_enabled` | La conexión no tiene habilitado el alcance que la tool pide. | Habilítalo en la conexión desde el dashboard, o quítalo del input de la sincronización. |
Cada código, con su envelope completo, está en el [catálogo de errores](/docs/operar/errores).
## Próximos pasos [#próximos-pasos]
* Las cuatro tools con su contrato: [referencia de `banco_estado`](/docs/referencia/banco_estado).
* El modelo de dos pasos detrás de cada `.consultar`: [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
* Que cada sync avise al terminar: [Webhooks](/docs/operar/webhooks).
---
# Banco Security
> Transferencias, nóminas de pago, saldos y movimientos del portal de Banco Security, servidos al instante desde la caché de Connect.
`banco_security` sincroniza cuatro alcances del portal de empresas de Banco Security hacia el plano persistido de Connect: transferencias TEF, nóminas de pago masivas, saldos y movimientos de cuenta corriente. El login usa la clave de la empresa; cinco tools de consulta leen esa caché al instante. Cada llamada identifica a la empresa por el `conn_…` de su conexión (en REST, el header `X-Connect-Connection`).
## Qué necesitas para conectar [#qué-necesitas-para-conectar]
La **Clave de banca en línea** del portal entra por el [enlace de conexión](/docs/empezar/conectar), en tres campos:
| Campo | Qué es |
| ----------------- | ---------------------------------------------------- |
| RUT de acceso | El RUT de la persona que inicia sesión en el portal. |
| Clave | Su clave de banca en línea de Banco Security. |
| RUT de la empresa | La empresa que esta conexión va a leer. |
Que los dos RUT vayan separados tiene una razón: quien entra al portal casi nunca es la empresa. Connect usa ese acceso solo para leer y revocarlo desde el dashboard es inmediato; la clave entra por el enlace de un solo uso, directo de quien la tiene, sin cruzar tu código.
## Los alcances [#los-alcances]
| Alcance | Qué trae | Tool que lo lee |
| ---------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `transferencias` | Las TEF enviadas y recibidas del período, con contraparte, número de transacción y quién la creó y aprobó | `banco_security.transferencias.consultar` |
| `nominas` | Las nóminas de pago masivas: cabeceras por un lado, líneas de pago por otro | `banco_security.nominas.consultar` y `banco_security.nomina_pagos.consultar` |
| `saldos` | Los tres saldos de cada cuenta (contable, disponible y provisorio), como una foto por día | `banco_security.saldos.consultar` |
| `movimientos` | Los movimientos de cuenta corriente de cualquier período sincronizado | `banco_security.movimientos.consultar` |
Cada conexión habilita sus alcances desde el dashboard; consultar uno apagado responde `403 alcance_not_enabled`.
## Sincronizar [#sincronizar]
Varios alcances en la misma llamada comparten un único login contra el banco (cerca de 90 segundos); el conector los ejecuta en el orden interno que la sesión del portal exige, así que no importa cómo los ordenes tú.
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","alcances":["transferencias","saldos","movimientos"]}}'
```
Respuesta (recortada):
```json
{
"data": {
"periodo": "2026-07",
"results": [
{ "alcance": "transferencias", "status": "ok", "recordsSynced": 18 },
{
"alcance": "saldos",
"status": "ok",
"recordsSynced": 0,
"detalle": "Los saldos son una foto del momento, no del período, así que solo se sincronizan en el período corriente. Este cero NO significa que la cuenta no tenga saldo: pediste 2026-07; pide 2026-08 para obtenerlo."
},
{ "alcance": "movimientos", "status": "ok", "recordsSynced": 214 }
]
},
"meta": { "request_id": "req_...", "tool_id": "banco_security.conexion.sincronizar", "plane": "read" }
}
```
Con un período ya cerrado, `saldos` devuelve cero con su `detalle`: la foto del saldo existe solo para el período corriente. Los otros alcances cubren cualquier período.
El sync también se puede encolar por la [API de control](/docs/api-control) para no quedarse esperando el login; el resultado llega por [webhook](/docs/operar/webhooks).
## Verdades operativas [#verdades-operativas]
### Las nóminas van en dos niveles [#las-nóminas-van-en-dos-niveles]
`banco_security.nominas.consultar` devuelve solo las cabeceras, cada una con su `idNomina`, su monto total y `numRegistros`, la cantidad de líneas que contiene. Las líneas de pago (a quién, cuánto, a qué cuenta, con qué glosa y si fue rechazado) se piden aparte con `banco_security.nomina_pagos.consultar`, pasando ese `idNomina`. Separarlas no es capricho: hay nóminas de más de mil líneas, y `numRegistros` existe para que dimensiones antes de pedir el detalle.
### Dos cartolas del banco, una sola caché [#dos-cartolas-del-banco-una-sola-caché]
El banco sirve el mes en curso y los meses cerrados por dos rutas distintas de su portal. Eso es interno del conector: el período que pides elige la ruta, las dos escriben la misma tabla con la misma identidad por movimiento, y un mes que aparezca en ambas no se duplica. Los resultados de `movimientos.consultar` se pueden sumar sin miedo.
### El número de transacción es texto [#el-número-de-transacción-es-texto]
`numeroTransaccion` viene como string a propósito: el banco emite números de 14 dígitos, que no caben en un entero de 32 bits. Trátalo como identificador y no lo conviertas a número.
### Los montos son números, con la convención del libro del banco [#los-montos-son-números-con-la-convención-del-libro-del-banco]
`monto` es la magnitud sin signo; `type` dice si la plata entra (`debit`) o sale (`credit`) según el libro del banco, que invierte lo que una cartola muestra; y `display` trae el monto ya formateado a la chilena, signo incluido. Una nómina siempre es un desembolso, así que sus filas salen `credit`. Los saldos no llevan `type`: conservan su propio signo, y la misma empresa puede tener cuentas en pesos y en dólares, cada una con su `currency`.
## Errores que vas a ver [#errores-que-vas-a-ver]
| Código | Qué significa | Qué hacer |
| ------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `428 connection_credential_required` | La conexión no tiene una credencial viva: nunca se vinculó, venció o fue revocada. | Emite un enlace de reconexión y pide la clave de nuevo. No reintentes con la credencial anterior. |
| `409 connection_busy` | Otra operación tiene tomado el candado de esta conexión. | Espera unos segundos y reintenta: el candado se suelta solo. |
| `409 connection_sync_in_progress` | Ya corre una sincronización de esa conexión para ese período. | Espera a que termine, o consulta directamente: puede que ya haya datos. |
| `403 alcance_not_enabled` | La conexión no tiene habilitado el alcance que la tool pide. | Habilítalo en la conexión desde el dashboard, o quítalo del input de la sincronización. |
Cada código, con su envelope completo, está en el [catálogo de errores](/docs/operar/errores).
## Próximos pasos [#próximos-pasos]
* Las siete tools con su contrato: [referencia de `banco_security`](/docs/referencia/banco_security).
* El modelo de dos pasos detrás de cada `.consultar`: [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
* Que cada sync avise al terminar: [Webhooks](/docs/operar/webhooks).
---
# Banco de Chile Empresas
> Saldos, movimientos y cartolas mensuales del Portal Empresas de Banco de Chile, sincronizados a la caché de Connect. El login es HTTP puro: no hay desafío de navegador que resolver.
Con la clave del portal, `bch_empresas` sincroniza saldos, movimientos y cartolas mensuales del Portal Empresas de Banco de Chile hacia el plano persistido de Connect, y tres tools de consulta los leen de esa caché sin esperar al banco. El `conn_…` de la conexión dice qué empresa lees y viaja en toda llamada (en REST, el header `X-Connect-Connection`).
A diferencia de BICE Empresas, el login de Banco de Chile no exige resolver ningún desafío de navegador: todo el camino, desde el login hasta el logout, corre por HTTP puro. Es el conector bancario más simple de los cuatro que ofrece Connect, no el más caro.
## Qué necesitas para conectar [#qué-necesitas-para-conectar]
En el [enlace de conexión](/docs/empezar/conectar) la persona entrega la **Clave de banca en línea** del portal, en tres campos:
| Campo | Qué es |
| ----------------- | ---------------------------------------------------- |
| RUT de acceso | El RUT de la persona que inicia sesión en el portal. |
| Clave | Su clave de banca en línea de Banco de Chile. |
| RUT de la empresa | La empresa que esta conexión va a leer. |
El acceso solo lee, y desde el dashboard se revoca en el acto. La clave no pasa por tu código: la entrega quien la tiene, en un enlace que sirve una sola vez. Si el acceso ve más de una empresa en el portal, la conexión no se puede crear: Connect todavía no tiene un selector para elegir cuál, así que la clave que uses tiene que ver una sola empresa.
## Los alcances [#los-alcances]
| Alcance | Qué trae | Tool que lo lee |
| ------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `saldos` | El saldo disponible y la línea de crédito de cada cuenta, como una foto por día | `bch_empresas.saldos.consultar` |
| `movimientos` | Los movimientos del período, con descripción, canal y saldo arrastrado | `bch_empresas.movimientos.consultar` |
| `cartolas` | El extracto mensual ya emitido por el banco, con su número de cartola y los saldos de apertura y cierre | `bch_empresas.cartolas.consultar` |
Qué alcances quedan habilitados se decide por conexión, desde el dashboard. El gate de alcances rechaza la llamada **entera** si uno solo de los alcances pedidos no está habilitado: pedir `["saldos","movimientos","cartolas"]` sobre una conexión que no habilitó `cartolas` no sincroniza ni saldos ni movimientos, devuelve `403 alcance_not_enabled` completo. Una conexión creada **antes** del 2026-08-11 puede no tener `cartolas` habilitado, porque hasta esa fecha el alta no lo pre-marcaba: su parser MT940 todavía no se había corrido contra el banco real. Se habilita desde `/ajustes` como cualquier otro alcance.
## Sincronizar [#sincronizar]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/bch_empresas.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08","alcances":["saldos","movimientos"]}}'
```
Respuesta (recortada):
```json
{
"data": {
"periodo": "2026-08",
"results": [
{ "alcance": "saldos", "status": "ok", "recordsSynced": 2 },
{ "alcance": "movimientos", "status": "ok", "recordsSynced": 41 }
]
},
"meta": { "request_id": "req_...", "tool_id": "bch_empresas.conexion.sincronizar", "plane": "read" }
}
```
Un solo login del Portal Empresas cubre los alcances pedidos, y el logout corre al terminar. Como en los demás bancos, `saldos` es una foto del momento: en un período que no es el corriente devuelve cero con su explicación en `detalle`. Y el login no exige esperarlo en línea: el trabajo se encola por la [API de control](/docs/api-control). Para incluir `cartolas` en la misma llamada, habilítalo antes en la conexión (dashboard → alcances); de lo contrario pedirlo tumba la sincronización entera, no solo ese alcance.
## Verdades operativas [#verdades-operativas]
### La ventana es de 45 días por consulta, y el rechazo es HTTP 300 [#la-ventana-es-de-45-días-por-consulta-y-el-rechazo-es-http-300]
Cada consulta de movimientos al banco acepta como máximo 45 días de rango. Una ventana más ancha no vuelve con un código 4xx habitual: vuelve con **HTTP 300**, un estado de redirección que un cliente HTTP común seguiría en silencio como si fuera un enlace válido. Connect intercepta ese 300 antes de seguirlo y lo traduce a `upstream_unexpected_response` (502, no reintentable): reintentar la misma ventana da el mismo resultado, así que no vale la pena. `bch_empresas.conexion.sincronizar` pide siempre un mes calendario (máximo 31 días), así que tu sync nunca choca con este límite por sí solo; importa si ves ese código en la bitácora y necesitas saber por qué no es transitorio.
### La retención es de \~6 años [#la-retención-es-de-6-años]
El banco acepta consultas de hasta aproximadamente 6 años atrás: una ventana de 30 días ubicada 1, 3 o 5 años atrás responde HTTP 200, aunque en la medición volvió sin movimientos, así que ese 200 vacío no distingue "no hubo movimientos en esa ventana" de "la cuenta no existía todavía". La misma ventana ubicada 7 años atrás el banco la rechaza con el mismo HTTP 300 que usa para una ventana demasiado ancha: recién ahí queda claro que la fecha cayó fuera de lo que el banco conserva. Si necesitas historia más allá de esos \~6 años, no hay forma de conseguirla: el corte lo pone el banco, no Connect.
### El `x-xsrf-token` rota a mitad de sesión [#el-x-xsrf-token-rota-a-mitad-de-sesión]
El header `x-xsrf-token` que protege cada POST del portal no queda fijo durante todo el login: el banco lo reemite en ciertas respuestas, y el valor cambia. Connect relee la cookie del jar inmediatamente antes de cada POST, nunca cachea el valor del login. Un cliente que lo cachee del primer login funciona en las primeras llamadas y después empieza a fallar de forma intermitente, justo cuando el banco rota el token, un patrón difícil de reproducir porque depende del momento exacto de la rotación.
### El flag de paginación es `"Y"`/`"N"`, no `"S"` [#el-flag-de-paginación-es-yn-no-s]
El indicador de "hay más páginas" que devuelve el banco (`pagina[].masPaginas`) usa las letras en inglés, `"Y"` o `"N"`, nunca la `"S"` de "sí" que uno esperaría en un portal en castellano. Un cliente que compare contra `"S"` nunca la encuentra, corta después de la primera página y trunca en silencio: parece que la cuenta tiene pocos movimientos cuando en realidad hay más sin traer. Connect no usa ese flag para decidir si sigue paginando: el corte real es que el índice de término llegue al total de registros, el mismo criterio sin importar qué letra mande el banco.
### La cartola emitida es mensual y llega en MT940 [#la-cartola-emitida-es-mensual-y-llega-en-mt940]
El extracto que el banco emite una vez al mes (la cartola) no es el mismo feed que `movimientos`: es un objeto propio, con su propio saldo de apertura y de cierre. El único formato en que el banco la entrega con esos dos datos es **MT940**, el estándar SWIFT de mensajería bancaria: trae el número de cartola (tag 28C) y los saldos de apertura y cierre (tags 60/62), que ningún otro formato del portal declara en su encabezado. El único formato alternativo medido, el XLS, no tiene esos dos campos y sus columnas para identificar cada línea (número de documento, transacción, caja) vienen en cero en toda la muestra medida; el PDF y el TXT no se llegaron a inspeccionar. `bch_empresas.cartolas.consultar` ya devuelve el número de cartola y los saldos parseados desde MT940: no hace falta interpretarlo por tu cuenta.
### El logout no invalida la sesión del lado del banco [#el-logout-no-invalida-la-sesión-del-lado-del-banco]
Está medido, más de una vez: guardando la cookie de sesión antes de que Connect cierre la conexión con el banco y reinyectándola después, el banco sigue respondiendo autenticado con HTTP 200. Connect igual llama al logout en cada sincronización (son tres peticiones baratas y es lo que hace el propio portal), pero eso limpia el estado del lado de Connect, no el del banco: ninguna integración debería asumir que, tras un logout, la sesión anterior quedó inutilizable. Como Connect nunca persiste esa sesión entre sincronizaciones, el residual queda enteramente del lado del banco, que la expira sola pasado un tiempo de inactividad.
## Errores que vas a ver [#errores-que-vas-a-ver]
| Código | Qué significa | Qué hacer |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `428 connection_credential_required` | La conexión no tiene una credencial viva: nunca se vinculó, venció o fue revocada. | Emite un enlace de reconexión y pide la clave de nuevo. No reintentes con la credencial anterior. |
| `409 connection_busy` | Otra operación tiene tomado el candado de esta conexión. | Espera unos segundos y reintenta: el candado se suelta solo. |
| `409 connection_sync_in_progress` | Ya corre una sincronización de esa conexión para ese período. | Espera a que termine, o consulta directamente: puede que ya haya datos. |
| `403 alcance_not_enabled` | La conexión no tiene habilitado el alcance que la tool pide. | Habilítalo en la conexión desde el dashboard, o quítalo del input de la sincronización. |
| `409 connection_identity_mismatch` | La empresa que ve el banco no coincide con la que declara la conexión: la credencial ve más de una empresa en el portal (detectado al verificar), o una fila sincronizada trajo el RUT de otra empresa. | No reintentes sin revisar: puede ser una credencial multiempresa o una rotación que cambió de empresa. Repórtalo con el `request_id`. |
| `502 upstream_unexpected_response` | El banco rechazó la ventana de fechas consultada (HTTP 300). No es un problema transitorio. | Repórtalo con el `request_id` si el mismo período sigue fallando tras reintentar. |
| `502 upstream_error` | El banco (o el camino hasta él) falló de forma transitoria. | Reintenta más tarde. Si persiste, el problema está del lado del banco. |
El envelope de cada código está en el [catálogo de errores](/docs/operar/errores).
## Próximos pasos [#próximos-pasos]
* Las cinco tools, con contrato completo: [referencia de `bch_empresas`](/docs/referencia/bch_empresas).
* La separación entre escribir la caché y leerla: [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
* Saber de cada sync sin preguntar: [Webhooks](/docs/operar/webhooks).
---
# BCI PyME
> Conecta el portal de empresas de BCI y obtén saldos y movimientos en una caché que se consulta al instante, sin tocar al banco.
El conector `bci_pyme` entra al portal Banco en Línea de BCI con la clave de la empresa, guarda saldos y movimientos en el plano persistido de Connect, y los sirve con dos tools de consulta que responden al instante. La conexión es la empresa: toda llamada lleva su `conn_…` (en REST, el header `X-Connect-Connection`).
## Qué necesitas para conectar [#qué-necesitas-para-conectar]
El [enlace de conexión](/docs/empezar/conectar) pide la **Clave de internet** del portal, en tres campos:
| Campo | Qué es |
| ----------------- | ----------------------------------------------------- |
| RUT de acceso | El RUT de la persona que inicia sesión en el portal. |
| Clave | Su clave de internet de BCI. |
| RUT de la empresa | La empresa (el convenio) que esta conexión va a leer. |
El RUT de acceso y el RUT de la empresa son datos distintos a propósito: un mismo acceso puede administrar varias empresas. Cuando el portal ofrece más de un convenio, el conector elige el que coincide con el RUT de la empresa; y cuando ofrece uno solo, verifica igual que sea el configurado. Si no coinciden, la verificación falla pidiendo revisar ese campo, nunca sincroniza una empresa distinta en silencio.
El acceso es de solo lectura y se revoca al instante desde el dashboard. La clave la entrega quien la tiene, por el enlace de un solo uso: no pasa por tu código ni por el chat de un agente.
## Los alcances [#los-alcances]
| Alcance | Qué trae | Tool que lo lee |
| ------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| `saldos` | Los cuatro saldos de cada cuenta (contable, disponible, contable a las 9AM y retención), como una foto por día | `bci_pyme.saldos.consultar` |
| `movimientos` | Los movimientos del período, con contraparte, categoría, mnemónico y saldo arrastrado | `bci_pyme.movimientos.consultar` |
Los alcances se habilitan por conexión, desde el dashboard. Consultar un alcance apagado responde `403 alcance_not_enabled`.
## Sincronizar [#sincronizar]
Pide los alcances que necesites en la misma llamada: es un solo login contra el banco, y ese login toma cerca de 90 segundos.
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/bci_pyme.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","alcances":["saldos","movimientos"]}}'
```
Respuesta (recortada):
```json
{
"data": {
"periodo": "2026-07",
"results": [
{
"alcance": "saldos",
"status": "ok",
"recordsSynced": 0,
"detalle": "Los saldos son una foto del momento, no del período, así que solo se sincronizan en el período corriente. Este cero NO significa que la cuenta no tenga saldo: pediste 2026-07; pide 2026-08 para obtenerlo."
},
{ "alcance": "movimientos", "status": "ok", "recordsSynced": 2 }
]
},
"meta": { "request_id": "req_...", "tool_id": "bci_pyme.conexion.sincronizar", "plane": "read" }
}
```
Aquí se pidió julio siendo agosto el período corriente: `movimientos` sincroniza ese mes, y `saldos` devuelve cero con la explicación en `detalle`, porque los saldos son una foto del momento y solo se sincronizan pidiendo el período corriente. En el período corriente ambos alcances traen datos.
Si no quieres bloquear tu proceso durante el login, encola el trabajo por la [API de control](/docs/api-control) y entérate del resultado por [webhook](/docs/operar/webhooks).
## Verdades operativas [#verdades-operativas]
### El banco admite una sola sesión activa por usuario [#el-banco-admite-una-sola-sesión-activa-por-usuario]
Entrar al portal corta la sesión de quien esté adentro, en ambas direcciones: una sincronización puede expulsar a la persona que está mirando la cartola en el navegador, y esa persona, al entrar, puede botar una sincronización en curso. Cuando le pasa al conector, el fallo se clasifica como `upstream_error` (reintentable), con la instrucción de reintentar en unos minutos sin nadie más usando ese acceso: las credenciales siguen buenas y no hay que reconectar nada.
Dos prácticas evitan el choque: una credencial dedicada para Connect (que ninguna persona use para navegar el portal) y una cadencia programada en un horario sin actividad. La cadencia (diaria, cada 12 o cada 6 horas) queda anclada a la hora en que la activas, así que activarla de noche la deja corriendo de noche.
### El corte de 1000 movimientos por consulta [#el-corte-de-1000-movimientos-por-consulta]
El endpoint del banco entrega a lo más los 1000 movimientos más recientes de una cuenta para el mes pedido: es el mismo tope de su propia interfaz, que sobre esa cifra ofrece exportar por Excel. Cuando una cuenta lo alcanza, el conector persiste igual todo lo que trajo y marca ese alcance con `error: "movimientos_truncated"` en el resultado del sync. Al consultar, el campo `completo` traduce ese estado: `true` significa que el último sync de ese período trajo todo, `false` que faltan filas del mes, y `null` (cuando consultas sin filtro de período) significa que no se sabe.
### Débito y crédito van por el libro del banco [#débito-y-crédito-van-por-el-libro-del-banco]
`type` clasifica cada movimiento al revés de como se lee una cartola: un abono (letra `A` del banco) es plata que entra y sale como `debit`; un cargo (letra `C`) es plata que sale y va como `credit`. `monto` es la magnitud sin signo y `display` trae ese monto formateado a la chilena con el signo ya aplicado, listo para mostrar. Los saldos no llevan `type`: son balances y conservan su propio signo, así que un sobregiro es negativo.
### Las correcciones del banco entran como fila nueva [#las-correcciones-del-banco-entran-como-fila-nueva]
La identidad de un movimiento es una huella de su contenido, así que cuando el banco corrige un movimiento ya sincronizado, la corrección entra como una fila nueva en vez de reemplazar la anterior. Ante dos filas del mismo hecho, vale la de `ultimaLecturaEn` mayor: ese campo viaja en cada fila justamente para eso.
## Errores que vas a ver [#errores-que-vas-a-ver]
| Código | Qué significa | Qué hacer |
| ------------------------------------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `428 connection_credential_required` | La conexión no tiene una credencial viva: nunca se vinculó, venció o fue revocada. | Emite un enlace de reconexión y pide la clave de nuevo. No reintentes con la credencial anterior. |
| `409 connection_busy` | Otra operación tiene tomado el candado de esta conexión (por ejemplo un sync en curso). | Espera unos segundos y reintenta: el candado se suelta solo. |
| `409 connection_sync_in_progress` | Ya corre una sincronización de esa conexión para ese período. | Espera a que termine, o consulta directamente: puede que ya haya datos. |
| `502 upstream_error` | El banco falló o cerró la sesión a mitad del login (típico de la sesión única). | Reintenta más tarde, idealmente sin nadie más usando ese acceso. |
El detalle de cada código, con su envelope, está en el [catálogo de errores](/docs/operar/errores).
## Próximos pasos [#próximos-pasos]
* El contrato completo de las cuatro tools: [referencia de `bci_pyme`](/docs/referencia/bci_pyme).
* Por qué leer son dos pasos y qué implica: [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
* Enterarte de cada sync sin sondear: [Webhooks](/docs/operar/webhooks).
---
# BICE Empresas
> Saldos y movimientos de BICE Empresas, sincronizados a la caché de Connect. El desafío de navegador del portal lo resuelve la sincronización, no tú.
Con la clave de la empresa, `bice_empresas` sincroniza saldos y movimientos de BICE Empresas hacia el plano persistido de Connect, y dos tools de consulta los leen de esa caché sin esperar al banco. El `conn_…` de la conexión dice qué empresa lees y viaja en toda llamada (en REST, el header `X-Connect-Connection`).
BICE tiene una particularidad que ningún otro conector comparte: su portal protege el login con un desafío de navegador. Connect lo resuelve por ti, con las reglas que se explican abajo.
## Qué necesitas para conectar [#qué-necesitas-para-conectar]
En el [enlace de conexión](/docs/empezar/conectar) la persona entrega la **Clave de banca en línea** del portal, en tres campos:
| Campo | Qué es |
| ----------------- | ---------------------------------------------------- |
| RUT de acceso | El RUT de la persona que inicia sesión en el portal. |
| Clave | Su clave de banca en línea de BICE. |
| RUT de la empresa | La empresa que esta conexión va a leer. |
El acceso solo lee, y desde el dashboard se revoca en el acto. La clave no pasa por tu código: la entrega quien la tiene, en un enlace que sirve una sola vez.
## Los alcances [#los-alcances]
| Alcance | Qué trae | Tool que lo lee |
| ------------- | ---------------------------------------------------------------------------------------- | ------------------------------------- |
| `saldos` | El saldo contable y el disponible de cada cuenta, como una foto por día | `bice_empresas.saldos.consultar` |
| `movimientos` | Los movimientos de la cartola del período, con descripción, documento y saldo arrastrado | `bice_empresas.movimientos.consultar` |
Qué alcances quedan habilitados se decide por conexión, desde el dashboard.
## Sincronizar [#sincronizar]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/bice_empresas.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08","alcances":["saldos","movimientos"]}}'
```
Respuesta (recortada):
```json
{
"data": {
"periodo": "2026-08",
"results": [
{ "alcance": "saldos", "status": "ok", "recordsSynced": 1 },
{ "alcance": "movimientos", "status": "ok", "recordsSynced": 18, "cuentasConsultadas": 1 }
]
},
"meta": { "request_id": "req_...", "tool_id": "bice_empresas.conexion.sincronizar", "plane": "read" }
}
```
El primer login abre el desafío de navegador y puede tardar cerca de un minuto; las sincronizaciones siguientes reutilizan la sesión de portal vigente y son más rápidas. `cuentasConsultadas` acompaña a `movimientos` para que un cero sea interpretable: cero registros con una cuenta consultada es un dato del banco; cero registros con cero cuentas significa que ni siquiera se llegó a preguntar, y el campo `detalle` lo dice en palabras.
Como en los demás bancos, `saldos` es una foto del momento: en un período que no es el corriente devuelve cero con su explicación en `detalle`. Y el login no exige esperarlo en línea: el trabajo se encola por la [API de control](/docs/api-control).
## Verdades operativas [#verdades-operativas]
### El portal exige un desafío de navegador [#el-portal-exige-un-desafío-de-navegador]
El login de BICE corre detrás de un desafío anti-bot que no se puede resolver con HTTP puro. Cuando `conexion.sincronizar` necesita entrar, abre un navegador remoto solo para ese login y acuña una sesión de portal; desde ahí, todos los datos viajan por HTTP normal, igual que en los demás bancos. La sesión acuñada se reutiliza entre sincronizaciones mientras siga vigente (25 minutos por defecto), y se invalida sola cuando rotas la credencial.
### `connection_session_pending`: qué es y qué hacer [#connection_session_pending-qué-es-y-qué-hacer]
Solo la sincronización puede abrir ese navegador. Cualquier otra tool que necesite sesión de portal responde `409 connection_session_pending` cuando no hay una vigente, en vez de dejarte esperando un login largo. El código es reintentable: ejecuta `bice_empresas.conexion.sincronizar` (ella acuña la sesión) o espera la sincronización programada, y reintenta la llamada original.
### `conexion.verificar` no abre navegador [#conexionverificar-no-abre-navegador]
La verificación de credenciales es deliberadamente barata: con una sesión de portal vigente verifica de inmediato, y sin sesión devuelve `connection_session_pending` sin tocar al banco. Un `verificar` nunca dispara el login de navegador; si lo que quieres es acuñar la sesión, el camino es sincronizar.
### La cartola corre de fin de mes a fin de mes [#la-cartola-corre-de-fin-de-mes-a-fin-de-mes]
BICE no arma sus cartolas por mes calendario: la del período `2026-07` va del 30 de junio al 31 de julio. Por eso `movimientos.consultar` de un período puede incluir movimientos fechados en los últimos días del mes anterior; no es un duplicado, y la identidad por movimiento garantiza que el día compartido entre dos cartolas entre una sola vez. Y no todos los meses están disponibles: cuando pides un período que el banco no ofrece, el sync lo marca con `fueraDeVentana: true` y un `detalle` que pide no confundirlo con «no hubo movimientos».
## Errores que vas a ver [#errores-que-vas-a-ver]
| Código | Qué significa | Qué hacer |
| ------------------------------------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `409 connection_session_pending` | No hay una sesión de portal acuñada y esta llamada no puede acuñarla. | Ejecuta la sincronización de la conexión (ella acuña la sesión) o espera la programada, y reintenta. |
| `428 connection_credential_required` | La conexión no tiene una credencial viva: nunca se vinculó, venció o fue revocada. | Emite un enlace de reconexión y pide la clave de nuevo. No reintentes con la credencial anterior. |
| `409 connection_busy` | Otra operación tiene tomado el candado de esta conexión. | Espera unos segundos y reintenta: el candado se suelta solo. |
| `502 upstream_error` | El banco (o el camino hasta él) falló de forma transitoria. | Reintenta más tarde. Si persiste, el problema está del lado del banco. |
El envelope de cada código está en el [catálogo de errores](/docs/operar/errores).
## Próximos pasos [#próximos-pasos]
* Las cuatro tools, con contrato completo: [referencia de `bice_empresas`](/docs/referencia/bice_empresas).
* La separación entre escribir la caché y leerla: [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
* Saber de cada sync sin preguntar: [Webhooks](/docs/operar/webhooks).
---
# Sistemas
> Qué se puede conectar hoy, qué credencial pide cada sistema y qué conviene saber de cada portal antes de la primera sincronización.
Cada sistema conectable tiene su página con lo que el contrato por sí solo no cuenta: la credencial exacta que pide el enlace de conexión, qué trae cada alcance y las verdades operativas del portal de origen (sesiones, ventanas, topes). La ficha técnica de cada tool vive en la [referencia](/docs/referencia); estas páginas cuentan lo que necesitas saber antes de depender de los datos.
## Los sistemas conectables [#los-sistemas-conectables]
| Sistema | Credencial que pide | Alcances | Para saber |
| --------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [SII](/docs/sistemas/sii) | Clave tributaria (RUT y clave) | Compras y ventas (RCV), boletas electrónicas de venta, guías de despacho, boletas de honorarios | El SII conserva solo 6 meses del detalle de guías: esa historia se construye sincronizando seguido, nunca con un backfill. |
| [BCI PyME](/docs/sistemas/bci-pyme) | Clave de internet (RUT de acceso, clave y RUT de la empresa) | Saldos, movimientos | El banco admite una sola sesión activa por usuario: una sincronización y una persona dentro del portal se expulsan mutuamente. |
| [Banco Security](/docs/sistemas/banco-security) | Clave de banca en línea (RUT de acceso, clave y RUT de la empresa) | Transferencias, nóminas de pago, saldos, movimientos | Las nóminas van en dos niveles: cabeceras y líneas de pago, cruzadas por `idNomina`. |
| [BICE Empresas](/docs/sistemas/bice-empresas) | Clave de banca en línea (RUT de acceso, clave y RUT de la empresa) | Saldos, movimientos | El portal exige un desafío de navegador: el primer login tarda más que en otros bancos y solo la sincronización puede resolverlo. |
| [BancoEstado Empresas](/docs/sistemas/banco-estado) | Clave de banca en línea de empresas (RUT de la empresa, RUT de usuario y clave) | Saldos, movimientos | Una sola sesión activa por usuario: mientras corre una sincronización nadie puede entrar al portal, y al revés. Conviene agendarla fuera del horario de uso. |
| [Banco de Chile](/docs/sistemas/bch-empresas) | Clave de banca en línea (RUT de acceso, clave y RUT de la empresa) | Saldos, movimientos, cartolas emitidas | El login es HTTP puro, sin desafío de navegador, pero el banco solo acepta ventanas de hasta 45 días y rechaza una ventana inválida con HTTP 300. |
| [Previred](/docs/sistemas/previred) | Clave Previred (RUT de acceso, clave y RUT de la empresa) | Planillas pagadas, cotizaciones por trabajador, deuda, archivo para el F30-1, certificados de cotizaciones, empresas de la credencial | En Previred una empresa la administra un solo RUT de usuario, casi siempre el del contador: conviene pedirle un usuario secundario antes de conectar. |
## Los que no se conectan [#los-que-no-se-conectan]
Aparte de los conectables están [`indicadores`](/docs/sistemas/indicadores) (UF, dólar, euro, IPC y UTM del almacén de referencia) y `core` (utilidades como la hora del servidor, para probar el camino end-to-end). Son otra clase de sistema: datos de referencia idénticos para todas las organizaciones, habilitados para todos, gratis, y sus tools se llaman sin `connectionId`, porque no hay ninguna empresa detrás. `indicadores` tiene además un canal público que responde sin API key.
## El modelo común [#el-modelo-común]
Todos los conectables siguen el mismo modelo, en cuatro líneas:
1. Se conectan por un enlace de un solo uso: la persona que tiene la clave la entrega ahí y queda cifrada en el vault, sin pasar por tu código ([conecta tu primera empresa](/docs/empezar/conectar)).
2. La [conexión](/docs/conceptos/conexiones) resultante es la empresa (`conn_…`): viaja en cada llamada y es la unidad de facturación.
3. `.conexion.sincronizar` hace el login real contra el portal, por período (`AAAA-MM`), y escribe la caché de Connect.
4. Las tools `.consultar` leen de lo ya persistido, al instante y sin gastar un login ([Sincronizar y consultar](/docs/conceptos/sincronizar-consultar)).
---
# Indicadores
> UF, dólar, euro, IPC y UTM del almacén de referencia: tres tools gratis, sin conexión, y un canal público para probar sin cuenta.
El conector `indicadores` sirve los cinco indicadores económicos chilenos de uso diario: UF, dólar observado, euro, IPC (variación mensual, en porcentaje) y UTM. Es un conector de datos de referencia: los valores son idénticos para todas las organizaciones, no hay nada que conectar (sus tools se llaman sin `connectionId`) y su uso es gratis. Por eso el [quickstart](/docs/empezar/primera-llamada) parte por aquí: da un dato real de entrada, sin configurar nada.
Las tools leen el almacén de referencia de Connect, refrescado a diario; ninguna consulta una fuente externa en tiempo real (ver «De dónde salen los datos», al final).
## El último valor: `indicadores.valor.actual` [#el-último-valor-indicadoresvaloractual]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/indicadores.valor.actual/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{"input":{"codigo":"UF"}}'
```
```json
{
"data": { "codigo": "UF", "fecha": "2026-08-08", "valor": 39487.23, "unidad": "CLP", "antiguedadDias": -1 },
"meta": { "request_id": "req_...", "tool_id": "indicadores.valor.actual", "plane": "action" }
}
```
«Último disponible» no es lo mismo que «el de hoy», y `antiguedadDias` existe para que no tengas que compararlo contra tu propio reloj: son los días entre `fecha` y hoy en `America/Santiago`. Cero significa que el valor es de hoy; positivo, que está atrasado esa cantidad de días (la fuente no se ha actualizado); negativo, que está fechado en el futuro, lo que es normal en la UF y la UTM porque se publican por adelantado. En el ejemplo, `-1`: el valor devuelto es el de mañana. Compáralo contra tu propia tolerancia antes de calcular plata con el número.
## El valor a una fecha: `indicadores.valor.consultar` [#el-valor-a-una-fecha-indicadoresvalorconsultar]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/indicadores.valor.consultar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{"input":{"codigo":"DOLAR","fecha":"2026-08-02"}}'
```
```json
{
"data": {
"codigo": "DOLAR",
"fecha": "2026-07-31",
"valor": 943.18,
"unidad": "CLP",
"fechaSolicitada": "2026-08-02",
"esArrastre": true
},
"meta": { "request_id": "req_...", "tool_id": "indicadores.valor.consultar", "plane": "action" }
}
```
Esta tool responde con arrastre: si la fecha pedida no tiene dato propio (un fin de semana, un feriado, o una fuente que no se ha actualizado), devuelve el último valor anterior. Por eso `fecha` puede diferir de `fechaSolicitada`, y `esArrastre` te dice cuándo pasó: `false` significa que ese día tiene dato propio; `true`, que el valor viene de la `fecha` devuelta, que es anterior. En el ejemplo, el 2 de agosto de 2026 es domingo y el valor viene arrastrado del viernes. Pedir una fecha futura devuelve el último valor conocido con `esArrastre: true`, no un error. Un arrastre de un día sobre un fin de semana es normal; uno de tres semanas significa que la fuente está caída.
## La serie histórica: `indicadores.serie.consultar` [#la-serie-histórica-indicadoresserieconsultar]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/indicadores.serie.consultar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "Content-Type: application/json" \
-d '{"input":{"codigo":"UF","desde":"2026-08-01","hasta":"2026-08-03"}}'
```
```json
{
"data": {
"codigo": "UF",
"unidad": "CLP",
"valores": [
{ "fecha": "2026-08-01", "valor": 39461.87 },
{ "fecha": "2026-08-02", "valor": 39470.12 },
{ "fecha": "2026-08-03", "valor": 39478.4 }
]
},
"meta": { "request_id": "req_...", "tool_id": "indicadores.serie.consultar", "plane": "action" }
}
```
La serie sale en orden ascendente y acotada por `limite`, con tope duro de 1000 puntos. Los indicadores mensuales (IPC y UTM) traen un punto por mes, normalizado al primer día del mes.
## El canal público, sin API key [#el-canal-público-sin-api-key]
Para probar sin cuenta, los mismos datos responden en una ruta pública anónima:
```bash
curl https://connect.emisso.ai/public/v1/indicadores/UF
```
```json
{ "data": { "codigo": "UF", "fecha": "2026-08-08", "valor": 39487.23, "unidad": "CLP" } }
```
```bash
curl "https://connect.emisso.ai/public/v1/indicadores/UF/serie?desde=2026-08-01&hasta=2026-08-03"
```
```json
{
"data": {
"codigo": "UF",
"unidad": "CLP",
"valores": [
{ "fecha": "2026-08-01", "valor": 39461.87 },
{ "fecha": "2026-08-02", "valor": 39470.12 },
{ "fecha": "2026-08-03", "valor": 39478.4 }
]
}
}
```
La primera ruta acepta `?fecha=AAAA-MM-DD` para el valor a una fecha (con el mismo arrastre); la serie exige `desde` y `hasta`, y acepta `limite` (1 a 1000). El payload es la forma desnuda `{codigo, fecha, valor, unidad}`: los campos de frescura (`antiguedadDias`, `esArrastre`, `fechaSolicitada`) son de las tools autenticadas.
Está pensado para probar y para páginas que solo muestran el valor. Con API key, en cambio, cada llamada a estas tools queda en la bitácora como cualquier otra, sin costo. Las respuestas públicas se cachean en el CDN hasta una hora, así que tras el refresco diario el valor puede tardar ese margen en verse.
## De dónde salen los datos [#de-dónde-salen-los-datos]
Del sitio de estadísticas del Banco Central de Chile (`si3.bcentral.cl`). Un job programado los refresca una vez al día (a las 12:00 UTC) y guarda solo lo que cambió. El histórico viene de una semilla committeada en el repositorio, cinco años hacia atrás (valores desde 2021); desde ahí el job diario mantiene la serie al día, sin raspar la historia a demanda.
## Próximos pasos [#próximos-pasos]
* El recorrido completo de la primera llamada, con SDK y MCP: [Tu primera llamada](/docs/empezar/primera-llamada).
* Las tres tools y su contrato: [referencia de `indicadores`](/docs/referencia/indicadores).
* Cuando necesites datos de una empresa y no de referencia: [los sistemas conectables](/docs/sistemas).
---
# Notta
> Emite facturas y notas ante el SII desde tus agentes. La conexión se aprovisiona sola: no hay ninguna API key que crear ni pegar.
`notta` emite documentos tributarios electrónicos ante el SII: facturas afectas y exentas, y notas de débito y de crédito. Es el primer conector que **escribe en un sistema externo**, así que su tool de emisión es la primera del catálogo marcada como destructiva y la primera que pasa por el control de idempotencia.
Notta es producto de Emisso, y eso cambia el alta: en vez de pedirte una credencial que tendrías que ir a buscar a otro portal, Connect crea la empresa en Notta y su credencial por ti.
## Qué necesitas para conectar [#qué-necesitas-para-conectar]
Tres datos, y ninguno es un secreto:
| Campo | Qué es |
| --------------------- | -------------------------------------------------------------- |
| RUT de la empresa | La empresa que va a emitir. |
| Razón social | Su nombre legal. |
| Email del responsable | Quien completará el certificado digital y los folios en Notta. |
No hay ninguna API key que copiar. Connect llama a Notta, crea la organización, acuña la credencial y la guarda cifrada. Nadie la ve nunca, ni tú ni el agente.
Si el RUT ya tiene una cuenta en Notta, hay dos caminos. Cuando el email que declaras es dueño o administrador de esa cuenta, la conexión queda lista en el acto. Si no lo es, Notta le envía al dueño un enlace para autorizar la vinculación con un click, y la conexión queda esperando esa respuesta.
## Antes de la primera factura [#antes-de-la-primera-factura]
Emitir ante el SII exige dos cosas que solo puede conseguir una persona: el **certificado digital** de la empresa y los **folios** (CAF) del tipo de documento. Los dos se cargan en Notta, no en Connect.
Mientras falten, la conexión responde `connector_onboarding_required` y el mensaje dice qué falta. No es un error de tu integración: es el trámite que todavía no termina.
## Las tools [#las-tools]
| Tool | Qué hace |
| -------------------------- | --------------------------------------------------------- |
| `notta.dte.emitir` | Emite una factura o nota. Devuelve el folio asignado. |
| `notta.dte.consultar` | El estado de un documento, incluido el veredicto del SII. |
| `notta.dte.listar` | Los documentos recientes, con filtros. |
| `notta.dte.descargar` | El XML o el PDF, en base64, para uso programático. |
| `notta.dte.reenviar` | Reenvía el documento por correo al receptor. |
| `notta.conexion.verificar` | Prueba la credencial sin emitir nada. |
## Emitir [#emitir]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/notta.dte.emitir/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Idempotency-Key: 7f3c1b90-2d4e-4a11-9c6f-5b8e0a2d13c7" \
-H "Content-Type: application/json" \
-d '{"input":{"tipo_dte":33,"receptor_rut":"76900600-1","items":[{"nombre":"Asesoría de septiembre","cantidad":1,"precio":450000}]}}'
```
Respuesta (recortada):
```json
{
"data": {
"id": "dte_8sKq2mR4",
"folio": 267,
"tipo_dte": 33,
"estado": "queued",
"neto": 450000,
"iva": 85500,
"total": 535500
},
"meta": { "request_id": "req_...", "tool_id": "notta.dte.emitir", "plane": "action" }
}
```
El folio ya está asignado y es tuyo: ese número no se reutiliza. Lo que sigue (firmar, armar el sobre, subirlo al SII y esperar el veredicto) ocurre después, y por eso el estado dice `queued`. Para saber cómo terminó, consulta el documento.
## Emitir dos veces por accidente [#emitir-dos-veces-por-accidente]
Una factura duplicada no se deshace: se corrige con una nota de crédito, que es otro documento con otro folio. Por eso `emitir` es la única tool del catálogo que trata la repetición como parte del contrato.
Manda tu propia `Idempotency-Key` en el header. Si repites la llamada con la misma key y el mismo contenido, Connect devuelve la respuesta guardada sin volver a tocar Notta. Si repites la key con un contenido distinto, responde `idempotency_conflict`: esa key ya nombra otra operación. Y si el primer intento sigue en curso, responde `idempotency_in_progress`, que se reintenta a los segundos, nunca con una key nueva.
El SDK no reintenta esta llamada por su cuenta, y es deliberado.
## Consultar el resultado [#consultar-el-resultado]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/notta.dte.consultar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"id":"dte_8sKq2mR4"}}'
```
Respuesta (recortada):
```json
{
"data": {
"id": "dte_8sKq2mR4",
"folio": 267,
"tipo_dte": 33,
"estado": "aceptado",
"track_id": "1284455901",
"sii_glosa": "Envio Aceptado"
},
"meta": { "request_id": "req_...", "tool_id": "notta.dte.consultar", "plane": "action" }
}
```
El SII se toma su tiempo en responder, así que un documento recién emitido puede seguir en camino por varios minutos.
## Verdades operativas [#verdades-operativas]
**El ambiente lo decide la credencial, no la llamada.** Una conexión aprovisionada por Connect emite contra el ambiente de certificación del SII. El request no puede forzar producción: el dato viaja en la credencial guardada.
**El plan de Notta es de Notta.** La cuenta nace en el plan gratuito, con su cuota mensual de documentos. Al agotarla, la respuesta es `connector_plan_limit` y el mensaje lleva al lugar donde se cambia el plan, que es Notta y no Connect.
**Para que la factura llegue, hay dos caminos.** `descargar` te devuelve el PDF y el XML para que hagas lo tuyo con ellos. Si lo que quieres es que le lleguen al receptor, usa `reenviar`, o incluye su correo al emitir: Notta se lo manda cuando el SII acepta.
**No hay caché que consultar.** A diferencia del SII y los bancos, este conector no sincroniza nada al plano persistido: cada consulta pregunta en vivo, porque los documentos viven en Notta.
---
# Previred
> Planillas de cotizaciones pagadas, el desglose por trabajador, la deuda previsional y el archivo para el F30-1, sincronizados a la caché de Connect. Con el comprobante en PDF.
Con la clave de Previred, `previred` sincroniza hacia el plano persistido de Connect las cotizaciones previsionales de una empresa: las planillas que se pagaron, lo que cotizó cada trabajador, la deuda pendiente, el archivo con que se saca el Certificado F30-1 y el certificado oficial de cada trabajador. Cinco tools de consulta las leen de esa caché sin esperar al portal. El `conn_…` de la conexión dice qué empresa lees y viaja en toda llamada (en REST, el header `X-Connect-Connection`).
Previred es el único sistema del catálogo que además guarda **documentos**: el comprobante en PDF de cada planilla, que es la prueba de pago que piden la Dirección del Trabajo, las mutuales y cualquier auditoría, y el archivo para el F30-1.
## Qué necesitas para conectar [#qué-necesitas-para-conectar]
En el [enlace de conexión](/docs/empezar/conectar) la persona entrega la **Clave Previred**, en tres campos:
| Campo | Qué es |
| ----------------- | ---------------------------------------------- |
| RUT de acceso | El RUT de la persona que entra a previred.com. |
| Clave Previred | Su clave del portal. |
| RUT de la empresa | La empresa que esta conexión va a leer. |
El acceso solo lee, y desde el dashboard se revoca en el acto. La clave no pasa por tu código: la entrega quien la tiene, en un enlace que sirve una sola vez.
En Previred una empresa está asignada a **un** RUT de usuario, y con mucha frecuencia es el del contador, no el del dueño. Si conectas con una clave que no la administra, la sincronización responde `connection_credential_required` explicando exactamente eso.
La salida correcta **no** es quitarle la empresa al contador. El portal ofrece hacerlo, pero pide acertar el monto del último pago y le avisa a él por correo. Lo que corresponde es pedirle que cree un **usuario secundario** para la integración. Está en el portal, en Remuneraciones, bajo «Usuarios». Así cada uno conserva su acceso.
## Los alcances [#los-alcances]
| Alcance | Qué trae | Tool que lo lee |
| -------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| `planillas` | Una planilla por institución previsional, con su folio, el monto pagado y el enlace al comprobante en PDF | `previred.planillas.consultar` |
| `cotizaciones` | Lo que cotizó cada trabajador: renta imponible, monto y días trabajados | `previred.cotizaciones.consultar` |
| `deuda` | Las dos mitades de «¿estoy al día?»: declaraciones sin pago (DNP) y nóminas cuyo plazo corre y aún no se pagan | `previred.deuda.consultar` |
| `f301` | El archivo de 106 campos con que la Dirección del Trabajo emite el Certificado F30-1 | `previred.f301.consultar` |
| `certificados` | El certificado oficial de cotizaciones de cada trabajador, en PDF | `previred.certificados.consultar` |
| `empresas` | Las empresas que esta credencial administra en Previred | `previred.empresas.consultar` |
Qué alcances quedan habilitados se decide por conexión, desde el dashboard.
## Sincronizar [#sincronizar]
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_..." \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-06","alcances":["planillas","cotizaciones","deuda","f301","certificados","empresas"]}}'
```
Respuesta (recortada):
```json
{
"data": {
"periodo": "2026-06",
"results": [
{ "alcance": "planillas", "status": "ok", "recordsSynced": 4 },
{ "alcance": "cotizaciones", "status": "ok", "recordsSynced": 4 },
{ "alcance": "deuda", "status": "ok", "recordsSynced": 0, "detalle": "Previred no reporta deuda pendiente para esta empresa." },
{ "alcance": "f301", "status": "ok", "recordsSynced": 1 },
{ "alcance": "certificados", "status": "ok", "recordsSynced": 12 },
{ "alcance": "empresas", "status": "ok", "recordsSynced": 3 }
]
},
"meta": { "request_id": "req_...", "tool_id": "previred.conexion.sincronizar", "plane": "read" }
}
```
Un login basta para los seis alcances. El sync no exige esperarlo en línea: el trabajo se encola por la [API de control](/docs/api-control).
## Verdades operativas [#verdades-operativas]
### Un pago se abre en varias planillas [#un-pago-se-abre-en-varias-planillas]
Pagar las cotizaciones de un mes genera **una planilla por institución previsional**: la AFP, la Isapre o Fonasa, el seguro de cesantía, la mutual, la caja de compensación. Cada una tiene su propio folio y su propio monto. Ver cuatro filas para un período con un solo trabajador es lo normal, no una duplicación.
El folio es un identificador de 16 dígitos que emite Previred, y es la identidad de la planilla en Connect. Se guarda entero y opaco: no intentes descomponerlo, porque los folios reales no siguen un patrón parejo.
### El período vacío casi nunca significa «no cotizó» [#el-período-vacío-casi-nunca-significa-no-cotizó]
Las cotizaciones de un mes se declaran y pagan **entre el día 10 y el 13 del mes siguiente**, y la planilla aparece timbrada hasta 24 horas después del pago. Un período reciente sin planillas casi siempre significa «todavía no se paga», no «esta empresa no cotiza». Cuando el sync devuelve cero, el campo `detalle` lo dice con todas sus letras para que no se transmita como un hecho.
### Entre el 10 y el 13 no sincronizamos solos [#entre-el-10-y-el-13-no-sincronizamos-solos]
Esos cuatro días vencen las cotizaciones de todo el país y Previred concentra la carga de Chile entero. La sincronización programada se salta esa ventana a propósito. No se pierde nada: en esos días el período corriente todavía no tiene planillas pagadas, así que traería lo mismo que el día 9. Un sync manual sigue funcionando si lo necesitas.
### «¿Estoy al día?» son dos cosas distintas [#estoy-al-día-son-dos-cosas-distintas]
El alcance `deuda` trae dos tipos de fila y conviene no confundirlos.
`dnp` es una **declaración sin pago**: la empresa declaró lo que debía y no lo pagó. Viene con su
institución y sus cargos legales.
`por_pagar` es una **nómina cuyo plazo todavía corre**. Aparece apenas se genera la planilla y
desaparece cuando se paga. Ahí `institucion` dice `"Todas"` y solo llega `montoTotal`, porque el
portal muestra un total por nómina sin desglosarlo por institución. No es un dato incompleto: es lo
que la pantalla da.
El plazo vence el **día 13 del mes siguiente** al de las remuneraciones, y no se corre por feriado.
Una fila `por_pagar` del período anterior después de esa fecha ya es una deuda, aunque Previred aún no
la haya movido a DNP.
### El comprobante en PDF [#el-comprobante-en-pdf]
Cada planilla trae su comprobante, y `planillas.consultar` lo devuelve en `comprobanteUrl` como un **enlace firmado de vida corta**. Está pensado para seguirlo o descargarlo en el momento, no para guardarlo: caduca a los pocos minutos y no sirve como enlace compartible. El documento trae RUT, nombres y rentas de los trabajadores, y esa vida corta es deliberada.
Si `comprobanteUrl` viene en `null`, el PDF todavía no se ha descargado; la planilla es válida igual y el siguiente sync lo reintenta.
### El desglose por trabajador sale del comprobante [#el-desglose-por-trabajador-sale-del-comprobante]
Previred no publica en pantalla lo que cotizó cada persona: ese detalle vive dentro del PDF. Connect lo extrae al sincronizar, así que `cotizaciones.consultar` responde igual de rápido que las demás consultas.
Dos consecuencias que conviene conocer. Los **días trabajados** solo llegan cuando la institución los informa. Lo hace el Seguro Social y no las demás, así que en el resto quedan en `null`, que es la verdad y no un hueco. Y el `montoCotizacion` de cada fila cuadra con el total que su planilla declara: si alguna vez no cuadrara, la sincronización falla en vez de guardar un número dudoso.
### El archivo para el F30-1 [#el-archivo-para-el-f30-1]
El F30-1 es el Certificado de Cumplimiento de Obligaciones Laborales y Previsionales. Si tu empresa trabaja como contratista, tu mandante te lo va a pedir todos los meses antes de pagarte: la Ley 20.123 lo hace responsable solidario de tus cotizaciones, así que sin ese papel te retiene el pago.
La Dirección del Trabajo lo emite en línea a partir de un archivo que subes, y ese archivo lo genera Previred. El alcance `f301` lo trae en cada sync, y `f301.consultar` lo entrega en `archivoUrl` como enlace firmado de vida corta.
Connect entrega **el archivo con que se pide el certificado**, no el certificado. Ese último paso es de la Dirección del Trabajo y sigue siendo tuyo.
Tres cosas que conviene saber. El archivo cubre **una nómina**, así que un período con dos nóminas trae dos archivos. Se identifica por el **nombre** que la nómina tiene en Previred, y si dos del mismo período se llaman igual la sincronización falla y te lo dice: preferimos eso a mezclar dos archivos en uno. Y el contenido son 106 campos por trabajador, con RUT, nombres y rentas, que es la razón de que el enlace caduque a los pocos minutos.
### El certificado de cotizaciones [#el-certificado-de-cotizaciones]
Es el documento que Previred firma y que una persona pide cuando tiene que probar lo que se le cotizó: para un crédito, un trámite previsional o un juicio laboral. El alcance `certificados` guarda **uno vigente por trabajador**, y `certificados.consultar` lo entrega en `certificadoUrl`.
El certificado cubre la ventana más ancha que Previred permite, 36 meses, terminando en el período que sincronizaste; `periodoDesde` y `periodoHasta` lo dicen. No se elige el rango, y es a propósito: la fila es «el certificado vigente de esta persona», así que cada sincronización lo reemplaza por uno un mes más nuevo en vez de acumular uno por mes.
Previred emite el certificado de a un trabajador por vez, así que sincronizarlo cuesta **una ida y vuelta al portal por persona**. En una empresa de cincuenta, eso es cincuenta peticiones sobre un portal que ya es lento. Actívalo solo si vas a usar los documentos; si lo que necesitas son los montos, `cotizaciones.consultar` los tiene sin descargar nada.
Hay una segunda razón para no dejarlo prendido por costumbre. Previred a veces pone el servicio en modo diferido, que **envía un correo al empleador** con el documento adjunto. Connect no usa ese modo: cuando el portal lo declara, la sincronización de este alcance falla y te lo dice. Un conector de solo lectura no manda correos a nombre de nadie.
### Qué otras empresas ve esta clave [#qué-otras-empresas-ve-esta-clave]
En Previred una misma clave suele administrar varias empresas, sobre todo si es la del contador. El alcance `empresas` guarda ese listado y `empresas.consultar` lo devuelve, lo que responde de una vez la pregunta de onboarding: «¿qué más puedo conectar con esta clave?».
Traerlo es gratis. El listado llega en la misma respuesta con la que Connect entra al portal, así que pedirlo no agrega ni una petición y se puede dejar prendido sin pensarlo.
Una advertencia que importa: **ver una empresa en esa lista no es tenerla conectada.** Cada conexión de Connect es una empresa, y para leer los datos de otra hay que crearle su propia conexión con su propio `conn_…`. La lista dice qué claves alcanzan hasta dónde, no de dónde salen los datos que lees.
### Filtrar por trabajador [#filtrar-por-trabajador]
`cotizaciones.consultar` y `certificados.consultar` aceptan `rutTrabajador`. El RUT nunca queda legible en la base de Connect: se guarda cifrado y el filtro corre sobre un índice ciego. Mándalo sin puntos y con guion (`12345678-5`).
## Errores que vas a ver [#errores-que-vas-a-ver]
| Código | Qué significa | Qué hacer |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `428 connection_credential_required` | La clave no sirve, está bloqueada o vencida, o la empresa no está asignada a ese usuario de Previred. El mensaje distingue los casos. | Si es la clave, emite un enlace de reconexión. Si es la asignación, pide un usuario secundario a quien administre la empresa. |
| `409 connection_busy` | Otra operación tiene tomado el candado de esta conexión. | Espera unos segundos y reintenta: el candado se suelta solo. |
| `502 upstream_error` | El portal falló de forma transitoria, o devolvió algo que no era el documento esperado. | Reintenta más tarde. El portal es lento cuando genera comprobantes. |
El envelope de cada código está en el [catálogo de errores](/docs/operar/errores).
## Lo que este conector no hace [#lo-que-este-conector-no-hace]
**No paga.** Previred no cierra el pago dentro de su propio sitio: la orden se genera ahí y el dinero se mueve en el portal del banco, con las credenciales y el segundo factor del banco. Ningún software del mercado automatiza ese tramo, y Connect tampoco lo intenta.
**No carga nóminas.** Declarar una nómina es escribir en Previred, y este conector solo lee.
## Próximos pasos [#próximos-pasos]
* Las ocho tools, con contrato completo: [referencia de `previred`](/docs/referencia/previred).
* La separación entre escribir la caché y leerla: [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar).
* Saber de cada sync sin preguntar: [Webhooks](/docs/operar/webhooks).
---
# SII
> Una conexión al SII lee los cinco módulos tributarios de una empresa con un solo login, registro de compras y ventas, boletas, guías de despacho, boletas de honorarios y el XML de cada documento.
La credencial es la clave tributaria de la empresa, entregada por su representante mediante el
[enlace de conexión](/docs/empezar/conectar). Un solo login cubre los cinco alcances; la
sincronización es por período mensual (`AAAA-MM`).
## Los cinco alcances [#los-cinco-alcances]
Al conectar eliges qué módulos habilitar. `sincronizar` los trae todos en la misma sesión (un login,
un logout):
| Alcance | Qué trae | Se lee con |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `rcv` | El Registro de Compra-Venta: cada DTE recibido y emitido con montos, estados y eventos del receptor. La base del IVA mensual. | [`sii.rcv.consultar`](/docs/referencia/sii/rcv-consultar) |
| `boletas` | El resumen diario de boletas electrónicas de venta (tipos 39 y 41). | [`sii.boletas.consultar`](/docs/referencia/sii/boletas-consultar) |
| `guias` | Guías de despacho electrónicas emitidas (DTE 52). | [`sii.guias.consultar`](/docs/referencia/sii/guias-consultar) |
| `boletas_honorarios` | Boletas de honorarios emitidas y recibidas, con su retención. | [`sii.boletas_honorarios.consultar`](/docs/referencia/sii/boletas_honorarios-consultar) |
| `documentos` | El XML firmado de cada DTE, con sus ítems, giros, direcciones y forma de pago. El RCV dice qué documentos existen; este respaldo trae el documento. | [`sii.documentos.consultar`](/docs/referencia/sii/documentos-consultar) y [`sii.documentos.detallar`](/docs/referencia/sii/documentos-detallar) |
La mayoría de los alcances los sirve cualquiera de las dos claves, pero dos no, y en sentidos
opuestos: `boletas_honorarios` **solo lo sirve la clave de la empresa**, y `documentos` **solo la de
una persona que la representa**. Si el alcance que necesitas es uno de esos dos, la clave que pidas en
el enlace decide si la conexión va a servir.
El detalle de guías emitidas solo existe en el SII para los últimos 6 meses, en ventana rodante. Por
eso su historia no se puede reconstruir con un backfill: se construye sincronizando de forma continua
(la cadencia programada ya lo hace). Conecta la empresa antes de necesitar el histórico, porque lo que
la ventana ya botó no se puede recuperar.
## Cómo sincronizar el SII [#cómo-sincronizar-el-sii]
La cadencia `daily` mantiene el mes corriente al día. El cierre de un mes suele pedirse a demanda:
```bash
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input": {"periodo": "2026-07"}}'
```
Salida esperada (`200`, campo `data`, recortada):
```json
{
"periodo": "2026-07",
"results": [
{ "alcance": "rcv", "status": "ok", "recordsSynced": 214, "completo": true },
{ "alcance": "boletas", "status": "ok", "recordsSynced": 31 }
]
}
```
La respuesta trae un resultado por alcance (`ok`, `partial` o `failed`, con `recordsSynced` y
`completo`). Un alcance puede fallar sin tumbar a los demás: revisa `results[]` antes de dar el
período por cerrado.
## Errores que vas a ver, y qué hacer [#errores-que-vas-a-ver-y-qué-hacer]
| Código | Cuándo | Qué hacer |
| -------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_credential_required` | La empresa cambió su clave tributaria, o el SII la bloqueó. | Crea un enlace con `modo: "reconectar"` y pide a tu cliente que entregue la clave nueva. No reintentes con la anterior: el SII bloquea por intentos. |
| `connection_sync_in_progress` | Ya corre un sync de ese período, quizá el programado. | Espera y reintenta (es reintentable), o consulta directamente: puede que ya haya datos. |
| `alcance_not_enabled` | Pediste un alcance que la conexión no habilitó. | Habilítalo en [connect.emisso.ai/connections](https://connect.emisso.ai/connections) o quítalo del input. |
## Próximos pasos [#próximos-pasos]
* [`sii.rcv.consultar`](/docs/referencia/sii/rcv-consultar): la referencia completa del RCV, con ejemplo pareado.
* [Conecta tu primera empresa](/docs/empezar/conectar): el enlace hosted paso a paso.
* [Errores](/docs/operar/errores): cómo manejar el catálogo completo.
---
# Catálogo de errores
> Todos los códigos que la API puede emitir, con su status HTTP, su semántica de reintento y qué hacer con cada uno.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
Cada error llega con la misma forma, pensada para que un humano o un agente sepan qué hacer sin adivinar. Ejemplo real (falta el `connectionId` en una tool que lo exige):
```json
{
"error": {
"code": "validation_error",
"message": "'sii.rcv.consultar' requiere una conexión concreta.",
"suggested_fix": "Pasa el connectionId de la conexión de 'sii' (MCP: campo 'connectionId' de execute; REST: header 'X-Connect-Connection'). Lístalos con la tool 'conexiones.estado.consultar'.",
"request_id": "req_5tYw2nRk88Ma"
},
"meta": {
"request_id": "req_5tYw2nRk88Ma",
"tool_id": "sii.rcv.consultar",
"plane": "action",
"latency_ms": 12,
"audit_status": "recorded"
}
}
```
Reglas de manejo:
* Decide por `code`, nunca por el texto: `message` puede cambiar, los códigos no.
* `suggested_fix` está escrito para ejecutarse: si tu agente recibe el error crudo, ya sabe el paso siguiente.
* `request_id` correlaciona con la fila de la bitácora y es lo primero que pedirá soporte. Guárdalo en tus logs.
* Reintenta solo lo reintentable (columna de cada tabla). El SDK reintenta GET por su cuenta; los `execute` (POST) nunca se reintentan solos: reintentar a ciegas una acción regulada es la receta del doble pago.
## Petición [#petición]
| Código | HTTP | Reintentable | Qué significa | Qué hacer |
| ------------------------- | ---- | ------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `validation_error` | 400 | no | El input no cumple el esquema de la tool. | Revisa error.details: viene el detalle campo a campo. Corrige el input y reenvía. |
| `malformed_request` | 400 | no | El cuerpo no es JSON válido. | Verifica la serialización y el Content-Type antes de reenviar. |
| `unsupported_media_type` | 415 | no | El Content-Type no es application/json. | Envía el cuerpo con el header Content-Type: application/json. |
| `payload_too_large` | 413 | no | El cuerpo supera el límite de 256 KB. | Reduce el payload; si es una lista, pagina con cursor. |
| `idempotency_conflict` | 409 | no | Esa Idempotency-Key ya se usó con un cuerpo distinto. | Genera una clave nueva para una operación nueva; reutiliza la clave solo para reintentar la misma. |
| `idempotency_in_progress` | 409 | sí | Otro intento con esa misma Idempotency-Key todavía está corriendo. | Espera unos segundos y reintenta con la MISMA clave. Generar una nueva para la misma operación es lo que duplica el efecto. |
| `tool_not_found` | 404 | no | Ese id de tool no existe para tu organización. | Confirma el id con conexiones.sistemas.listar o search\_docs. También cubre conectores que tu organización no tiene habilitados. |
## Autenticación y permisos [#autenticación-y-permisos]
| Código | HTTP | Reintentable | Qué significa | Qué hacer |
| ------------------- | ---- | ------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `unauthorized` | 401 | no | Falta el bearer o el token no es válido. | Envía Authorization: Bearer connect\_sk\_… con una API key vigente. Si usas OAuth, renueva el access token. |
| `forbidden` | 403 | no | La identidad autenticó pero no tiene permiso para esta operación. | Confirma el rol y la organización de la credencial que estás usando. |
| `scope_not_granted` | 403 | no | La API key no tiene el scope que la tool exige. | Crea o edita la key en /api-keys con el scope requerido. Recuerda: scopes vacíos significan cero autoridad, no comodín. |
## Conexiones y sincronización [#conexiones-y-sincronización]
| Código | HTTP | Reintentable | Qué significa | Qué hacer |
| -------------------------------- | ---- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_ambiguous` | 409 | no | Hay más de una conexión de ese sistema y la llamada no dijo cuál. | Indica la conexión: header X-Connect-Connection en REST, campo connectionId en MCP y el SDK. |
| `connection_busy` | 409 | sí | Otra operación tiene tomada esta conexión en este momento. | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `connection_disabled` | 403 | no | La conexión existe pero está deshabilitada. | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | La conexión no tiene una credencial viva: nunca se vinculó, venció o fue revocada. | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_identity_mismatch` | 409 | no | La respuesta del sistema externo no corresponde a la identidad de esta conexión, o no trajo la prueba de identidad que Connect exige. | No reintentes: la respuesta es determinista. Revisa qué RUT tiene guardado la conexión y vuelve a vincular la credencial si no es el correcto. |
| `connected_account_required` | 428 | no | La tool exige una cuenta conectada del usuario y no existe. | Completa el flujo de autorización que indica el challenge de la respuesta. |
| `connection_session_pending` | 409 | sí | El sistema exige un desafío de navegador y todavía no hay una sesión de portal acuñada. | Ejecuta la sincronización de esa conexión (ella acuña la sesión) o espera la programada, y reintenta. |
| `connection_sync_in_progress` | 409 | sí | Ya corre una sincronización de esa conexión para ese período. | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `connector_onboarding_required` | 428 | no | El sistema externo exige completar su configuración antes de operar. En Notta, por ejemplo, el certificado digital y los folios. | Lee el mensaje: dice qué falta. Se completa en el portal de ese sistema, no en Connect, y después la llamada funciona sin cambios. |
| `too_many_pending` | 429 | sí | La cola de sincronizaciones pendientes de la conexión está llena. | Deja terminar los trabajos en curso antes de encolar más. |
| `sync_job_not_found` | 404 | no | Ese id de trabajo de sincronización no existe en tu organización. | Usa los job\_ids que devolvió el POST que encoló el trabajo. |
| `alcance_not_enabled` | 403 | no | La conexión no tiene habilitado el módulo de datos que la tool pide. | Habilita el alcance en /connections o quítalo del input de la sincronización. |
| `alcance_scheme_unsupported` | 422 | no | El módulo de datos pedido no está disponible con el tipo de acceso de esta conexión. | Crea una conexión con el otro acceso, o quita ese módulo del input de la sincronización. |
## Límites y plan [#límites-y-plan]
| Código | HTTP | Reintentable | Qué significa | Qué hacer |
| ------------------------- | ---- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `rate_limited` | 429 | sí | Superaste un límite de tasa. | Espera y reintenta con backoff exponencial. |
| `quota_exceeded` | 429 | no | Esta conexión alcanzó su cupo de llamadas del período. Tus demás conexiones siguen funcionando. | Espera el período siguiente o pide una ampliación. Subir de plan no cambia el cupo, y reintentar tampoco. |
| `feature_not_in_plan` | 403 | no | Esa función no está incluida en tu plan. | Revisa /billing para habilitarla. |
| `billing_past_due` | 402 | no | La organización tiene un pago vencido. Se pausan la sincronización y las conexiones nuevas; la lectura de lo ya persistido sigue funcionando. | Regulariza el pago en /billing. Reintentar no paga la deuda. |
| `payment_method_required` | 402 | no | La prueba terminó y no hay una tarjeta registrada. Se pausan la sincronización y las conexiones nuevas; la lectura de lo ya persistido sigue funcionando. | Agrega un medio de pago en /billing. No hay ninguna deuda que regularizar: falta la tarjeta. |
| `connector_plan_limit` | 402 | no | Se agotó la cuota del plan que tu organización tiene EN ESE SISTEMA. No es un límite de Connect. | Sube el plan en el portal de ese sistema. El mensaje trae el enlace. |
## Sistema externo [#sistema-externo]
| Código | HTTP | Reintentable | Qué significa | Qué hacer |
| ------------------------------ | ---- | ------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `upstream_error` | 502 | sí | El sistema externo (SII, banco) falló o respondió mal. | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `upstream_unexpected_response` | 502 | no | El sistema externo respondió con una forma que Connect no reconoce. | No reintentes: la respuesta es determinista. Repórtalo con el request\_id para que actualicemos el conector. |
| `timeout` | 504 | sí | La operación superó su tiempo máximo, casi siempre esperando al sistema externo. | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
## Webhooks [#webhooks]
| Código | HTTP | Reintentable | Qué significa | Qué hacer |
| ---------------------------- | ---- | ------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------- |
| `webhook_endpoint_not_found` | 404 | no | Ese endpoint de webhook no existe en tu organización. | Lista tus endpoints con GET /v1/webhooks y usa un id vigente. |
| `webhook_delivery_not_found` | 404 | no | Esa entrega de webhook no existe para ese endpoint. | Lista las entregas con GET /v1/webhooks/\{id}/deliveries y usa un id vigente. |
| `invalid_webhook_url` | 422 | no | La URL del webhook no es válida para recibir entregas. | Usa una URL https pública. Direcciones internas o no resolubles se rechazan. |
## Internos de Connect [#internos-de-connect]
| Código | HTTP | Reintentable | Qué significa | Qué hacer |
| ----------------------- | ---- | ------------ | --------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `service_unavailable` | 503 | sí | Connect no pudo atender la petición por una falla transitoria propia. | Reintenta con backoff. No es un problema de tu petición ni de tu key. |
| `output_contract_error` | 500 | no | El resultado interno no cumplió el contrato de salida de la tool. | Es un defecto de Connect. Repórtalo con el request\_id. |
| `handler_error` | 500 | no | La tool falló de forma no clasificada. | Repórtalo con el request\_id si persiste. No reintentes a ciegas. |
| `internal_error` | 500 | no | Error interno no clasificado de Connect. | Repórtalo con el request\_id. No reintentes a ciegas. |
| `not_implemented` | 501 | no | La operación existe en el contrato pero aún no está implementada. | Consulta el changelog o espera su disponibilidad. |
---
# Referencia
> El catálogo completo: cada tool con su contrato, su ejemplo en tres canales y sus errores.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
Cada página de esta sección se genera del registry, la única fuente del contrato: lo que ves es lo que el gateway valida. Para el recorrido guiado, parte por [Empieza aquí](/docs/empezar/primera-llamada).
## Sistemas conectables [#sistemas-conectables]
| Sistema | Código | Tools |
| ------------------------------------------- | ---------------- | ----- |
| [BancoEstado Empresas](./banco_estado) | `banco_estado` | 4 |
| [Banco Security Empresas](./banco_security) | `banco_security` | 7 |
| [Banco de Chile Empresas](./bch_empresas) | `bch_empresas` | 5 |
| [Banco BCI Empresas](./bci_pyme) | `bci_pyme` | 4 |
| [Banco BICE Empresas](./bice_empresas) | `bice_empresas` | 4 |
| [Notta](./notta) | `notta` | 6 |
| [Previred](./previred) | `previred` | 8 |
| [Servicio de Impuestos Internos](./sii) | `sii` | 8 |
## De uso general [#de-uso-general]
| Conector | Código | Tools |
| -------------------------------------------- | ------------- | ----- |
| [Conexiones de Emisso Connect](./conexiones) | `conexiones` | 3 |
| [Core](./core) | `core` | 1 |
| [Echo](./echo) | `echo` | 1 |
| [Indicadores económicos](./indicadores) | `indicadores` | 3 |
Además: el [catálogo de errores](./errores) con los 34 códigos, y el contrato OpenAPI completo en [`/docs/openapi.json`](/docs/openapi.json).
---
# Sincronizar conexión BancoEstado
> Inicia sesión en BancoEstado Empresas y persiste los alcances pedidos (saldos, movimientos) para un período, en UNA sola sesión (un login, un logout).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_estado.conexion.sincronizar` |
| **Nombre MCP** | `banco_estado__conexion__sincronizar` |
| **Conector** | `banco_estado` |
| **Plano** | `read` |
| **Alcances** | `saldos`, `movimientos` |
| **Scope (permiso)** | `banco_estado:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Es el ÚNICO camino que trae datos del banco: las tools de consulta leen lo ya guardado. `saldos` es una foto del momento, no del período, así que sólo se sincroniza pidiendo el período corriente. Puede tardar cerca de un minuto, y mientras corre el titular no va a poder entrar al portal: BancoEstado admite una sola sesión activa por usuario.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | sí | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `alcances` | lista de `"saldos"` · `"movimientos"` | sí | Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance\_not\_enabled'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"alcances": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"saldos",
"movimientos"
]
},
"description": "Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance_not_enabled'."
}
},
"required": [
"periodo",
"alcances"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_estado.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08","alcances":["saldos","movimientos"]}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_estado.conexion.sincronizar({ periodo: "2026-08", alcances: ["saldos", "movimientos"] }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_estado.conexion.sincronizar",
"params": {
"periodo": "2026-08",
"alcances": [
"saldos",
"movimientos"
]
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"periodo": "2026-08",
"results": [
{
"alcance": "saldos",
"status": "ok",
"recordsSynced": 2
},
{
"alcance": "movimientos",
"status": "ok",
"recordsSynced": 137
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_estado.conexion.sincronizar",
"plane": "read",
"latency_ms": 58240,
"audit_status": "recorded"
}
}
```
> Con un período ya cerrado, 'saldos' devuelve 0 con su 'detalle': es una foto del momento y sólo se sincroniza pidiendo el período corriente.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string | sí | Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú. |
| `results` | lista de objeto | sí | Una fila por alcance pedido, con cómo le fue a cada uno. |
| `results[].alcance` | string | sí | Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste. |
| `results[].status` | `"ok"` · `"failed"` | sí | 'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].recordsSynced` | entero | sí | Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'. |
| `results[].error` | string | no | Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos\_truncated', 'cartolas\_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre. |
| `results[].detalle` | string | no | Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú."
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"alcance": {
"type": "string",
"description": "Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste."
},
"status": {
"type": "string",
"enum": [
"ok",
"failed"
],
"description": "'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
},
"recordsSynced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'."
},
"error": {
"description": "Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos_truncated', 'cartolas_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre.",
"type": "string"
},
"detalle": {
"description": "Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo.",
"type": "string"
}
},
"required": [
"alcance",
"status",
"recordsSynced"
],
"additionalProperties": false
},
"description": "Una fila por alcance pedido, con cómo le fue a cada uno."
}
},
"required": [
"periodo",
"results"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_session_pending` | 409 | sí | Ejecuta la sincronización de esa conexión (ella acuña la sesión) o espera la programada, y reintenta. |
| `connection_sync_in_progress` | 409 | sí | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `too_many_pending` | 429 | sí | Deja terminar los trabajos en curso antes de encolar más. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_estado.saldos.consultar`](./saldos-consultar): lee el alcance `saldos` que esta sincronización escribe.
* [`banco_estado.movimientos.consultar`](./movimientos-consultar): lee el alcance `movimientos` que esta sincronización escribe.
---
# Verificar conexión BancoEstado Empresas
> Prueba las credenciales de la conexión contra BancoEstado Empresas haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_estado.conexion.verificar` |
| **Nombre MCP** | `banco_estado__conexion__verificar` |
| **Conector** | `banco_estado` |
| **Plano** | `action` |
| **Scope (permiso)** | `banco_estado:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_estado.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_estado.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_estado.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_estado.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_session_pending` | 409 | sí | Ejecuta la sincronización de esa conexión (ella acuña la sesión) o espera la programada, y reintenta. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_estado.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# BancoEstado Empresas
> Las 4 tools de BancoEstado Empresas en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | --------------------------- |
| **Código** | `banco_estado` |
| **Tipo** | `bank` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `portal_credentials` |
| **Alcances** | `saldos`, `movimientos` |
| **Versión** | `1.0.0` |
## Tools [#tools]
* [`banco_estado.conexion.sincronizar`](./conexion-sincronizar): Inicia sesión en BancoEstado Empresas y persiste los alcances pedidos (saldos, movimientos) para un período, en UNA sola sesión (un login, un logout).
* [`banco_estado.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra BancoEstado Empresas haciendo un login real (y su logout, a cargo del pipeline).
* [`banco_estado.movimientos.consultar`](./movimientos-consultar): Lee los movimientos de BancoEstado YA sincronizados de esta conexión, del más reciente al más antiguo.
* [`banco_estado.saldos.consultar`](./saldos-consultar): Lee los saldos de BancoEstado YA sincronizados de esta conexión, del más reciente al más antiguo.
---
# Consultar movimientos de BancoEstado
> Lee los movimientos de BancoEstado YA sincronizados de esta conexión, del más reciente al más antiguo.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_estado.movimientos.consultar` |
| **Nombre MCP** | `banco_estado__movimientos__consultar` |
| **Conector** | `banco_estado` |
| **Plano** | `action` |
| **Lee el alcance** | `movimientos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `banco_estado:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO contacta al banco ni dispara una sincronización, así que si falta un período usa 'banco\_estado.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud SIN signo, 'type' dice si sale ('credit') o entra ('debit') plata según el libro del banco (al revés de como se lee una cartola), y 'display' es ese monto ya formateado a la chilena con su signo. 'saldo' es el saldo arrastrado: es un balance, no lleva 'type' y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `numeroCuenta` | string | no | Filtra por un número de cuenta. Omítelo para ver los movimientos de todas las cuentas de la conexión. |
| `periodo` | string `^\d{4}-\d{2}$` | no | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `cursor` | string | no | Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae la página, entre 1 y 500. Por omisión, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"numeroCuenta": {
"description": "Filtra por un número de cuenta. Omítelo para ver los movimientos de todas las cuentas de la conexión.",
"type": "string"
},
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"cursor": {
"description": "Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae la página, entre 1 y 500. Por omisión, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_estado.movimientos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_estado.movimientos.consultar({ periodo: "2026-08" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_estado.movimientos.consultar",
"params": {
"periodo": "2026-08"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"movimientos": [
{
"numeroCuenta": "12345678901",
"periodo": "2026-08",
"fecha": "2026-08-11",
"descripcion": "PAGO PROVEEDOR",
"documento": "1234567",
"monto": 1700000,
"type": "credit",
"display": "-1.700.000",
"saldo": 300000,
"oficina": "STGO.PRINCIPAL",
"origen": "linea",
"syncedAt": "2026-08-11T17:32:04.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_estado.movimientos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> 'type' es 'credit' porque la plata SALE: es la convención del libro del banco, al revés de como se lee una cartola. 'monto' no lleva el signo; 'display' sí.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ---------------------------- | ------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `movimientos` | lista de objeto | sí | Los movimientos guardados que calzan con los filtros, del más reciente al más antiguo. Una lista vacía significa que ese período no se ha sincronizado, no que no haya movimientos. | |
| `movimientos[].numeroCuenta` | string | sí | El número de la cuenta a la que pertenece el movimiento. | |
| `movimientos[].periodo` | string | null | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del movimiento: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva ni infla los totales. |
| `movimientos[].fecha` | string | null | sí | Fecha del movimiento, en formato AAAA-MM-DD. No trae hora: la cartola no la informa. null si el banco no trajo la celda. |
| `movimientos[].descripcion` | string | null | sí | La glosa del movimiento tal como aparece en la cartola (por ejemplo 'PAGO PROVEEDOR'). |
| `movimientos[].documento` | string | null | sí | El número de documento asociado al movimiento, cuando el banco lo trae. |
| `movimientos[].monto` | número | null | sí | Magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible lo trae 'display'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero. |
| `movimientos[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `movimientos[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `movimientos[].saldo` | número | null | sí | El saldo que queda en la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo. |
| `movimientos[].oficina` | string | null | sí | La oficina que el banco asocia al movimiento, en su propio texto (por ejemplo 'STGO.PRINCIPAL'). |
| `movimientos[].origen` | `"linea"` · `"historica"` | sí | Por cuál de las dos cartolas del banco se trajo la fila: 'linea' es la del mes en curso e 'historica' la de los meses ya cerrados. Es metadato de procedencia y no entra en la identidad del movimiento, así que el mismo movimiento traído por las dos no se duplica. | |
| `movimientos[].syncedAt` | string | null | sí | Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. |
| `cursor` | string | null | sí | Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"movimientos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "El número de la cuenta a la que pertenece el movimiento."
},
"periodo": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del movimiento: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva ni infla los totales."
},
"fecha": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Fecha del movimiento, en formato AAAA-MM-DD. No trae hora: la cartola no la informa. null si el banco no trajo la celda."
},
"descripcion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La glosa del movimiento tal como aparece en la cartola (por ejemplo 'PAGO PROVEEDOR')."
},
"documento": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El número de documento asociado al movimiento, cuando el banco lo trae."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible lo trae 'display'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"saldo": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo que queda en la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo."
},
"oficina": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La oficina que el banco asocia al movimiento, en su propio texto (por ejemplo 'STGO.PRINCIPAL')."
},
"origen": {
"type": "string",
"enum": [
"linea",
"historica"
],
"description": "Por cuál de las dos cartolas del banco se trajo la fila: 'linea' es la del mes en curso e 'historica' la de los meses ya cerrados. Es metadato de procedencia y no entra en la identidad del movimiento, así que el mismo movimiento traído por las dos no se duplica."
},
"syncedAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas."
}
},
"required": [
"numeroCuenta",
"periodo",
"fecha",
"descripcion",
"documento",
"monto",
"type",
"display",
"saldo",
"oficina",
"origen",
"syncedAt"
],
"additionalProperties": false
},
"description": "Los movimientos guardados que calzan con los filtros, del más reciente al más antiguo. Una lista vacía significa que ese período no se ha sincronizado, no que no haya movimientos."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer."
}
},
"required": [
"movimientos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_estado.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar saldos de BancoEstado
> Lee los saldos de BancoEstado YA sincronizados de esta conexión, del más reciente al más antiguo.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_estado.saldos.consultar` |
| **Nombre MCP** | `banco_estado__saldos__consultar` |
| **Conector** | `banco_estado` |
| **Plano** | `action` |
| **Lee el alcance** | `saldos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `banco_estado:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO contacta al banco ni dispara una sincronización, así que si nunca se sincronizó devuelve una lista vacía. Para traer datos nuevos usa 'banco\_estado.conexion.sincronizar' primero. Los saldos son una foto POR DÍA y vienen como NÚMERO conservando su signo: un sobregiro es negativo, y un saldo no lleva 'type' porque no es una operación. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual y nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `observedDay` | string `^\d{4}-\d{2}-\d{2}$` | no | Filtra por el día de la foto de saldo, en formato AAAA-MM-DD. Si lo omites, la primera página ya trae las fotos más recientes que haya guardadas. |
| `numeroCuenta` | string | no | Filtra por un número de cuenta. Omítelo para ver todas las cuentas de la conexión. |
| `cursor` | string | no | Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae la página, entre 1 y 500. Por omisión, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"observedDay": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Filtra por el día de la foto de saldo, en formato AAAA-MM-DD. Si lo omites, la primera página ya trae las fotos más recientes que haya guardadas."
},
"numeroCuenta": {
"description": "Filtra por un número de cuenta. Omítelo para ver todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae la página, entre 1 y 500. Por omisión, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_estado.saldos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_estado.saldos.consultar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_estado.saldos.consultar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"saldos": [
{
"numeroCuenta": "12345678901",
"moneda": "PESOS",
"observedDay": "2026-08-11",
"hora": "14:30",
"saldoContable": 300000,
"saldoDisponible": 250000,
"retencionUnDia": 0,
"retencionDosDias": 0,
"retencionOtras": 0,
"retencionTotal": 0,
"syncedAt": "2026-08-11T17:32:04.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_estado.saldos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------- | --------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `saldos` | lista de objeto | sí | Las fotos de saldo guardadas que calzan con los filtros, de la más reciente a la más antigua. Una lista vacía significa que la conexión nunca sincronizó saldos, no que la empresa no tenga cuentas. | |
| `saldos[].numeroCuenta` | string | sí | El número de la cuenta a la que corresponde esta foto de saldo. | |
| `saldos[].moneda` | string | null | sí | La moneda de la cuenta, en el texto del propio banco (por ejemplo 'PESOS'). null cuando el listado de cuentas no la trae. |
| `saldos[].observedDay` | string | null | sí | El día de esta foto de saldo, en formato AAAA-MM-DD. Hay una foto por cuenta y por día: volver a sincronizar el mismo día actualiza esta fila en vez de agregar otra. Se llama 'observedDay' y no 'fecha' para que sea el mismo nombre que en los otros bancos de Connect. |
| `saldos[].hora` | string | null | sí | La hora en que el banco reportó esta foto, en su propio formato (por ejemplo '14:30'). BancoEstado la entrega y los otros bancos de Connect no, así que conserva el nombre del banco. |
| `saldos[].saldoContable` | número | null | sí | El saldo contable de la cuenta. Es un balance, no una operación: conserva su propio signo (un sobregiro es negativo) y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero. |
| `saldos[].saldoDisponible` | número | null | sí | El saldo disponible de la cuenta. Es un balance: conserva su propio signo y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero. |
| `saldos[].retencionUnDia` | número | null | sí | Lo retenido a un día. Un 0 afirma que no hay retención; un null dice que el banco no informó la celda, que es un dato distinto. |
| `saldos[].retencionDosDias` | número | null | sí | Lo retenido a dos días. Un 0 afirma que no hay retención; un null dice que el banco no informó la celda. |
| `saldos[].retencionOtras` | número | null | sí | El resto de las retenciones, sumando las dos celdas que el banco publica por separado. null si no vino ninguna de las dos; un 0 sí afirma que no hay retención. |
| `saldos[].retencionTotal` | número | null | sí | El total de retenciones según el banco. null significa que no informó la celda. |
| `saldos[].syncedAt` | string | null | sí | Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. |
| `cursor` | string | null | sí | Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"saldos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "El número de la cuenta a la que corresponde esta foto de saldo."
},
"moneda": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La moneda de la cuenta, en el texto del propio banco (por ejemplo 'PESOS'). null cuando el listado de cuentas no la trae."
},
"observedDay": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El día de esta foto de saldo, en formato AAAA-MM-DD. Hay una foto por cuenta y por día: volver a sincronizar el mismo día actualiza esta fila en vez de agregar otra. Se llama 'observedDay' y no 'fecha' para que sea el mismo nombre que en los otros bancos de Connect."
},
"hora": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La hora en que el banco reportó esta foto, en su propio formato (por ejemplo '14:30'). BancoEstado la entrega y los otros bancos de Connect no, así que conserva el nombre del banco."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo contable de la cuenta. Es un balance, no una operación: conserva su propio signo (un sobregiro es negativo) y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero."
},
"saldoDisponible": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo disponible de la cuenta. Es un balance: conserva su propio signo y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero."
},
"retencionUnDia": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Lo retenido a un día. Un 0 afirma que no hay retención; un null dice que el banco no informó la celda, que es un dato distinto."
},
"retencionDosDias": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Lo retenido a dos días. Un 0 afirma que no hay retención; un null dice que el banco no informó la celda."
},
"retencionOtras": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El resto de las retenciones, sumando las dos celdas que el banco publica por separado. null si no vino ninguna de las dos; un 0 sí afirma que no hay retención."
},
"retencionTotal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El total de retenciones según el banco. null significa que no informó la celda."
},
"syncedAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas."
}
},
"required": [
"numeroCuenta",
"moneda",
"observedDay",
"hora",
"saldoContable",
"saldoDisponible",
"retencionUnDia",
"retencionDosDias",
"retencionOtras",
"retencionTotal",
"syncedAt"
],
"additionalProperties": false
},
"description": "Las fotos de saldo guardadas que calzan con los filtros, de la más reciente a la más antigua. Una lista vacía significa que la conexión nunca sincronizó saldos, no que la empresa no tenga cuentas."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer."
}
},
"required": [
"saldos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_estado.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar cartolas emitidas de Banco de Chile
> Lee las cartolas (extractos mensuales) ya sincronizadas de esta conexión, la más reciente primero, filtrables por período de búsqueda (AAAA-MM) y por cuenta.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bch_empresas.cartolas.consultar` |
| **Nombre MCP** | `bch_empresas__cartolas__consultar` |
| **Conector** | `bch_empresas` |
| **Plano** | `action` |
| **Lee el alcance** | `cartolas` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `bch_empresas:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO contacta al banco ni dispara una sincronización. Para traer datos nuevos, usa 'bch\_empresas.conexion.sincronizar' primero. Una cartola es un OBJETO propio, no una vista de 'movimientos': sus saldos de apertura y cierre pueden no cuadrar exactamente con la suma de movimientos del mismo mes porque el extracto encadena por fecha contable y el feed vivo por fecha del movimiento. 'numeroCartola' es TEXTO siempre (convertirlo a número pierde ceros a la izquierda). Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas; reenvía ese valor tal cual, nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ----------------- | ---------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fechaEmisionDay` | string `^\d{4}-\d{2}-\d{2}$` | no | Filtra por el día en que el banco emitió el extracto, en formato AAAA-MM-DD. |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por el mes (AAAA-MM) con el que se BUSCÓ la cartola, que no es su fecha de emisión: para esa usa 'fechaEmisionDay'. Sin él, la respuesta cruza todos los períodos guardados. |
| `numeroCuenta` | string | no | Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión. |
| `cursor` | string | no | Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae una página, entre 1 y 500. Por defecto, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"fechaEmisionDay": {
"description": "Filtra por el día en que el banco emitió el extracto, en formato AAAA-MM-DD.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"periodo": {
"description": "Filtra por el mes (AAAA-MM) con el que se BUSCÓ la cartola, que no es su fecha de emisión: para esa usa 'fechaEmisionDay'. Sin él, la respuesta cruza todos los períodos guardados.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}$"
},
"numeroCuenta": {
"description": "Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae una página, entre 1 y 500. Por defecto, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bch_empresas.cartolas.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bch_empresas.cartolas.consultar({ periodo: "2026-07" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bch_empresas.cartolas.consultar",
"params": {
"periodo": "2026-07"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"cartolas": [
{
"numeroCuenta": "CTD12345678",
"tipoProducto": "CTD",
"currency": "CLP",
"fechaEmisionDay": "2026-07-31",
"periodo": "2026-07",
"numeroCartola": "00042",
"saldoInicial": 8462150,
"saldoFinal": 4370480,
"ultimaLecturaEn": "2026-08-10T14:02:11.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "bch_empresas.cartolas.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> 'numeroCartola' preserva los ceros a la izquierda: nunca se convierte a número.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ---------------------------- | --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cartolas` | lista de objeto | sí | Las cartolas guardadas, la más reciente primero. Una lista vacía significa que ese período todavía no se sincronizó, no que el banco no tenga extractos de esa cuenta. | |
| `cartolas[].numeroCuenta` | string | sí | La cuenta a la que pertenece esta fila, con el código de producto adelante y sin el relleno de ceros del banco (por ejemplo 'CTD12345678'). Es el mismo valor en saldos, movimientos y cartolas, y el que espera el filtro 'numeroCuenta'. | |
| `cartolas[].tipoProducto` | string | sí | El tipo de producto de la cuenta según el índice de cartolas del banco (por ejemplo 'CTD'). Cuando el índice no lo trae, cae al código de producto de la cuenta. | |
| `cartolas[].currency` | string | sí | La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta y nunca se asume: hoy el conector solo persiste cuentas en pesos chilenos y saltea las demás, avisándolo en el 'detalle' de la sincronización. | |
| `cartolas[].fechaEmisionDay` | string | sí | El día (AAAA-MM-DD) en que el banco emitió este extracto. Junto con la cuenta es lo que identifica a la cartola, y es lo que filtra el 'fechaEmisionDay' de la entrada. | |
| `cartolas[].periodo` | string | sí | El mes (AAAA-MM) con el que se buscó esta cartola, que no es la fecha del extracto: esa es 'fechaEmisionDay'. | |
| `cartolas[].numeroCartola` | string | null | sí | El número correlativo del extracto (tag 28C del MT940), SIEMPRE como texto: convertirlo a número le come los ceros a la izquierda. 'null' cuando el extracto descargado no trae el tag. |
| `cartolas[].saldoInicial` | número | null | sí | El saldo de apertura del extracto (tag 60 del MT940), con su propio signo: en MT940 la marca 'D' es un sobregiro y sale negativa. No cuadra necesariamente con la suma de 'movimientos' del mismo mes, porque el extracto encadena por fecha contable y el feed vivo por fecha del movimiento. 'null' cuando el extracto no lo declara. |
| `cartolas[].saldoFinal` | número | null | sí | El saldo de cierre del extracto (tag 62 del MT940), con el mismo criterio de signo y la misma advertencia de cuadratura que 'saldoInicial'. |
| `cartolas[].ultimaLecturaEn` | string | sí | Instante (ISO 8601) en que esta cartola se leyó del banco por última vez. | |
| `cursor` | string | null | sí | El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"cartolas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "La cuenta a la que pertenece esta fila, con el código de producto adelante y sin el relleno de ceros del banco (por ejemplo 'CTD12345678'). Es el mismo valor en saldos, movimientos y cartolas, y el que espera el filtro 'numeroCuenta'."
},
"tipoProducto": {
"type": "string",
"description": "El tipo de producto de la cuenta según el índice de cartolas del banco (por ejemplo 'CTD'). Cuando el índice no lo trae, cae al código de producto de la cuenta."
},
"currency": {
"type": "string",
"description": "La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta y nunca se asume: hoy el conector solo persiste cuentas en pesos chilenos y saltea las demás, avisándolo en el 'detalle' de la sincronización."
},
"fechaEmisionDay": {
"type": "string",
"description": "El día (AAAA-MM-DD) en que el banco emitió este extracto. Junto con la cuenta es lo que identifica a la cartola, y es lo que filtra el 'fechaEmisionDay' de la entrada."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se buscó esta cartola, que no es la fecha del extracto: esa es 'fechaEmisionDay'."
},
"numeroCartola": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El número correlativo del extracto (tag 28C del MT940), SIEMPRE como texto: convertirlo a número le come los ceros a la izquierda. 'null' cuando el extracto descargado no trae el tag."
},
"saldoInicial": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo de apertura del extracto (tag 60 del MT940), con su propio signo: en MT940 la marca 'D' es un sobregiro y sale negativa. No cuadra necesariamente con la suma de 'movimientos' del mismo mes, porque el extracto encadena por fecha contable y el feed vivo por fecha del movimiento. 'null' cuando el extracto no lo declara."
},
"saldoFinal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo de cierre del extracto (tag 62 del MT940), con el mismo criterio de signo y la misma advertencia de cuadratura que 'saldoInicial'."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Instante (ISO 8601) en que esta cartola se leyó del banco por última vez."
}
},
"required": [
"numeroCuenta",
"tipoProducto",
"currency",
"fechaEmisionDay",
"periodo",
"numeroCartola",
"saldoInicial",
"saldoFinal",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Las cartolas guardadas, la más reciente primero. Una lista vacía significa que ese período todavía no se sincronizó, no que el banco no tenga extractos de esa cuenta."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página."
}
},
"required": [
"cartolas",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bch_empresas.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Sincronizar conexión Banco de Chile
> Sincroniza los alcances solicitados (saldos, movimientos, cartolas) para un período en una sola sesión de portal (un login, un logout).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bch_empresas.conexion.sincronizar` |
| **Nombre MCP** | `bch_empresas__conexion__sincronizar` |
| **Conector** | `bch_empresas` |
| **Plano** | `read` |
| **Alcances** | `saldos`, `movimientos`, `cartolas` |
| **Scope (permiso)** | `bch_empresas:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
`saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente. `cartolas` son los extractos MENSUALES ya emitidos por el banco, un objeto propio que NO alimenta `movimientos`.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ---------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | sí | El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes. |
| `alcances` | lista de `"saldos"` · `"movimientos"` · `"cartolas"` | sí | Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance\_not\_enabled'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes."
},
"alcances": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"saldos",
"movimientos",
"cartolas"
]
},
"description": "Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance_not_enabled'."
}
},
"required": [
"periodo",
"alcances"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bch_empresas.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08","alcances":["saldos","movimientos"]}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bch_empresas.conexion.sincronizar({ periodo: "2026-08", alcances: ["saldos", "movimientos"] }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bch_empresas.conexion.sincronizar",
"params": {
"periodo": "2026-08",
"alcances": [
"saldos",
"movimientos"
]
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"periodo": "2026-08",
"results": [
{
"alcance": "saldos",
"status": "ok",
"recordsSynced": 1
},
{
"alcance": "movimientos",
"status": "ok",
"recordsSynced": 12
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "bch_empresas.conexion.sincronizar",
"plane": "read",
"latency_ms": 58240,
"audit_status": "recorded"
}
}
```
> Un solo login del Portal Empresas cubre los alcances pedidos; el logout lo hace el pipeline al terminar. `cartolas` existe y se puede pedir junto con los otros dos, pero revisa que la conexión lo tenga habilitado: si no, esta llamada devuelve 403 alcance\_not\_enabled entera.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string | sí | Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú. |
| `results` | lista de objeto | sí | Una fila por alcance pedido, en el orden canónico del conector. Revísalas todas: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].alcance` | string | sí | Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste. |
| `results[].status` | `"ok"` · `"failed"` | sí | 'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].recordsSynced` | entero | sí | Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'. |
| `results[].error` | string | no | Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos\_truncated', 'cartolas\_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre. |
| `results[].detalle` | string | no | Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú."
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"alcance": {
"type": "string",
"description": "Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste."
},
"status": {
"type": "string",
"enum": [
"ok",
"failed"
],
"description": "'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
},
"recordsSynced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'."
},
"error": {
"description": "Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos_truncated', 'cartolas_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre.",
"type": "string"
},
"detalle": {
"description": "Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo.",
"type": "string"
}
},
"required": [
"alcance",
"status",
"recordsSynced"
],
"additionalProperties": false
},
"description": "Una fila por alcance pedido, en el orden canónico del conector. Revísalas todas: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
}
},
"required": [
"periodo",
"results"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_sync_in_progress` | 409 | sí | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `too_many_pending` | 429 | sí | Deja terminar los trabajos en curso antes de encolar más. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bch_empresas.saldos.consultar`](./saldos-consultar): lee el alcance `saldos` que esta sincronización escribe.
* [`bch_empresas.movimientos.consultar`](./movimientos-consultar): lee el alcance `movimientos` que esta sincronización escribe.
* [`bch_empresas.cartolas.consultar`](./cartolas-consultar): lee el alcance `cartolas` que esta sincronización escribe.
---
# Verificar conexión Banco de Chile
> Prueba las credenciales de la conexión contra Banco de Chile haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bch_empresas.conexion.verificar` |
| **Nombre MCP** | `bch_empresas__conexion__verificar` |
| **Conector** | `bch_empresas` |
| **Plano** | `action` |
| **Scope (permiso)** | `bch_empresas:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bch_empresas.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bch_empresas.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bch_empresas.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "bch_empresas.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bch_empresas.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# Banco de Chile Empresas
> Las 5 tools de Banco de Chile Empresas en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | ----------------------------------- |
| **Código** | `bch_empresas` |
| **Tipo** | `bank` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `portal_credentials` |
| **Alcances** | `saldos`, `movimientos`, `cartolas` |
| **Versión** | `1.0.0` |
## Tools [#tools]
* [`bch_empresas.cartolas.consultar`](./cartolas-consultar): Lee las cartolas (extractos mensuales) ya sincronizadas de esta conexión, la más reciente primero, filtrables por período de búsqueda (AAAA-MM) y por cuenta.
* [`bch_empresas.conexion.sincronizar`](./conexion-sincronizar): Sincroniza los alcances solicitados (saldos, movimientos, cartolas) para un período en una sola sesión de portal (un login, un logout).
* [`bch_empresas.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra Banco de Chile haciendo un login real (y su logout, a cargo del pipeline).
* [`bch_empresas.movimientos.consultar`](./movimientos-consultar): Lee los movimientos ya sincronizados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM) y por cuenta.
* [`bch_empresas.saldos.consultar`](./saldos-consultar): Lee los saldos ya sincronizados de esta conexión, con el snapshot más reciente primero.
---
# Consultar movimientos de Banco de Chile
> Lee los movimientos ya sincronizados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM) y por cuenta.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bch_empresas.movimientos.consultar` |
| **Nombre MCP** | `bch_empresas__movimientos__consultar` |
| **Conector** | `bch_empresas` |
| **Plano** | `action` |
| **Lee el alcance** | `movimientos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `bch_empresas:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO contacta al banco ni dispara una sincronización. Si el período nunca se sincronizó, devuelve una lista vacía, que NO significa que no haya movimientos. Para traer datos nuevos, usa 'bch\_empresas.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola), y 'display' es ese monto ya formateado a la chilena con su signo. 'saldoContable' es un balance: no lleva 'type' y conserva su propio signo. 'id' es la huella estable con la que se guardó el movimiento: el mismo movimiento visto en dos sincronizaciones solapadas trae el mismo 'id'. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas; reenvía ese valor tal cual, nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por el mes (AAAA-MM) con el que se sincronizó la fila. Sin él, la respuesta cruza todos los períodos guardados de esta conexión. |
| `numeroCuenta` | string | no | Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión. |
| `cursor` | string | no | Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae una página, entre 1 y 500. Por defecto, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"description": "Filtra por el mes (AAAA-MM) con el que se sincronizó la fila. Sin él, la respuesta cruza todos los períodos guardados de esta conexión.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}$"
},
"numeroCuenta": {
"description": "Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae una página, entre 1 y 500. Por defecto, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bch_empresas.movimientos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bch_empresas.movimientos.consultar({ periodo: "2026-07" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bch_empresas.movimientos.consultar",
"params": {
"periodo": "2026-07"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"movimientos": [
{
"id": "CTD12345678:20260728 09:15:33:890750:cargo:1",
"numeroCuenta": "CTD12345678",
"codigoProducto": "CTD",
"currency": "CLP",
"periodo": "2026-07",
"fechaMovimiento": "2026-07-28T09:15:33.000Z",
"fechaContable": "2026-07-28",
"codigoTransaccion": "170",
"monto": 890750,
"type": "credit",
"display": "-890.750",
"saldoContable": 4370480,
"descripcion": "Transferencia a proveedor",
"canal": "INTERNET",
"detalleGlosa": "RUT: 76111111-6 | Nombre: Proveedora del Maule SpA",
"ultimaLecturaEn": "2026-08-10T14:02:11.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "bch_empresas.movimientos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> 'id' es literal el que entrega el banco (canonizando sólo el relleno de ceros de la cuenta): no es un hash recomputado, a diferencia de BICE y Banco Security.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------- | ---------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `movimientos` | lista de objeto | sí | Los movimientos guardados, del más reciente al más antiguo. Una lista vacía significa que ese período todavía no se sincronizó, no que no haya movimientos. | |
| `movimientos[].id` | string | sí | La identidad estable del movimiento: el 'id' que entrega el propio Banco de Chile, con el relleno de ceros de la cuenta canonizado. El mismo movimiento visto en dos sincronizaciones solapadas trae el mismo 'id', así que sirve para deduplicar sin comparar campos de presentación. | |
| `movimientos[].numeroCuenta` | string | sí | La cuenta a la que pertenece esta fila, con el código de producto adelante y sin el relleno de ceros del banco (por ejemplo 'CTD12345678'). Es el mismo valor en saldos, movimientos y cartolas, y el que espera el filtro 'numeroCuenta'. | |
| `movimientos[].codigoProducto` | string | sí | Las tres letras del tipo de producto de la cuenta (por ejemplo 'CTD'), tal como las entrega el banco. Es el prefijo de 'numeroCuenta'. | |
| `movimientos[].currency` | string | sí | La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta y nunca se asume: hoy el conector solo persiste cuentas en pesos chilenos y saltea las demás, avisándolo en el 'detalle' de la sincronización. | |
| `movimientos[].periodo` | string | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es la ventana con que se pidió, no una propiedad del movimiento: la fecha del movimiento vive en 'fechaMovimiento'. Es el valor con el que filtra el 'periodo' de la entrada. | |
| `movimientos[].fechaMovimiento` | string | null | sí | Fecha y hora del movimiento (ISO 8601), tal como la entrega el banco. 'null' cuando el banco no la trajo. |
| `movimientos[].fechaContable` | string | null | sí | La fecha contable en formato AAAA-MM-DD, sin hora, y distinta de 'fechaMovimiento'. El banco la manda como dd/mm/aaaa y aquí ya viene convertida a ISO. 'null' cuando llegó en una forma que no se reconoció: nunca se adivina una fecha ni se deja pasar la celda cruda. |
| `movimientos[].codigoTransaccion` | string | null | sí | El código de transacción del núcleo del banco, tal cual. Es una etiqueta interna sin catálogo publicado: sirve para agrupar movimientos del mismo tipo, no para deducir qué fue la operación. 'null' cuando el banco no lo trae. |
| `movimientos[].monto` | número | null | sí | La magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible, 'display'. 'null' significa que el banco no trajo la celda, nunca 0. |
| `movimientos[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `movimientos[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `movimientos[].saldoContable` | número | null | sí | El saldo de la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo, así que un sobregiro es negativo. |
| `movimientos[].descripcion` | string | null | sí | La glosa del movimiento, tal como la escribe el banco. 'null' cuando llega vacía. |
| `movimientos[].canal` | string | null | sí | El canal por el que se cursó el movimiento, con la etiqueta del propio banco (por ejemplo 'INTERNET'). 'null' cuando el banco no lo informa. |
| `movimientos[].detalleGlosa` | string | null | sí | Las etiquetas extra del movimiento (RUT y nombre de la contraparte, entre otras) aplanadas en un solo texto, separadas por ' \| '. Trae datos personales de terceros: trátalo como tal. 'null' cuando el banco no adjunta ninguna. |
| `movimientos[].ultimaLecturaEn` | string | sí | Instante (ISO 8601) en que esta fila se leyó del banco por última vez. Una corrección del banco entra como fila NUEVA en vez de reemplazar a la anterior, así que ante dos filas del mismo movimiento vale la de 'ultimaLecturaEn' mayor. | |
| `cursor` | string | null | sí | El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"movimientos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "La identidad estable del movimiento: el 'id' que entrega el propio Banco de Chile, con el relleno de ceros de la cuenta canonizado. El mismo movimiento visto en dos sincronizaciones solapadas trae el mismo 'id', así que sirve para deduplicar sin comparar campos de presentación."
},
"numeroCuenta": {
"type": "string",
"description": "La cuenta a la que pertenece esta fila, con el código de producto adelante y sin el relleno de ceros del banco (por ejemplo 'CTD12345678'). Es el mismo valor en saldos, movimientos y cartolas, y el que espera el filtro 'numeroCuenta'."
},
"codigoProducto": {
"type": "string",
"description": "Las tres letras del tipo de producto de la cuenta (por ejemplo 'CTD'), tal como las entrega el banco. Es el prefijo de 'numeroCuenta'."
},
"currency": {
"type": "string",
"description": "La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta y nunca se asume: hoy el conector solo persiste cuentas en pesos chilenos y saltea las demás, avisándolo en el 'detalle' de la sincronización."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es la ventana con que se pidió, no una propiedad del movimiento: la fecha del movimiento vive en 'fechaMovimiento'. Es el valor con el que filtra el 'periodo' de la entrada."
},
"fechaMovimiento": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Fecha y hora del movimiento (ISO 8601), tal como la entrega el banco. 'null' cuando el banco no la trajo."
},
"fechaContable": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La fecha contable en formato AAAA-MM-DD, sin hora, y distinta de 'fechaMovimiento'. El banco la manda como dd/mm/aaaa y aquí ya viene convertida a ISO. 'null' cuando llegó en una forma que no se reconoció: nunca se adivina una fecha ni se deja pasar la celda cruda."
},
"codigoTransaccion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El código de transacción del núcleo del banco, tal cual. Es una etiqueta interna sin catálogo publicado: sirve para agrupar movimientos del mismo tipo, no para deducir qué fue la operación. 'null' cuando el banco no lo trae."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "La magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible, 'display'. 'null' significa que el banco no trajo la celda, nunca 0."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo de la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo, así que un sobregiro es negativo."
},
"descripcion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La glosa del movimiento, tal como la escribe el banco. 'null' cuando llega vacía."
},
"canal": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El canal por el que se cursó el movimiento, con la etiqueta del propio banco (por ejemplo 'INTERNET'). 'null' cuando el banco no lo informa."
},
"detalleGlosa": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Las etiquetas extra del movimiento (RUT y nombre de la contraparte, entre otras) aplanadas en un solo texto, separadas por ' | '. Trae datos personales de terceros: trátalo como tal. 'null' cuando el banco no adjunta ninguna."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Instante (ISO 8601) en que esta fila se leyó del banco por última vez. Una corrección del banco entra como fila NUEVA en vez de reemplazar a la anterior, así que ante dos filas del mismo movimiento vale la de 'ultimaLecturaEn' mayor."
}
},
"required": [
"id",
"numeroCuenta",
"codigoProducto",
"currency",
"periodo",
"fechaMovimiento",
"fechaContable",
"codigoTransaccion",
"monto",
"type",
"display",
"saldoContable",
"descripcion",
"canal",
"detalleGlosa",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Los movimientos guardados, del más reciente al más antiguo. Una lista vacía significa que ese período todavía no se sincronizó, no que no haya movimientos."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página."
}
},
"required": [
"movimientos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bch_empresas.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar saldos de Banco de Chile
> Lee los saldos ya sincronizados de esta conexión, con el snapshot más reciente primero.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bch_empresas.saldos.consultar` |
| **Nombre MCP** | `bch_empresas__saldos__consultar` |
| **Conector** | `bch_empresas` |
| **Plano** | `action` |
| **Lee el alcance** | `saldos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `bch_empresas:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO contacta al banco ni dispara una sincronización. Si nunca se sincronizó, devuelve una lista vacía. Para traer datos nuevos, usa 'bch\_empresas.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los saldos más recientes que hay guardados. Los tres saldos vienen como NÚMERO y conservan su signo. Un saldo no lleva 'type' (no es una operación). 'saldoContable' puede venir null en filas sincronizadas antes del 2026-08-11, que es cuando se empezó a leer; desde entonces trae el saldo contable real, que difiere del disponible por retenciones y cheques en canje. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas; reenvía ese valor tal cual, nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `observedDay` | string `^\d{4}-\d{2}-\d{2}$` | no | Filtra por el día del snapshot, en formato AAAA-MM-DD. Sin él, la primera página ya trae los saldos más recientes que hay guardados de cada cuenta. |
| `numeroCuenta` | string | no | Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión. |
| `cursor` | string | no | Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae una página, entre 1 y 500. Por defecto, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"observedDay": {
"description": "Filtra por el día del snapshot, en formato AAAA-MM-DD. Sin él, la primera página ya trae los saldos más recientes que hay guardados de cada cuenta.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"numeroCuenta": {
"description": "Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae una página, entre 1 y 500. Por defecto, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bch_empresas.saldos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bch_empresas.saldos.consultar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bch_empresas.saldos.consultar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"saldos": [
{
"numeroCuenta": "12345678",
"codigoProducto": "CTD",
"currency": "CLP",
"observedDay": "2026-08-10",
"saldoDisponible": 8462150,
"saldoContable": 8501200,
"lineaCredito": 0,
"ultimaLecturaEn": "2026-08-10T14:02:11.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "bch_empresas.saldos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> 'saldoContable' difiere del disponible por retenciones y cheques en canje. Puede venir null en filas sincronizadas antes del 2026-08-11: hasta esa fecha no se leía.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| -------------------------- | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `saldos` | lista de objeto | sí | Los snapshots de saldo guardados, el más reciente primero. Una lista vacía significa que esta conexión todavía no se sincronizó, no que la empresa no tenga cuentas. | |
| `saldos[].numeroCuenta` | string | sí | La cuenta a la que pertenece esta fila, con el código de producto adelante y sin el relleno de ceros del banco (por ejemplo 'CTD12345678'). Es el mismo valor en saldos, movimientos y cartolas, y el que espera el filtro 'numeroCuenta'. | |
| `saldos[].codigoProducto` | string | sí | Las tres letras con que Banco de Chile identifica el tipo de producto de la cuenta (por ejemplo 'CTD'). Es el prefijo de 'numeroCuenta' y viene del propio banco. | |
| `saldos[].currency` | string | sí | La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta y nunca se asume: hoy el conector solo persiste cuentas en pesos chilenos y saltea las demás, avisándolo en el 'detalle' de la sincronización. | |
| `saldos[].observedDay` | string | sí | El día (AAAA-MM-DD) de esta foto de saldos. Los saldos se guardan como un snapshot por día, así que sin filtro de fecha la primera página ya trae el más reciente de cada cuenta. | |
| `saldos[].saldoDisponible` | número | null | sí | El saldo disponible de la cuenta ese día, como número y con su propio signo (un sobregiro es negativo). 'null' significa que el banco no trajo la celda, nunca 0: un 0 es un saldo real. |
| `saldos[].saldoContable` | número | null | sí | El saldo contable de la cuenta ese día. Difiere del disponible por retenciones y cheques en canje. Viene 'null' en las filas sincronizadas antes del 2026-08-11, que es cuando se empezó a leer; ese 'null' significa «no se leyó», nunca cero. |
| `saldos[].lineaCredito` | número | null | sí | El monto disponible de la línea de crédito de la cuenta, tal como lo informa el banco. 'null' significa que el banco no trajo la celda, nunca 0. |
| `saldos[].ultimaLecturaEn` | string | sí | Instante (ISO 8601) en que esta fila se leyó del banco por última vez. La caché puede quedarse quieta sin que la consulta falle (una conexión se auto-pausa tras tres fallos de credencial), así que este campo es lo que distingue un saldo recién leído de uno viejo. | |
| `cursor` | string | null | sí | El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"saldos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "La cuenta a la que pertenece esta fila, con el código de producto adelante y sin el relleno de ceros del banco (por ejemplo 'CTD12345678'). Es el mismo valor en saldos, movimientos y cartolas, y el que espera el filtro 'numeroCuenta'."
},
"codigoProducto": {
"type": "string",
"description": "Las tres letras con que Banco de Chile identifica el tipo de producto de la cuenta (por ejemplo 'CTD'). Es el prefijo de 'numeroCuenta' y viene del propio banco."
},
"currency": {
"type": "string",
"description": "La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta y nunca se asume: hoy el conector solo persiste cuentas en pesos chilenos y saltea las demás, avisándolo en el 'detalle' de la sincronización."
},
"observedDay": {
"type": "string",
"description": "El día (AAAA-MM-DD) de esta foto de saldos. Los saldos se guardan como un snapshot por día, así que sin filtro de fecha la primera página ya trae el más reciente de cada cuenta."
},
"saldoDisponible": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo disponible de la cuenta ese día, como número y con su propio signo (un sobregiro es negativo). 'null' significa que el banco no trajo la celda, nunca 0: un 0 es un saldo real."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo contable de la cuenta ese día. Difiere del disponible por retenciones y cheques en canje. Viene 'null' en las filas sincronizadas antes del 2026-08-11, que es cuando se empezó a leer; ese 'null' significa «no se leyó», nunca cero."
},
"lineaCredito": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El monto disponible de la línea de crédito de la cuenta, tal como lo informa el banco. 'null' significa que el banco no trajo la celda, nunca 0."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Instante (ISO 8601) en que esta fila se leyó del banco por última vez. La caché puede quedarse quieta sin que la consulta falle (una conexión se auto-pausa tras tres fallos de credencial), así que este campo es lo que distingue un saldo recién leído de uno viejo."
}
},
"required": [
"numeroCuenta",
"codigoProducto",
"currency",
"observedDay",
"saldoDisponible",
"saldoContable",
"lineaCredito",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Los snapshots de saldo guardados, el más reciente primero. Una lista vacía significa que esta conexión todavía no se sincronizó, no que la empresa no tenga cuentas."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página."
}
},
"required": [
"saldos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bch_empresas.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Sincronizar conexión Banco Security
> Sincroniza los alcances solicitados (transferencias, nóminas, saldos, movimientos) para un período en una sola sesión (un login, un logout).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_security.conexion.sincronizar` |
| **Nombre MCP** | `banco_security__conexion__sincronizar` |
| **Conector** | `banco_security` |
| **Plano** | `read` |
| **Alcances** | `transferencias`, `saldos`, `movimientos`, `nominas` |
| **Scope (permiso)** | `banco_security:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
`saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente. `movimientos` cubre cualquier período: el conector elige solo la cartola que corresponde (la del mes en curso o la histórica) y las dos escriben la misma tabla, así que un mismo movimiento traído por las dos NO se duplica.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------------------------------------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | sí | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `alcances` | lista de `"transferencias"` · `"nominas"` · `"saldos"` · `"movimientos"` | sí | Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance\_not\_enabled'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"alcances": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"transferencias",
"nominas",
"saldos",
"movimientos"
]
},
"description": "Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance_not_enabled'."
}
},
"required": [
"periodo",
"alcances"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","alcances":["transferencias","saldos","movimientos"]}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_security.conexion.sincronizar({ periodo: "2026-07", alcances: ["transferencias", "saldos", "movimientos"] }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_security.conexion.sincronizar",
"params": {
"periodo": "2026-07",
"alcances": [
"transferencias",
"saldos",
"movimientos"
]
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"periodo": "2026-07",
"results": [
{
"alcance": "transferencias",
"status": "ok",
"recordsSynced": 18
},
{
"alcance": "saldos",
"status": "ok",
"recordsSynced": 0,
"detalle": "Los saldos son una foto del momento, no del período, así que solo se sincronizan en el período corriente. Este cero NO significa que la cuenta no tenga saldo: pediste 2026-07; pide 2026-08 para obtenerlo."
},
{
"alcance": "movimientos",
"status": "ok",
"recordsSynced": 214
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_security.conexion.sincronizar",
"plane": "read",
"latency_ms": 58240,
"audit_status": "recorded"
}
}
```
> Con un período ya cerrado, 'saldos' devuelve 0 con su 'detalle': es una foto del momento y solo se sincroniza pidiendo el período corriente.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string | sí | Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú. |
| `results` | lista de objeto | sí | Una fila por alcance pedido, con cómo le fue a cada uno. |
| `results[].alcance` | string | sí | Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste. |
| `results[].status` | `"ok"` · `"failed"` | sí | 'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].recordsSynced` | entero | sí | Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'. |
| `results[].error` | string | no | Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos\_truncated', 'cartolas\_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre. |
| `results[].detalle` | string | no | Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú."
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"alcance": {
"type": "string",
"description": "Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste."
},
"status": {
"type": "string",
"enum": [
"ok",
"failed"
],
"description": "'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
},
"recordsSynced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'."
},
"error": {
"description": "Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos_truncated', 'cartolas_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre.",
"type": "string"
},
"detalle": {
"description": "Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo.",
"type": "string"
}
},
"required": [
"alcance",
"status",
"recordsSynced"
],
"additionalProperties": false
},
"description": "Una fila por alcance pedido, con cómo le fue a cada uno."
}
},
"required": [
"periodo",
"results"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_sync_in_progress` | 409 | sí | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `too_many_pending` | 429 | sí | Deja terminar los trabajos en curso antes de encolar más. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_security.transferencias.consultar`](./transferencias-consultar): lee el alcance `transferencias` que esta sincronización escribe.
* [`banco_security.nominas.consultar`](./nominas-consultar): lee el alcance `nominas` que esta sincronización escribe.
* [`banco_security.nomina_pagos.consultar`](./nomina_pagos-consultar): lee el alcance `nominas` que esta sincronización escribe.
* [`banco_security.saldos.consultar`](./saldos-consultar): lee el alcance `saldos` que esta sincronización escribe.
* [`banco_security.movimientos.consultar`](./movimientos-consultar): lee el alcance `movimientos` que esta sincronización escribe.
---
# Verificar conexión Banco Security
> Prueba las credenciales de la conexión contra Banco Security haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_security.conexion.verificar` |
| **Nombre MCP** | `banco_security__conexion__verificar` |
| **Conector** | `banco_security` |
| **Plano** | `action` |
| **Scope (permiso)** | `banco_security:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_security.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_security.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_security.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_security.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# Banco Security Empresas
> Las 7 tools de Banco Security Empresas en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | ---------------------------------------------------- |
| **Código** | `banco_security` |
| **Tipo** | `bank` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `portal_credentials` |
| **Alcances** | `transferencias`, `nominas`, `saldos`, `movimientos` |
| **Versión** | `1.0.0` |
## Tools [#tools]
* [`banco_security.conexion.sincronizar`](./conexion-sincronizar): Sincroniza los alcances solicitados (transferencias, nóminas, saldos, movimientos) para un período en una sola sesión (un login, un logout).
* [`banco_security.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra Banco Security haciendo un login real (y su logout, a cargo del pipeline).
* [`banco_security.movimientos.consultar`](./movimientos-consultar): Lee la caché ya sincronizada; NO contacta al banco.
* [`banco_security.nomina_pagos.consultar`](./nomina_pagos-consultar): Lee la caché ya sincronizada; NO contacta al banco.
* [`banco_security.nominas.consultar`](./nominas-consultar): Lee la caché ya sincronizada; NO contacta al banco.
* [`banco_security.saldos.consultar`](./saldos-consultar): Lee la caché ya sincronizada; NO contacta al banco.
* [`banco_security.transferencias.consultar`](./transferencias-consultar): Lee la caché ya sincronizada; NO contacta al banco.
---
# Consultar movimientos de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_security.movimientos.consultar` |
| **Nombre MCP** | `banco_security__movimientos__consultar` |
| **Conector** | `banco_security` |
| **Plano** | `action` |
| **Lee el alcance** | `movimientos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `banco_security:read` |
| **Auth** | `none` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve los movimientos de cuenta corriente guardados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM). Exige el alcance 'movimientos' habilitado. Sin 'periodo' devuelve todos los períodos sincronizados. Si el período nunca se sincronizó, devuelve una lista vacía (lo que NO significa que no haya movimientos): usa 'banco\_security.conexion.sincronizar' primero. El banco sirve el mes en curso y los meses ya cerrados por dos cartolas distintas, pero eso es interno: las dos escriben esta misma caché con la misma identidad por movimiento, así que un mes de solape NO aparece duplicado y los resultados se pueden sumar sin miedo. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------- | ---------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `cursor` | string | no | Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae la página, entre 1 y 500. Por omisión, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"cursor": {
"description": "Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae la página, entre 1 y 500. Por omisión, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.movimientos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_security.movimientos.consultar({ periodo: "2026-07" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_security.movimientos.consultar",
"params": {
"periodo": "2026-07"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"movimientos": [
{
"numeroCuenta": "915042876",
"currency": "CLP",
"periodo": "2026-07",
"fechaMovimiento": "2026-07-28T00:00:00.000Z",
"monto": 2380000,
"type": "credit",
"display": "-2.380.000",
"saldo": 12456789,
"descripcion": "TEF A DISTRIBUIDORA LOS ANDES LTDA",
"documento": "9081234",
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
},
{
"numeroCuenta": "915042876",
"currency": "CLP",
"periodo": "2026-07",
"fechaMovimiento": "2026-07-21T00:00:00.000Z",
"monto": 5490000,
"type": "debit",
"display": "5.490.000",
"saldo": 14836789,
"descripcion": "DEPOSITO TRANSFERENCIA DE FONDOS",
"documento": null,
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_security.movimientos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> El cargo del 28 de julio sale como 'credit' con 'display' negativo y el abono del 21 como 'debit' positivo (libro del banco); 'saldo' conserva su propio signo.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ------------------------------- | ---------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `movimientos` | lista de objeto | sí | Los movimientos guardados que calzan con los filtros, del más reciente al más antiguo. Una lista vacía significa que ese período no se ha sincronizado, no que no haya movimientos. | |
| `movimientos[].numeroCuenta` | string | sí | El número de la cuenta corriente a la que pertenece el movimiento. | |
| `movimientos[].periodo` | string | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva. | |
| `movimientos[].fechaMovimiento` | string | null | sí | Fecha del movimiento en la cartola (ISO 8601). null si el banco no la trajo. |
| `movimientos[].monto` | número | null | sí | Magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible lo trae 'display'. Reemplaza al par cargo/abono del portal, que obligaba a mirar cuál de las dos celdas venía llena. Un null significa que el banco no trajo la celda, que no es lo mismo que cero. |
| `movimientos[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `movimientos[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `movimientos[].saldo` | número | null | sí | El saldo que queda en la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo. |
| `movimientos[].descripcion` | string | null | sí | La glosa del movimiento tal como aparece en la cartola (por ejemplo 'TEF A PROVEEDOR LTDA'). |
| `movimientos[].documento` | string | null | sí | El número de documento asociado al movimiento, cuando el banco lo trae. |
| `movimientos[].ultimaLecturaEn` | string | sí | Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor. | |
| `movimientos[].currency` | string | sí | Moneda de la cuenta, en código ISO. Se deriva de la cuenta, así que viene igual por las dos cartolas del banco y nunca es null. | |
| `cursor` | string | null | sí | Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"movimientos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "El número de la cuenta corriente a la que pertenece el movimiento."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva."
},
"fechaMovimiento": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Fecha del movimiento en la cartola (ISO 8601). null si el banco no la trajo."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible lo trae 'display'. Reemplaza al par cargo/abono del portal, que obligaba a mirar cuál de las dos celdas venía llena. Un null significa que el banco no trajo la celda, que no es lo mismo que cero."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"saldo": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo que queda en la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo."
},
"descripcion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La glosa del movimiento tal como aparece en la cartola (por ejemplo 'TEF A PROVEEDOR LTDA')."
},
"documento": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El número de documento asociado al movimiento, cuando el banco lo trae."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor."
},
"currency": {
"type": "string",
"description": "Moneda de la cuenta, en código ISO. Se deriva de la cuenta, así que viene igual por las dos cartolas del banco y nunca es null."
}
},
"required": [
"numeroCuenta",
"periodo",
"fechaMovimiento",
"monto",
"type",
"display",
"saldo",
"descripcion",
"documento",
"ultimaLecturaEn",
"currency"
],
"additionalProperties": false
},
"description": "Los movimientos guardados que calzan con los filtros, del más reciente al más antiguo. Una lista vacía significa que ese período no se ha sincronizado, no que no haya movimientos."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer."
}
},
"required": [
"movimientos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_security.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar los pagos de una nómina de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_security.nomina_pagos.consultar` |
| **Nombre MCP** | `banco_security__nomina_pagos__consultar` |
| **Conector** | `banco_security` |
| **Plano** | `action` |
| **Lee el alcance** | `nominas` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `banco_security:read` |
| **Auth** | `none` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve las LÍNEAS DE PAGO de UNA nómina: el 'idNomina' es obligatorio y sale de 'banco\_security.nominas.consultar'. Exige el alcance 'nominas' habilitado (el mismo que las cabeceras). Las líneas salen en orden ascendente de 'linea'. Si esa nómina nunca se sincronizó, devuelve una lista vacía. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `idNomina` | string | sí | La nómina cuyas líneas de pago quieres leer. Sale del campo 'idNomina' de una cabecera de 'banco\_security.nominas.consultar'; no es el 'numeroNomina' que el banco muestra en su listado. |
| `cursor` | string | no | Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae la página, entre 1 y 500. Por omisión, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"idNomina": {
"type": "string",
"minLength": 1,
"description": "La nómina cuyas líneas de pago quieres leer. Sale del campo 'idNomina' de una cabecera de 'banco_security.nominas.consultar'; no es el 'numeroNomina' que el banco muestra en su listado."
},
"cursor": {
"description": "Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae la página, entre 1 y 500. Por omisión, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
},
"required": [
"idNomina"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.nomina_pagos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"idNomina":"611487"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_security.nomina_pagos.consultar({ idNomina: "611487" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_security.nomina_pagos.consultar",
"params": {
"idNomina": "611487"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"pagos": [
{
"idNomina": "611487",
"linea": 1,
"tipoCuenta": "Cuenta Corriente",
"estado": "Pagado",
"periodo": "2026-07",
"rut": "12.345.678-5",
"nombre": "María Fernanda Rojas Soto",
"numeroCuenta": "00123456789",
"banco": "Banco Estado",
"mail": "maria.rojas@ejemplo.cl",
"monto": 1480000,
"type": "credit",
"display": "-1.480.000",
"glosa": "Sueldo julio 2026",
"motivoRechazo": null,
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
},
{
"idNomina": "611487",
"linea": 2,
"tipoCuenta": "Cuenta Vista",
"estado": "Pagado",
"periodo": "2026-07",
"rut": "16.789.012-1",
"nombre": "Jorge Andrés Muñoz Vidal",
"numeroCuenta": "22334455",
"banco": "Banco de Chile",
"mail": null,
"monto": 1320000,
"type": "credit",
"display": "-1.320.000",
"glosa": "Sueldo julio 2026",
"motivoRechazo": null,
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_security.nomina_pagos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> El 'idNomina' viene de la cabecera que devuelve 'banco\_security.nominas.consultar' (en su ejemplo, la nómina 611487 declara 12 registros; aquí se muestran 2).
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ------------------------- | ---------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pagos` | lista de objeto | sí | Las líneas de pago de la nómina pedida, en orden ascendente de 'linea'. Una lista vacía significa que esa nómina no se ha sincronizado. | |
| `pagos[].idNomina` | string | sí | El identificador de la nómina a la que pertenece esta línea: el mismo que pediste en la entrada. | |
| `pagos[].linea` | entero | sí | Posición de esta línea dentro de la nómina, empezando en 1. Es un ordinal de la nómina completa, no de la página del portal de donde se leyó. | |
| `pagos[].tipoCuenta` | string | null | sí | Tipo de cuenta del beneficiario según el banco (por ejemplo 'Cuenta Corriente' o 'Cuenta Vista'). |
| `pagos[].estado` | string | null | sí | Estado del pago en el texto del propio banco (por ejemplo 'Pagado'). Cuando el banco lo rechazó, el motivo va en 'motivoRechazo'. |
| `pagos[].periodo` | string | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva. | |
| `pagos[].rut` | string | null | sí | RUT del beneficiario del pago, tal como lo escribe el banco. |
| `pagos[].nombre` | string | null | sí | Nombre del beneficiario del pago. |
| `pagos[].numeroCuenta` | string | null | sí | La cuenta de destino del beneficiario. Esta tool no ofrece filtro por cuenta: la columna va cifrada y un filtro sobre ella devolvería cero filas. |
| `pagos[].banco` | string | null | sí | Banco del beneficiario, en el texto del portal. |
| `pagos[].mail` | string | null | sí | Correo al que el banco avisó el pago al beneficiario, cuando lo hay. |
| `pagos[].monto` | número | null | sí | El monto de esta línea, como magnitud SIN signo. El sentido lo da 'type', que en un pago de nómina es siempre 'credit' porque la plata sale de la empresa. |
| `pagos[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `pagos[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `pagos[].glosa` | string | null | sí | La glosa con que el pago se identifica ante el beneficiario (por ejemplo 'Sueldo julio 2026'). |
| `pagos[].motivoRechazo` | string | null | sí | Por qué el banco rechazó este pago, en su propio texto. null cuando no hubo rechazo. |
| `pagos[].ultimaLecturaEn` | string | sí | Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor. | |
| `cursor` | string | null | sí | Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"pagos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"idNomina": {
"type": "string",
"description": "El identificador de la nómina a la que pertenece esta línea: el mismo que pediste en la entrada."
},
"linea": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Posición de esta línea dentro de la nómina, empezando en 1. Es un ordinal de la nómina completa, no de la página del portal de donde se leyó."
},
"tipoCuenta": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Tipo de cuenta del beneficiario según el banco (por ejemplo 'Cuenta Corriente' o 'Cuenta Vista')."
},
"estado": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Estado del pago en el texto del propio banco (por ejemplo 'Pagado'). Cuando el banco lo rechazó, el motivo va en 'motivoRechazo'."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva."
},
"rut": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "RUT del beneficiario del pago, tal como lo escribe el banco."
},
"nombre": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Nombre del beneficiario del pago."
},
"numeroCuenta": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La cuenta de destino del beneficiario. Esta tool no ofrece filtro por cuenta: la columna va cifrada y un filtro sobre ella devolvería cero filas."
},
"banco": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Banco del beneficiario, en el texto del portal."
},
"mail": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Correo al que el banco avisó el pago al beneficiario, cuando lo hay."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El monto de esta línea, como magnitud SIN signo. El sentido lo da 'type', que en un pago de nómina es siempre 'credit' porque la plata sale de la empresa."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"glosa": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La glosa con que el pago se identifica ante el beneficiario (por ejemplo 'Sueldo julio 2026')."
},
"motivoRechazo": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Por qué el banco rechazó este pago, en su propio texto. null cuando no hubo rechazo."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor."
}
},
"required": [
"idNomina",
"linea",
"tipoCuenta",
"estado",
"periodo",
"rut",
"nombre",
"numeroCuenta",
"banco",
"mail",
"monto",
"type",
"display",
"glosa",
"motivoRechazo",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Las líneas de pago de la nómina pedida, en orden ascendente de 'linea'. Una lista vacía significa que esa nómina no se ha sincronizado."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer."
}
},
"required": [
"pagos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_security.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar nóminas de pago de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_security.nominas.consultar` |
| **Nombre MCP** | `banco_security__nominas__consultar` |
| **Conector** | `banco_security` |
| **Plano** | `action` |
| **Lee el alcance** | `nominas` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `banco_security:read` |
| **Auth** | `none` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve SOLO las CABECERAS de las nóminas de pago masivas guardadas de esta conexión, de la más reciente a la más antigua, filtrables por período (AAAA-MM). Exige el alcance 'nominas' habilitado. Cada cabecera trae 'numRegistros' para que dimensiones antes de pedir el detalle: las líneas de pago se piden aparte con 'banco\_security.nomina\_pagos.consultar' pasándole el 'idNomina' de la cabecera (hay nóminas de más de mil líneas, por eso no vienen aquí). Si el período nunca se sincronizó, devuelve una lista vacía: usa 'banco\_security.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------- | ---------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `cursor` | string | no | Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae la página, entre 1 y 500. Por omisión, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"cursor": {
"description": "Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae la página, entre 1 y 500. Por omisión, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.nominas.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_security.nominas.consultar({ periodo: "2026-07" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_security.nominas.consultar",
"params": {
"periodo": "2026-07"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"nominas": [
{
"idNomina": "611487",
"numeroNomina": "2451",
"fecha": "2026-07-30T11:05:00.000Z",
"tipo": "Remuneraciones",
"estado": "Procesada",
"numRegistros": 12,
"cuentaCargo": "915042876",
"periodo": "2026-07",
"monto": 18450000,
"type": "credit",
"display": "-18.450.000",
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_security.nominas.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a una cabecera. Sus 12 líneas de pago se piden aparte con 'banco\_security.nomina\_pagos.consultar' pasando este 'idNomina'.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------- | ---------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nominas` | lista de objeto | sí | Las cabeceras de nómina guardadas que calzan con los filtros, de la más reciente a la más antigua. Las líneas de pago no vienen aquí: se piden con 'banco\_security.nomina\_pagos.consultar'. | |
| `nominas[].idNomina` | string | sí | El identificador de la nómina en el banco. Es el valor que pide 'banco\_security.nomina\_pagos.consultar' para traer sus líneas de pago. | |
| `nominas[].numeroNomina` | string | null | sí | El número con que el banco rotula la nómina en su listado. No sirve para pedir el detalle: para eso va 'idNomina'. |
| `nominas[].fecha` | string | null | sí | Fecha de la nómina según el banco (ISO 8601). null si no la informa. |
| `nominas[].tipo` | string | null | sí | Tipo de nómina en el texto del propio banco (por ejemplo 'Remuneraciones'), sin traducir. |
| `nominas[].estado` | string | null | sí | Estado de la nómina en el texto del propio banco (por ejemplo 'Procesada'), sin traducir. |
| `nominas[].numRegistros` | entero | null | sí | Cuántas líneas de pago tiene la nómina. Está aquí para que dimensiones antes de pedir el detalle con 'banco\_security.nomina\_pagos.consultar': hay nóminas de más de mil líneas. |
| `nominas[].cuentaCargo` | string | null | sí | La cuenta corriente de la empresa contra la que se cargó la nómina. |
| `nominas[].periodo` | string | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva. | |
| `nominas[].monto` | número | null | sí | El total de la nómina, como magnitud SIN signo. El sentido lo da 'type', que en una nómina es siempre 'credit' porque es un desembolso que la empresa origina. |
| `nominas[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `nominas[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `nominas[].ultimaLecturaEn` | string | sí | Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor. | |
| `cursor` | string | null | sí | Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"nominas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"idNomina": {
"type": "string",
"description": "El identificador de la nómina en el banco. Es el valor que pide 'banco_security.nomina_pagos.consultar' para traer sus líneas de pago."
},
"numeroNomina": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El número con que el banco rotula la nómina en su listado. No sirve para pedir el detalle: para eso va 'idNomina'."
},
"fecha": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Fecha de la nómina según el banco (ISO 8601). null si no la informa."
},
"tipo": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Tipo de nómina en el texto del propio banco (por ejemplo 'Remuneraciones'), sin traducir."
},
"estado": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Estado de la nómina en el texto del propio banco (por ejemplo 'Procesada'), sin traducir."
},
"numRegistros": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántas líneas de pago tiene la nómina. Está aquí para que dimensiones antes de pedir el detalle con 'banco_security.nomina_pagos.consultar': hay nóminas de más de mil líneas."
},
"cuentaCargo": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La cuenta corriente de la empresa contra la que se cargó la nómina."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El total de la nómina, como magnitud SIN signo. El sentido lo da 'type', que en una nómina es siempre 'credit' porque es un desembolso que la empresa origina."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor."
}
},
"required": [
"idNomina",
"numeroNomina",
"fecha",
"tipo",
"estado",
"numRegistros",
"cuentaCargo",
"periodo",
"monto",
"type",
"display",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Las cabeceras de nómina guardadas que calzan con los filtros, de la más reciente a la más antigua. Las líneas de pago no vienen aquí: se piden con 'banco_security.nomina_pagos.consultar'."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer."
}
},
"required": [
"nominas",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_security.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar saldos de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_security.saldos.consultar` |
| **Nombre MCP** | `banco_security__saldos__consultar` |
| **Conector** | `banco_security` |
| **Plano** | `action` |
| **Lee el alcance** | `saldos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `banco_security:read` |
| **Auth** | `none` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve los saldos guardados de esta conexión (contable, disponible y provisorio), con el snapshot más reciente primero. Exige el alcance 'saldos' habilitado. Si nunca se sincronizó, devuelve una lista vacía (eso NO significa que la empresa no tenga cuentas); usa 'banco\_security.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los más recientes que hay guardados. Los tres saldos vienen como NÚMERO ya normalizado (antes eran la celda cruda del banco, '$ 12.345.678'), y conservan su signo: un sobregiro es negativo. Un saldo no lleva 'type': no es una operación. 'ultimaLecturaEn' dice cuándo se leyó esa fila del banco: si es vieja, la conexión puede estar pausada. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas. reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `observedDay` | string `^\d{4}-\d{2}-\d{2}$` | no | Filtra por el día de la foto de saldo, en formato AAAA-MM-DD. Si lo omites, la primera página ya trae las fotos más recientes que haya guardadas. |
| `numeroCuenta` | string | no | Filtra por un número de cuenta. Omítelo para ver todas las cuentas de la conexión. |
| `cursor` | string | no | Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae la página, entre 1 y 500. Por omisión, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"observedDay": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Filtra por el día de la foto de saldo, en formato AAAA-MM-DD. Si lo omites, la primera página ya trae las fotos más recientes que haya guardadas."
},
"numeroCuenta": {
"description": "Filtra por un número de cuenta. Omítelo para ver todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae la página, entre 1 y 500. Por omisión, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.saldos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_security.saldos.consultar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_security.saldos.consultar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"saldos": [
{
"numeroCuenta": "915042876",
"currency": "CLP",
"observedDay": "2026-08-07",
"observedAt": "2026-08-07T07:15:38.000Z",
"saldoContable": 12845301,
"saldoDisponible": 12610301,
"saldoProvisorio": 235000,
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
},
{
"numeroCuenta": "915042884",
"currency": "USD",
"observedDay": "2026-08-07",
"observedAt": "2026-08-07T07:15:38.000Z",
"saldoContable": 15230.5,
"saldoDisponible": 15230.5,
"saldoProvisorio": 0,
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_security.saldos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Sin filtros, la primera página trae el snapshot más reciente de cada cuenta; la misma empresa puede tener cuentas en pesos y en dólares.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| -------------------------- | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `saldos` | lista de objeto | sí | Las fotos de saldo guardadas que calzan con los filtros, de la más reciente a la más antigua. Una lista vacía significa que la conexión nunca sincronizó saldos, no que la empresa no tenga cuentas. | |
| `saldos[].numeroCuenta` | string | sí | El número de la cuenta a la que corresponde esta foto de saldo. | |
| `saldos[].currency` | string | sí | Moneda de la cuenta, en código ISO ('CLP' o 'USD'). La misma empresa puede tener cuentas en pesos y en dólares, y cada una trae su propia fila. | |
| `saldos[].observedDay` | string | sí | El día de esta foto de saldo, en formato AAAA-MM-DD. Hay una foto por cuenta y por día: volver a sincronizar el mismo día actualiza esta fila en vez de agregar otra. | |
| `saldos[].observedAt` | string | sí | El instante (ISO 8601) en que se tomó la foto dentro de ese día. | |
| `saldos[].saldoContable` | número | null | sí | El saldo contable de la cuenta. Es un balance, no una operación: conserva su propio signo (un sobregiro es negativo) y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero. |
| `saldos[].saldoDisponible` | número | null | sí | El saldo disponible de la cuenta. Es un balance: conserva su propio signo y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero. |
| `saldos[].saldoProvisorio` | número | null | sí | El saldo provisorio de la cuenta, tal como lo publica el portal. Es un balance: conserva su propio signo y no lleva 'type'. Un null significa que el banco no trajo la celda. |
| `saldos[].ultimaLecturaEn` | string | sí | Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor. | |
| `cursor` | string | null | sí | Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"saldos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "El número de la cuenta a la que corresponde esta foto de saldo."
},
"currency": {
"type": "string",
"description": "Moneda de la cuenta, en código ISO ('CLP' o 'USD'). La misma empresa puede tener cuentas en pesos y en dólares, y cada una trae su propia fila."
},
"observedDay": {
"type": "string",
"description": "El día de esta foto de saldo, en formato AAAA-MM-DD. Hay una foto por cuenta y por día: volver a sincronizar el mismo día actualiza esta fila en vez de agregar otra."
},
"observedAt": {
"type": "string",
"description": "El instante (ISO 8601) en que se tomó la foto dentro de ese día."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo contable de la cuenta. Es un balance, no una operación: conserva su propio signo (un sobregiro es negativo) y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero."
},
"saldoDisponible": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo disponible de la cuenta. Es un balance: conserva su propio signo y no lleva 'type'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero."
},
"saldoProvisorio": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo provisorio de la cuenta, tal como lo publica el portal. Es un balance: conserva su propio signo y no lleva 'type'. Un null significa que el banco no trajo la celda."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor."
}
},
"required": [
"numeroCuenta",
"currency",
"observedDay",
"observedAt",
"saldoContable",
"saldoDisponible",
"saldoProvisorio",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Las fotos de saldo guardadas que calzan con los filtros, de la más reciente a la más antigua. Una lista vacía significa que la conexión nunca sincronizó saldos, no que la empresa no tenga cuentas."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer."
}
},
"required": [
"saldos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_security.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar transferencias de Banco Security
> Lee la caché ya sincronizada; NO contacta al banco.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `banco_security.transferencias.consultar` |
| **Nombre MCP** | `banco_security__transferencias__consultar` |
| **Conector** | `banco_security` |
| **Plano** | `action` |
| **Lee el alcance** | `transferencias` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `banco_security:read` |
| **Auth** | `none` |
| **Versión** | `4` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve las transferencias TEF guardadas de esta conexión, de la más reciente a la más antigua, filtrables por período (AAAA-MM) y por dirección. Exige el alcance 'transferencias' habilitado. Omitir 'direccion' trae enviadas y recibidas juntas; el campo 'direction' de cada fila las distingue ('issued' = enviada, 'received' = recibida). Si el período nunca se sincronizó, devuelve una lista vacía: usa 'banco\_security.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. Un saldo NO lleva 'type': es un balance y conserva su propio signo. 'numeroTransaccion' sí es texto (tiene 14 dígitos y no entra en un entero de 32 bits). 'ultimaLecturaEn' dice cuándo se leyó esa fila del banco. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas. reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ----------- | ---------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `direccion` | `"enviadas"` · `"recibidas"` | no | Filtra por dirección: 'enviadas' son las que la empresa cursó y 'recibidas' las que le llegaron. Omítelo para traer las dos juntas; el campo 'direction' de cada fila las distingue. |
| `cursor` | string | no | Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae la página, entre 1 y 500. Por omisión, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"direccion": {
"description": "Filtra por dirección: 'enviadas' son las que la empresa cursó y 'recibidas' las que le llegaron. Omítelo para traer las dos juntas; el campo 'direction' de cada fila las distingue.",
"type": "string",
"enum": [
"enviadas",
"recibidas"
]
},
"cursor": {
"description": "Puntero opaco a la página siguiente. Reenvía tal cual el 'cursor' que devolvió la llamada anterior; nunca lo construyas a mano. Omítelo para pedir la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae la página, entre 1 y 500. Por omisión, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/banco_security.transferencias.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","direccion":"enviadas"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.banco_security.transferencias.consultar({ periodo: "2026-07", direccion: "enviadas" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "banco_security.transferencias.consultar",
"params": {
"periodo": "2026-07",
"direccion": "enviadas"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"transferencias": [
{
"sourceTransferId": "issued:39262015878491",
"direction": "issued",
"periodo": "2026-07",
"numeroTransaccion": "39262015878491",
"transferredAt": "2026-07-15T14:23:08.000Z",
"currency": "CLP",
"statusCode": 1,
"transferType": "Transferencia a terceros",
"nominaNumber": null,
"ownRut": "77123456-9",
"ownAccount": "915042876",
"monto": 2380000,
"type": "credit",
"display": "-2.380.000",
"subject": "Pago factura 10452",
"counterpartyRut": "76543210-3",
"counterpartyName": "Distribuidora Los Andes Ltda",
"counterpartyBank": "Banco de Chile",
"counterpartyAccount": "001234567801",
"counterpartyEmail": "pagos@ejemplo.cl",
"creatorRut": "12345678-5",
"approverRut": "9876543-3",
"payerRut": "77123456-9",
"ultimaLecturaEn": "2026-08-07T07:15:42.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "banco_security.transferencias.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a una transferencia enviada. 'type' es 'credit' porque la plata SALE (libro del banco) y 'display' ya la muestra en negativo.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| -------------------------------------- | ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transferencias` | lista de objeto | sí | Las transferencias guardadas que calzan con los filtros, de la más reciente a la más antigua. Una lista vacía significa que ese período no se ha sincronizado, no que no haya transferencias. | |
| `transferencias[].sourceTransferId` | string | sí | Identificador estable de la transferencia dentro de Connect, compuesto por la dirección y el número de transacción (por ejemplo 'issued:39262015878491'). Sirve para reconocer la misma transferencia entre dos lecturas; el banco no lo muestra. | |
| `transferencias[].direction` | `"issued"` · `"received"` | sí | 'issued' es una transferencia que la empresa envió y 'received' una que recibió. Es el campo que las distingue cuando consultas sin filtrar por 'direccion'. | |
| `transferencias[].periodo` | string | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva. | |
| `transferencias[].numeroTransaccion` | string | sí | El número de transacción que emite el banco. Viene como texto a propósito: tiene unos 14 dígitos y no cabe en un entero de 32 bits. Trátalo como identificador y no lo conviertas a número. | |
| `transferencias[].transferredAt` | string | null | sí | Cuándo se cursó la transferencia (ISO 8601). null si el banco no trajo la fecha. |
| `transferencias[].currency` | string | sí | Moneda de la transferencia, en código ISO ('CLP' o 'USD'). | |
| `transferencias[].statusCode` | entero | null | sí | El código de estado que el banco asigna a la transferencia, tal cual, sin traducir. Solo viene en las enviadas ('issued'); en las recibidas es null. |
| `transferencias[].transferType` | string | null | sí | Tipo de transferencia en el texto del propio banco (por ejemplo 'Transferencia a terceros'). null cuando no lo informa. |
| `transferencias[].nominaNumber` | entero | null | sí | Número de la nómina de pago masiva de la que salió esta transferencia. null significa que no vino de una nómina. |
| `transferencias[].ownRut` | string | null | sí | RUT del lado propio de la operación: el de origen si la empresa envió la transferencia, el de destino si la recibió. |
| `transferencias[].ownAccount` | string | null | sí | Número de la cuenta propia en esta transferencia: la de origen si la empresa la envió, la de destino si la recibió. |
| `transferencias[].monto` | número | null | sí | Magnitud de la transferencia SIN signo. El sentido lo da 'type' y el signo visible lo trae 'display'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero. |
| `transferencias[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `transferencias[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `transferencias[].subject` | string | null | sí | El asunto con que se cursó la transferencia, tal como lo escribió quien la hizo (por ejemplo 'Pago factura 10452'). |
| `transferencias[].counterpartyRut` | string | null | sí | RUT de la contraparte: el destinatario si la transferencia salió, el emisor si entró. |
| `transferencias[].counterpartyName` | string | null | sí | Nombre de la contraparte, tal como lo informa el banco. |
| `transferencias[].counterpartyBank` | string | null | sí | Banco de la contraparte, en el texto del portal (por ejemplo 'Banco de Chile'). |
| `transferencias[].counterpartyAccount` | string | null | sí | Número de cuenta de la contraparte. |
| `transferencias[].counterpartyEmail` | string | null | sí | Correo al que el banco avisó la transferencia a la contraparte, cuando lo hay. |
| `transferencias[].creatorRut` | string | null | sí | RUT de quien creó la transferencia en el portal. Solo viene en las enviadas ('issued'); en las recibidas es null. |
| `transferencias[].approverRut` | string | null | sí | RUT de quien la aprobó en el portal. Solo viene en las enviadas ('issued'); en las recibidas es null. |
| `transferencias[].payerRut` | string | null | sí | RUT que el banco registra como pagador de la transferencia. Solo viene en las enviadas ('issued'); en las recibidas es null. |
| `transferencias[].ultimaLecturaEn` | string | sí | Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor. | |
| `cursor` | string | null | sí | Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"transferencias": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sourceTransferId": {
"type": "string",
"description": "Identificador estable de la transferencia dentro de Connect, compuesto por la dirección y el número de transacción (por ejemplo 'issued:39262015878491'). Sirve para reconocer la misma transferencia entre dos lecturas; el banco no lo muestra."
},
"direction": {
"type": "string",
"enum": [
"issued",
"received"
],
"description": "'issued' es una transferencia que la empresa envió y 'received' una que recibió. Es el campo que las distingue cuando consultas sin filtrar por 'direccion'."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es cómo se pidió el dato, no una propiedad del objeto: no entra en su identidad, así que volver a traerlo bajo otro período no crea una fila nueva."
},
"numeroTransaccion": {
"type": "string",
"description": "El número de transacción que emite el banco. Viene como texto a propósito: tiene unos 14 dígitos y no cabe en un entero de 32 bits. Trátalo como identificador y no lo conviertas a número."
},
"transferredAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo se cursó la transferencia (ISO 8601). null si el banco no trajo la fecha."
},
"currency": {
"type": "string",
"description": "Moneda de la transferencia, en código ISO ('CLP' o 'USD')."
},
"statusCode": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "El código de estado que el banco asigna a la transferencia, tal cual, sin traducir. Solo viene en las enviadas ('issued'); en las recibidas es null."
},
"transferType": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Tipo de transferencia en el texto del propio banco (por ejemplo 'Transferencia a terceros'). null cuando no lo informa."
},
"nominaNumber": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Número de la nómina de pago masiva de la que salió esta transferencia. null significa que no vino de una nómina."
},
"ownRut": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "RUT del lado propio de la operación: el de origen si la empresa envió la transferencia, el de destino si la recibió."
},
"ownAccount": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Número de la cuenta propia en esta transferencia: la de origen si la empresa la envió, la de destino si la recibió."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Magnitud de la transferencia SIN signo. El sentido lo da 'type' y el signo visible lo trae 'display'. Un null significa que el banco no trajo la celda, que no es lo mismo que cero."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"subject": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El asunto con que se cursó la transferencia, tal como lo escribió quien la hizo (por ejemplo 'Pago factura 10452')."
},
"counterpartyRut": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "RUT de la contraparte: el destinatario si la transferencia salió, el emisor si entró."
},
"counterpartyName": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Nombre de la contraparte, tal como lo informa el banco."
},
"counterpartyBank": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Banco de la contraparte, en el texto del portal (por ejemplo 'Banco de Chile')."
},
"counterpartyAccount": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Número de cuenta de la contraparte."
},
"counterpartyEmail": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Correo al que el banco avisó la transferencia a la contraparte, cuando lo hay."
},
"creatorRut": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "RUT de quien creó la transferencia en el portal. Solo viene en las enviadas ('issued'); en las recibidas es null."
},
"approverRut": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "RUT de quien la aprobó en el portal. Solo viene en las enviadas ('issued'); en las recibidas es null."
},
"payerRut": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "RUT que el banco registra como pagador de la transferencia. Solo viene en las enviadas ('issued'); en las recibidas es null."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se leyó esta fila del banco (ISO 8601). Si está vieja, la caché puede haber dejado de moverse (por ejemplo, con la conexión pausada tras varios fallos de credencial) mientras esta tool sigue respondiendo con filas antiguas. Entre dos filas del mismo hecho, gana la de 'ultimaLecturaEn' mayor."
}
},
"required": [
"sourceTransferId",
"direction",
"periodo",
"numeroTransaccion",
"transferredAt",
"currency",
"statusCode",
"transferType",
"nominaNumber",
"ownRut",
"ownAccount",
"monto",
"type",
"display",
"subject",
"counterpartyRut",
"counterpartyName",
"counterpartyBank",
"counterpartyAccount",
"counterpartyEmail",
"creatorRut",
"approverRut",
"payerRut",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Las transferencias guardadas que calzan con los filtros, de la más reciente a la más antigua. Una lista vacía significa que ese período no se ha sincronizado, no que no haya transferencias."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Puntero a la página siguiente. Si viene distinto de null hay más filas: vuelve a llamar reenviándolo tal cual en 'cursor'. Un null significa que no queda nada por traer."
}
},
"required": [
"transferencias",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`banco_security.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Sincronizar conexión BCI
> Sincroniza los alcances solicitados (saldos, movimientos) para un período en una sola sesión (un login, un logout).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bci_pyme.conexion.sincronizar` |
| **Nombre MCP** | `bci_pyme__conexion__sincronizar` |
| **Conector** | `bci_pyme` |
| **Plano** | `read` |
| **Alcances** | `saldos`, `movimientos` |
| **Scope (permiso)** | `bci_pyme:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
`saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | sí | El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes. |
| `alcances` | lista de `"saldos"` · `"movimientos"` | sí | Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance\_not\_enabled'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes."
},
"alcances": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"saldos",
"movimientos"
]
},
"description": "Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance_not_enabled'."
}
},
"required": [
"periodo",
"alcances"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bci_pyme.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","alcances":["saldos","movimientos"]}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bci_pyme.conexion.sincronizar({ periodo: "2026-07", alcances: ["saldos", "movimientos"] }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bci_pyme.conexion.sincronizar",
"params": {
"periodo": "2026-07",
"alcances": [
"saldos",
"movimientos"
]
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"periodo": "2026-07",
"results": [
{
"alcance": "saldos",
"status": "ok",
"recordsSynced": 0,
"detalle": "Los saldos son una foto del momento, no del período, así que solo se sincronizan en el período corriente. Este cero NO significa que la cuenta no tenga saldo: pediste 2026-07; pide 2026-08 para obtenerlo."
},
{
"alcance": "movimientos",
"status": "ok",
"recordsSynced": 2
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "bci_pyme.conexion.sincronizar",
"plane": "read",
"latency_ms": 58240,
"audit_status": "recorded"
}
}
```
> Se pidió julio siendo agosto el período corriente: 'movimientos' sincroniza ese mes, y 'saldos' (foto del momento, no del período) devuelve 0 con la explicación en 'detalle'. En el período corriente ambos alcances traen datos.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string | sí | Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú. |
| `results` | lista de objeto | sí | Una fila por alcance pedido, en el orden canónico del conector. Revísalas todas: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].alcance` | string | sí | Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste. |
| `results[].status` | `"ok"` · `"failed"` | sí | 'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].recordsSynced` | entero | sí | Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'. |
| `results[].error` | string | no | Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos\_truncated', 'cartolas\_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre. |
| `results[].detalle` | string | no | Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú."
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"alcance": {
"type": "string",
"description": "Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste."
},
"status": {
"type": "string",
"enum": [
"ok",
"failed"
],
"description": "'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
},
"recordsSynced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'."
},
"error": {
"description": "Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos_truncated', 'cartolas_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre.",
"type": "string"
},
"detalle": {
"description": "Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo.",
"type": "string"
}
},
"required": [
"alcance",
"status",
"recordsSynced"
],
"additionalProperties": false
},
"description": "Una fila por alcance pedido, en el orden canónico del conector. Revísalas todas: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
}
},
"required": [
"periodo",
"results"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_sync_in_progress` | 409 | sí | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `too_many_pending` | 429 | sí | Deja terminar los trabajos en curso antes de encolar más. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bci_pyme.saldos.consultar`](./saldos-consultar): lee el alcance `saldos` que esta sincronización escribe.
* [`bci_pyme.movimientos.consultar`](./movimientos-consultar): lee el alcance `movimientos` que esta sincronización escribe.
---
# Verificar conexión BCI
> Prueba las credenciales de la conexión contra BCI haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bci_pyme.conexion.verificar` |
| **Nombre MCP** | `bci_pyme__conexion__verificar` |
| **Conector** | `bci_pyme` |
| **Plano** | `action` |
| **Scope (permiso)** | `bci_pyme:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bci_pyme.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bci_pyme.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bci_pyme.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "bci_pyme.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bci_pyme.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# Banco BCI Empresas
> Las 4 tools de Banco BCI Empresas en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | --------------------------- |
| **Código** | `bci_pyme` |
| **Tipo** | `bank` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `portal_credentials` |
| **Alcances** | `saldos`, `movimientos` |
| **Versión** | `1.0.0` |
## Tools [#tools]
* [`bci_pyme.conexion.sincronizar`](./conexion-sincronizar): Sincroniza los alcances solicitados (saldos, movimientos) para un período en una sola sesión (un login, un logout).
* [`bci_pyme.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra BCI haciendo un login real (y su logout, a cargo del pipeline).
* [`bci_pyme.movimientos.consultar`](./movimientos-consultar): Lee la caché ya sincronizada; NO contacta al banco.
* [`bci_pyme.saldos.consultar`](./saldos-consultar): Lee la caché ya sincronizada; NO contacta al banco.
---
# Consultar movimientos de BCI
> Lee la caché ya sincronizada; NO contacta al banco.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bci_pyme.movimientos.consultar` |
| **Nombre MCP** | `bci_pyme__movimientos__consultar` |
| **Conector** | `bci_pyme` |
| **Plano** | `action` |
| **Lee el alcance** | `movimientos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `bci_pyme:read` |
| **Auth** | `none` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve los movimientos guardados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM) y por cuenta. Exige el alcance 'movimientos' habilitado. Si el período nunca se sincronizó, devuelve una lista vacía, que NO significa que no haya movimientos; usa 'bci\_pyme.conexion.sincronizar' primero. 'completo' dice si el último sync de ESE período trajo todo: BCI corta en 1000 movimientos por cuenta y mes, y 'completo: false' significa que faltan filas. Sin filtro de 'periodo' vale null, o sea «no se sabe». Ojo con las correcciones del banco: un movimiento corregido entra como fila NUEVA en vez de reemplazar a la anterior, así que ante dos filas del mismo movimiento vale la de 'ultimaLecturaEn' mayor. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola) y 'display' es ese monto ya formateado a la chilena con su signo. 'saldoContable' es un balance: no lleva 'type' y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por el mes (AAAA-MM) con el que se sincronizó la fila. Sin él, la respuesta cruza todos los períodos guardados y 'completo' llega en null. |
| `numeroCuenta` | string | no | Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión. |
| `cursor` | string | no | Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae una página, entre 1 y 500. Por defecto, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"description": "Filtra por el mes (AAAA-MM) con el que se sincronizó la fila. Sin él, la respuesta cruza todos los períodos guardados y 'completo' llega en null.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}$"
},
"numeroCuenta": {
"description": "Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae una página, entre 1 y 500. Por defecto, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bci_pyme.movimientos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bci_pyme.movimientos.consultar({ periodo: "2026-07" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bci_pyme.movimientos.consultar",
"params": {
"periodo": "2026-07"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"movimientos": [
{
"numeroCuenta": "78012345",
"periodo": "2026-07",
"fechaMovimiento": "2026-07-28T00:00:00.000Z",
"fechaContable": "2026-07-28T00:00:00.000Z",
"descripcion": "Pago a proveedor",
"monto": 890750,
"type": "credit",
"display": "-890.750",
"saldoContable": 4370480,
"category": "Transferencias",
"mnemonico": "TRF",
"counterparty": {
"name": "Proveedora del Maule SpA",
"rut": "77123456-9",
"bank": "Banco de Chile",
"account": null
},
"ultimaLecturaEn": "2026-08-01T07:12:45.310Z"
},
{
"numeroCuenta": "78012345",
"periodo": "2026-07",
"fechaMovimiento": "2026-07-15T00:00:00.000Z",
"fechaContable": "2026-07-15T00:00:00.000Z",
"descripcion": "Abono cliente",
"monto": 1450000,
"type": "debit",
"display": "1.450.000",
"saldoContable": 5261230,
"category": "Depositos",
"mnemonico": "DEP",
"counterparty": {
"name": "Distribuidora Andina Ltda",
"rut": "76543210-3",
"bank": null,
"account": null
},
"ultimaLecturaEn": "2026-08-01T07:12:45.310Z"
}
],
"cursor": null,
"completo": true
},
"meta": {
"request_id": "req_…",
"tool_id": "bci_pyme.movimientos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> El abono del cliente entra como 'debit' y el pago al proveedor como 'credit': es la convención del libro del banco, al revés de la cartola, y 'display' ya trae el signo aplicado. 'completo' en true porque la llamada filtró por período y el último sync de ese mes trajo todo.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ------------------------------------ | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `movimientos` | lista de objeto | sí | Los movimientos guardados, del más reciente al más antiguo. Una lista vacía significa que ese período todavía no se sincronizó, no que no haya movimientos. | |
| `movimientos[].numeroCuenta` | string | sí | El número de la cuenta a la que pertenece esta fila, tal como lo entrega el portal de BCI. Es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'. | |
| `movimientos[].periodo` | string | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es la ventana con que se pidió, no una propiedad del movimiento: la fecha vive en 'fechaMovimiento'. Es el valor con el que filtra el 'periodo' de la entrada. | |
| `movimientos[].fechaMovimiento` | string | null | sí | La fecha del movimiento (ISO 8601), tal como la entrega el banco. 'null' cuando no la trajo. |
| `movimientos[].fechaContable` | string | null | sí | La fecha contable del movimiento (ISO 8601), que puede diferir de 'fechaMovimiento'. 'null' cuando el banco no la trajo. |
| `movimientos[].descripcion` | string | null | sí | La glosa del movimiento, tal como la escribe el banco. 'null' cuando llega vacía. |
| `movimientos[].monto` | número | null | sí | La magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible, 'display'. 'null' significa que el banco no trajo la celda, nunca 0. |
| `movimientos[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `movimientos[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `movimientos[].saldoContable` | número | null | sí | El saldo de la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo, así que un sobregiro es negativo. |
| `movimientos[].category` | string | null | sí | La categoría con que el propio BCI clasifica el movimiento (por ejemplo 'Transferencias'). Es una etiqueta del banco, no un vocabulario de Connect: puede cambiar sin aviso. 'null' cuando el banco no la trae. |
| `movimientos[].mnemonico` | string | null | sí | El código corto de transacción del propio BCI (por ejemplo 'TRF'). Es una etiqueta del banco sin catálogo publicado: sirve para agrupar movimientos del mismo tipo, no para deducir qué fue la operación. 'null' cuando el banco no lo trae. |
| `movimientos[].counterparty` | objeto | sí | La contraparte del movimiento, extraída del detalle que adjunta el banco. Los cuatro campos vienen en 'null' cuando el movimiento no trae detalle, que es lo normal fuera de las transferencias. Son datos personales de terceros: trátalos como tales. | |
| `movimientos[].counterparty.name` | string | null | sí | El nombre o razón social de la contraparte. 'null' cuando el detalle no lo trae. |
| `movimientos[].counterparty.rut` | string | null | sí | El RUT de la contraparte, tal cual. 'null' cuando el detalle no lo trae. |
| `movimientos[].counterparty.bank` | string | null | sí | El banco de la contraparte. 'null' cuando el detalle no lo trae. |
| `movimientos[].counterparty.account` | string | null | sí | El número de cuenta de la contraparte. 'null' cuando el detalle no lo trae. |
| `movimientos[].ultimaLecturaEn` | string | sí | Instante (ISO 8601) en que esta fila se leyó del banco por última vez. Una corrección del banco entra como fila NUEVA en vez de reemplazar a la anterior, así que ante dos filas del mismo movimiento vale la de 'ultimaLecturaEn' mayor. | |
| `cursor` | string | null | sí | El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página. |
| `completo` | booleano | null | sí | Si el último sync de ESE período trajo todo. BCI corta en 1000 movimientos por cuenta y mes, así que 'false' significa que faltan filas del mes. 'null' significa «no se sabe», y es lo que devuelve una consulta sin filtro de 'periodo': nunca lo leas como un 'true'. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"movimientos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "El número de la cuenta a la que pertenece esta fila, tal como lo entrega el portal de BCI. Es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es la ventana con que se pidió, no una propiedad del movimiento: la fecha vive en 'fechaMovimiento'. Es el valor con el que filtra el 'periodo' de la entrada."
},
"fechaMovimiento": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La fecha del movimiento (ISO 8601), tal como la entrega el banco. 'null' cuando no la trajo."
},
"fechaContable": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La fecha contable del movimiento (ISO 8601), que puede diferir de 'fechaMovimiento'. 'null' cuando el banco no la trajo."
},
"descripcion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La glosa del movimiento, tal como la escribe el banco. 'null' cuando llega vacía."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "La magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible, 'display'. 'null' significa que el banco no trajo la celda, nunca 0."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo de la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo, así que un sobregiro es negativo."
},
"category": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La categoría con que el propio BCI clasifica el movimiento (por ejemplo 'Transferencias'). Es una etiqueta del banco, no un vocabulario de Connect: puede cambiar sin aviso. 'null' cuando el banco no la trae."
},
"mnemonico": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El código corto de transacción del propio BCI (por ejemplo 'TRF'). Es una etiqueta del banco sin catálogo publicado: sirve para agrupar movimientos del mismo tipo, no para deducir qué fue la operación. 'null' cuando el banco no lo trae."
},
"counterparty": {
"type": "object",
"properties": {
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El nombre o razón social de la contraparte. 'null' cuando el detalle no lo trae."
},
"rut": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El RUT de la contraparte, tal cual. 'null' cuando el detalle no lo trae."
},
"bank": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El banco de la contraparte. 'null' cuando el detalle no lo trae."
},
"account": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El número de cuenta de la contraparte. 'null' cuando el detalle no lo trae."
}
},
"required": [
"name",
"rut",
"bank",
"account"
],
"additionalProperties": false,
"description": "La contraparte del movimiento, extraída del detalle que adjunta el banco. Los cuatro campos vienen en 'null' cuando el movimiento no trae detalle, que es lo normal fuera de las transferencias. Son datos personales de terceros: trátalos como tales."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Instante (ISO 8601) en que esta fila se leyó del banco por última vez. Una corrección del banco entra como fila NUEVA en vez de reemplazar a la anterior, así que ante dos filas del mismo movimiento vale la de 'ultimaLecturaEn' mayor."
}
},
"required": [
"numeroCuenta",
"periodo",
"fechaMovimiento",
"fechaContable",
"descripcion",
"monto",
"type",
"display",
"saldoContable",
"category",
"mnemonico",
"counterparty",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Los movimientos guardados, del más reciente al más antiguo. Una lista vacía significa que ese período todavía no se sincronizó, no que no haya movimientos."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página."
},
"completo": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "Si el último sync de ESE período trajo todo. BCI corta en 1000 movimientos por cuenta y mes, así que 'false' significa que faltan filas del mes. 'null' significa «no se sabe», y es lo que devuelve una consulta sin filtro de 'periodo': nunca lo leas como un 'true'."
}
},
"required": [
"movimientos",
"cursor",
"completo"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bci_pyme.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar saldos de BCI
> Lee la caché ya sincronizada; NO contacta al banco.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bci_pyme.saldos.consultar` |
| **Nombre MCP** | `bci_pyme__saldos__consultar` |
| **Conector** | `bci_pyme` |
| **Plano** | `action` |
| **Lee el alcance** | `saldos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `bci_pyme:read` |
| **Auth** | `none` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve los saldos guardados de esta conexión (contable, disponible, 9AM y retención), con el snapshot más reciente primero. Exige el alcance 'saldos' habilitado. Si nunca se sincronizó, devuelve una lista vacía: eso NO significa que la empresa no tenga cuentas. Para traer datos nuevos usa 'bci\_pyme.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los más recientes que hay guardados. Los cuatro saldos vienen como NÚMERO ya normalizado y conservan su signo: un sobregiro es negativo. Un saldo no lleva 'type' (no es una operación). 'ultimaLecturaEn' dice cuándo se leyó esa fila del banco: si es vieja, la conexión puede estar pausada. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `observedDay` | string `^\d{4}-\d{2}-\d{2}$` | no | Filtra por el día del snapshot, en formato AAAA-MM-DD. Sin él, la primera página ya trae los saldos más recientes que hay guardados de cada cuenta. |
| `numeroCuenta` | string | no | Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión. |
| `cursor` | string | no | Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae una página, entre 1 y 500. Por defecto, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"observedDay": {
"description": "Filtra por el día del snapshot, en formato AAAA-MM-DD. Sin él, la primera página ya trae los saldos más recientes que hay guardados de cada cuenta.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"numeroCuenta": {
"description": "Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae una página, entre 1 y 500. Por defecto, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bci_pyme.saldos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bci_pyme.saldos.consultar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bci_pyme.saldos.consultar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"saldos": [
{
"numeroCuenta": "78012345",
"currency": "CLP",
"observedDay": "2026-08-07",
"observedAt": "2026-08-07T11:02:19.412Z",
"saldoContable": 4820500,
"saldoDisponible": 4715300,
"saldoContable9am": 4820500,
"retencion": 105200,
"ultimaLecturaEn": "2026-08-07T11:02:23.958Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "bci_pyme.saldos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Sin filtros, la primera página ya es el snapshot más reciente de cada cuenta; 'cursor' null significa que no hay más páginas. Un saldo en 0 es un cero real; null significaría que el banco no trajo la celda.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------- | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `saldos` | lista de objeto | sí | Los snapshots de saldo guardados, el más reciente primero. Una lista vacía significa que esta conexión todavía no se sincronizó, no que la empresa no tenga cuentas. | |
| `saldos[].numeroCuenta` | string | sí | El número de la cuenta a la que pertenece esta fila, tal como lo entrega el portal de BCI. Es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'. | |
| `saldos[].currency` | string | sí | La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta. | |
| `saldos[].observedDay` | string | sí | El día (AAAA-MM-DD) de esta foto de saldos. Los saldos se guardan como un snapshot por día, así que sin filtro de fecha la primera página ya trae el más reciente de cada cuenta. | |
| `saldos[].observedAt` | string | sí | El instante exacto (ISO 8601) en que se tomó la foto, dentro del día de 'observedDay'. Todas las cuentas de una misma sincronización comparten este valor. | |
| `saldos[].saldoContable` | número | null | sí | El saldo contable de la cuenta, como número y con su propio signo (un sobregiro es negativo). 'null' significa que el banco no trajo la celda, nunca 0: un 0 es un saldo real. |
| `saldos[].saldoDisponible` | número | null | sí | El saldo disponible de la cuenta, con el mismo criterio de signo y de 'null' que 'saldoContable'. |
| `saldos[].saldoContable9am` | número | null | sí | El saldo contable de las 9 de la mañana, que BCI publica como un campo aparte de los otros tres. Mismo criterio de signo y de 'null' que 'saldoContable'. |
| `saldos[].retencion` | número | null | sí | El monto retenido que BCI informa junto a los saldos. 'null' significa que el banco no trajo la celda, nunca 0. |
| `saldos[].ultimaLecturaEn` | string | sí | Instante (ISO 8601) en que esta fila se leyó del banco por última vez. La caché puede quedarse quieta sin que la consulta falle (una conexión se auto-pausa tras tres fallos de credencial), así que este campo es lo que distingue un saldo recién leído de uno viejo. | |
| `cursor` | string | null | sí | El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"saldos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "El número de la cuenta a la que pertenece esta fila, tal como lo entrega el portal de BCI. Es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'."
},
"currency": {
"type": "string",
"description": "La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). Sale de la cuenta."
},
"observedDay": {
"type": "string",
"description": "El día (AAAA-MM-DD) de esta foto de saldos. Los saldos se guardan como un snapshot por día, así que sin filtro de fecha la primera página ya trae el más reciente de cada cuenta."
},
"observedAt": {
"type": "string",
"description": "El instante exacto (ISO 8601) en que se tomó la foto, dentro del día de 'observedDay'. Todas las cuentas de una misma sincronización comparten este valor."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo contable de la cuenta, como número y con su propio signo (un sobregiro es negativo). 'null' significa que el banco no trajo la celda, nunca 0: un 0 es un saldo real."
},
"saldoDisponible": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo disponible de la cuenta, con el mismo criterio de signo y de 'null' que 'saldoContable'."
},
"saldoContable9am": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo contable de las 9 de la mañana, que BCI publica como un campo aparte de los otros tres. Mismo criterio de signo y de 'null' que 'saldoContable'."
},
"retencion": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El monto retenido que BCI informa junto a los saldos. 'null' significa que el banco no trajo la celda, nunca 0."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Instante (ISO 8601) en que esta fila se leyó del banco por última vez. La caché puede quedarse quieta sin que la consulta falle (una conexión se auto-pausa tras tres fallos de credencial), así que este campo es lo que distingue un saldo recién leído de uno viejo."
}
},
"required": [
"numeroCuenta",
"currency",
"observedDay",
"observedAt",
"saldoContable",
"saldoDisponible",
"saldoContable9am",
"retencion",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Los snapshots de saldo guardados, el más reciente primero. Una lista vacía significa que esta conexión todavía no se sincronizó, no que la empresa no tenga cuentas."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página."
}
},
"required": [
"saldos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bci_pyme.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Sincronizar conexión BICE
> Sincroniza los alcances solicitados (saldos, movimientos) para un período en una sola sesión de portal.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bice_empresas.conexion.sincronizar` |
| **Nombre MCP** | `bice_empresas__conexion__sincronizar` |
| **Conector** | `bice_empresas` |
| **Plano** | `read` |
| **Alcances** | `saldos`, `movimientos` |
| **Scope (permiso)** | `bice_empresas:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
`saldos` es una foto del momento, no del período: solo se sincroniza cuando se pide el período corriente.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | sí | El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes. |
| `alcances` | lista de `"saldos"` · `"movimientos"` | sí | Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance\_not\_enabled'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes."
},
"alcances": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"saldos",
"movimientos"
]
},
"description": "Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance_not_enabled'."
}
},
"required": [
"periodo",
"alcances"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bice_empresas.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08","alcances":["saldos","movimientos"]}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bice_empresas.conexion.sincronizar({ periodo: "2026-08", alcances: ["saldos", "movimientos"] }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bice_empresas.conexion.sincronizar",
"params": {
"periodo": "2026-08",
"alcances": [
"saldos",
"movimientos"
]
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"periodo": "2026-08",
"results": [
{
"alcance": "saldos",
"status": "ok",
"recordsSynced": 1
},
{
"alcance": "movimientos",
"status": "ok",
"recordsSynced": 18,
"cuentasConsultadas": 1
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "bice_empresas.conexion.sincronizar",
"plane": "read",
"latency_ms": 58240,
"audit_status": "recorded"
}
}
```
> El primer login de BICE abre un desafío de navegador remoto (Turnstile) y puede tardar cerca de un minuto; las sincronizaciones siguientes reutilizan la sesión de portal vigente.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ------------------------------ | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string | sí | Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú. |
| `results` | lista de objeto | sí | Una fila por alcance pedido, en el orden canónico del conector. Revísalas todas: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].alcance` | string | sí | Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste. |
| `results[].status` | `"ok"` · `"failed"` | sí | 'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].recordsSynced` | entero | sí | Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'. |
| `results[].error` | string | no | Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos\_truncated', 'cartolas\_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre. |
| `results[].cuentasConsultadas` | entero | no | Cuántas cuentas se alcanzaron a consultar. Es lo que vuelve interpretable un 'recordsSynced: 0': cero con una cuenta consultada significa que el banco no tiene movimientos ahí, y cero con cero cuentas significa que ni siquiera se llegó a preguntar. Los dos casos traen el mismo 0, así que revisa este campo antes de reportar «no hay datos». |
| `results[].fueraDeVentana` | booleano | no | 'true' cuando el banco no ofrece cartola para ese período en esa cuenta: el mes no está disponible, que es distinto de un mes sin movimientos. No lo reportes como «no hubo movimientos»; prueba un mes más reciente. |
| `results[].detalle` | string | no | Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú."
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"alcance": {
"type": "string",
"description": "Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste."
},
"status": {
"type": "string",
"enum": [
"ok",
"failed"
],
"description": "'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
},
"recordsSynced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'."
},
"error": {
"description": "Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos_truncated', 'cartolas_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre.",
"type": "string"
},
"cuentasConsultadas": {
"description": "Cuántas cuentas se alcanzaron a consultar. Es lo que vuelve interpretable un 'recordsSynced: 0': cero con una cuenta consultada significa que el banco no tiene movimientos ahí, y cero con cero cuentas significa que ni siquiera se llegó a preguntar. Los dos casos traen el mismo 0, así que revisa este campo antes de reportar «no hay datos».",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"fueraDeVentana": {
"description": "'true' cuando el banco no ofrece cartola para ese período en esa cuenta: el mes no está disponible, que es distinto de un mes sin movimientos. No lo reportes como «no hubo movimientos»; prueba un mes más reciente.",
"type": "boolean"
},
"detalle": {
"description": "Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo.",
"type": "string"
}
},
"required": [
"alcance",
"status",
"recordsSynced"
],
"additionalProperties": false
},
"description": "Una fila por alcance pedido, en el orden canónico del conector. Revísalas todas: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
}
},
"required": [
"periodo",
"results"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_session_pending` | 409 | sí | Ejecuta la sincronización de esa conexión (ella acuña la sesión) o espera la programada, y reintenta. |
| `connection_sync_in_progress` | 409 | sí | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `too_many_pending` | 429 | sí | Deja terminar los trabajos en curso antes de encolar más. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bice_empresas.saldos.consultar`](./saldos-consultar): lee el alcance `saldos` que esta sincronización escribe.
* [`bice_empresas.movimientos.consultar`](./movimientos-consultar): lee el alcance `movimientos` que esta sincronización escribe.
---
# Verificar conexión BICE
> Prueba las credenciales de la conexión contra BICE haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bice_empresas.conexion.verificar` |
| **Nombre MCP** | `bice_empresas__conexion__verificar` |
| **Conector** | `bice_empresas` |
| **Plano** | `action` |
| **Scope (permiso)** | `bice_empresas:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bice_empresas.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bice_empresas.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bice_empresas.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "bice_empresas.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_session_pending` | 409 | sí | Ejecuta la sincronización de esa conexión (ella acuña la sesión) o espera la programada, y reintenta. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bice_empresas.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# Banco BICE Empresas
> Las 4 tools de Banco BICE Empresas en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | --------------------------- |
| **Código** | `bice_empresas` |
| **Tipo** | `bank` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `portal_credentials` |
| **Alcances** | `saldos`, `movimientos` |
| **Versión** | `1.0.0` |
## Tools [#tools]
* [`bice_empresas.conexion.sincronizar`](./conexion-sincronizar): Sincroniza los alcances solicitados (saldos, movimientos) para un período en una sola sesión de portal.
* [`bice_empresas.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra BICE haciendo un login real (y su logout, a cargo del pipeline).
* [`bice_empresas.movimientos.consultar`](./movimientos-consultar): Lee los movimientos ya sincronizados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM), por cuenta y por 'type' (el eje credit/debit del libro del banco: 'debit' para los abonos, 'credit' para los cargos).
* [`bice_empresas.saldos.consultar`](./saldos-consultar): Lee los saldos ya sincronizados de esta conexión, con el snapshot más reciente primero.
---
# Consultar movimientos de BICE Empresas
> Lee los movimientos ya sincronizados de esta conexión, del más reciente al más antiguo, filtrables por período (AAAA-MM), por cuenta y por 'type' (el eje credit/debit del libro del banco: 'debit' para los abonos, 'credit' para los cargos).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bice_empresas.movimientos.consultar` |
| **Nombre MCP** | `bice_empresas__movimientos__consultar` |
| **Conector** | `bice_empresas` |
| **Plano** | `action` |
| **Lee el alcance** | `movimientos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `bice_empresas:read` |
| **Auth** | `none` |
| **Versión** | `4` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO contacta al banco ni dispara una sincronización. Si el período nunca se sincronizó, devuelve una lista vacía, que NO significa que no haya movimientos. Para traer datos nuevos, usa 'bice\_empresas.conexion.sincronizar' primero. Los montos vienen como NÚMERO: 'monto' es la magnitud sin signo, 'type' dice si entra ('debit') o sale ('credit') plata según el libro del banco (al revés de como se lee una cartola), y 'display' es ese monto ya formateado a la chilena con su signo. 'saldoContable' es un balance: no lleva 'type' y conserva su propio signo. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas. reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por el mes (AAAA-MM) con el que se sincronizó la fila. Como la cartola de BICE corre de fin de mes a fin de mes, un período puede traer movimientos fechados en los últimos días del mes anterior. Sin él, la respuesta cruza todos los períodos guardados. |
| `numeroCuenta` | string | no | Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión. |
| `type` | `"credit"` · `"debit"` | no | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `cursor` | string | no | Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae una página, entre 1 y 500. Por defecto, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"description": "Filtra por el mes (AAAA-MM) con el que se sincronizó la fila. Como la cartola de BICE corre de fin de mes a fin de mes, un período puede traer movimientos fechados en los últimos días del mes anterior. Sin él, la respuesta cruza todos los períodos guardados.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}$"
},
"numeroCuenta": {
"description": "Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión.",
"type": "string"
},
"type": {
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos.",
"type": "string",
"enum": [
"credit",
"debit"
]
},
"cursor": {
"description": "Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae una página, entre 1 y 500. Por defecto, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bice_empresas.movimientos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bice_empresas.movimientos.consultar({ periodo: "2026-07" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bice_empresas.movimientos.consultar",
"params": {
"periodo": "2026-07"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"movimientos": [
{
"id": "9c41f2ab7d305e8812aef6c40b9d73f56a28c1e9d47b0f3685c2ea193f6d08b7",
"numeroCuenta": "07203344",
"currency": "CLP",
"periodo": "2026-07",
"fecha": "2026-07-28T00:00:00.000Z",
"monto": 1250000,
"type": "credit",
"display": "-1.250.000",
"saldoContable": 4825310,
"descripcion": "Transferencia vía Electrónica a Proveedores Andinos Ltda",
"documento": "451208763"
},
{
"id": "e07a5c13b98d24f641c6a0d98f3e57b22d91c8e476f0a3b5c45d19e80a72f6c3",
"numeroCuenta": "07203344",
"currency": "CLP",
"periodo": "2026-07",
"fecha": "2026-07-15T00:00:00.000Z",
"monto": 348500,
"type": "debit",
"display": "348.500",
"saldoContable": 6075310,
"descripcion": "Depósito Transferencia de Fondos",
"documento": "048112954"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "bice_empresas.movimientos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a dos movimientos. 'id' es la huella estable con la que se guardó el movimiento, la misma por cualquiera de las dos rutas del banco. La cartola de BICE corre de fin de mes a fin de mes: el período 2026-07 puede incluir movimientos fechados el 30 de junio.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ----------------------------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `movimientos` | lista de objeto | sí | Los movimientos guardados, del más reciente al más antiguo. Una lista vacía significa que ese período todavía no se sincronizó, no que no haya movimientos. | |
| `movimientos[].id` | string | sí | La identidad estable del movimiento: la huella con la que se guardó. El mismo movimiento llega por dos rutas del banco con formatos distintos y por las dos trae este mismo 'id', así que sirve para deduplicar el día que comparten dos cartolas sin comparar campos de presentación. | |
| `movimientos[].numeroCuenta` | string | sí | El identificador ESTABLE de la cuenta a la que pertenece esta fila. No es la máscara que muestra el portal en pantalla, que cambia en cada sesión: es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'. | |
| `movimientos[].currency` | string | sí | La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). BICE la manda a veces como código numérico y aquí ya viene traducida a las tres letras. | |
| `movimientos[].periodo` | string | sí | El mes (AAAA-MM) con el que se sincronizó esta fila. Es la ventana con que se pidió, no una propiedad del movimiento: la cartola de BICE corre de fin de mes a fin de mes, así que el período '2026-07' incluye movimientos fechados el 30 de junio. La fecha vive en 'fecha'. | |
| `movimientos[].fecha` | string | null | sí | La fecha del movimiento (ISO 8601), tal como la entrega el banco. Puede caer en el mes anterior al de 'periodo', porque la cartola de BICE corre de fin de mes a fin de mes. 'null' cuando el banco no la trajo en una forma reconocible. |
| `movimientos[].monto` | número | null | sí | La magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible, 'display'. 'null' cuando ni el débito ni el crédito traen un valor distinto de cero: el banco manda las dos celdas siempre y escribe 0 en la que no aplica, así que ese 0 no es un movimiento de cero. |
| `movimientos[].type` | `"credit"` · `"debit"` | null | sí | Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos. |
| `movimientos[].display` | string | null | sí | El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'. |
| `movimientos[].saldoContable` | número | null | sí | El saldo de la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo, así que un sobregiro es negativo. |
| `movimientos[].descripcion` | string | sí | La glosa del movimiento, tal como la escribe el banco. Cadena vacía cuando el banco no la trae. | |
| `movimientos[].documento` | string | sí | El número de documento del movimiento, tal como lo entrega el banco. Ojo al compararlo: una ruta del banco lo manda con nueve dígitos y la otra truncado a los últimos ocho, así que dos textos distintos pueden ser el mismo documento. Para saber si dos filas son el mismo movimiento usa 'id'. Cadena vacía cuando el banco no lo trae. | |
| `cursor` | string | null | sí | El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"movimientos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "La identidad estable del movimiento: la huella con la que se guardó. El mismo movimiento llega por dos rutas del banco con formatos distintos y por las dos trae este mismo 'id', así que sirve para deduplicar el día que comparten dos cartolas sin comparar campos de presentación."
},
"numeroCuenta": {
"type": "string",
"description": "El identificador ESTABLE de la cuenta a la que pertenece esta fila. No es la máscara que muestra el portal en pantalla, que cambia en cada sesión: es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'."
},
"currency": {
"type": "string",
"description": "La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). BICE la manda a veces como código numérico y aquí ya viene traducida a las tres letras."
},
"periodo": {
"type": "string",
"description": "El mes (AAAA-MM) con el que se sincronizó esta fila. Es la ventana con que se pidió, no una propiedad del movimiento: la cartola de BICE corre de fin de mes a fin de mes, así que el período '2026-07' incluye movimientos fechados el 30 de junio. La fecha vive en 'fecha'."
},
"fecha": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La fecha del movimiento (ISO 8601), tal como la entrega el banco. Puede caer en el mes anterior al de 'periodo', porque la cartola de BICE corre de fin de mes a fin de mes. 'null' cuando el banco no la trajo en una forma reconocible."
},
"monto": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "La magnitud del movimiento SIN signo. El sentido lo da 'type' y el signo visible, 'display'. 'null' cuando ni el débito ni el crédito traen un valor distinto de cero: el banco manda las dos celdas siempre y escribe 0 en la que no aplica, así que ese 0 no es un movimiento de cero."
},
"type": {
"anyOf": [
{
"type": "string",
"enum": [
"credit",
"debit"
]
},
{
"type": "null"
}
],
"description": "Eje crédito/débito del LIBRO DEL BANCO, no el de la cartola: 'debit' es plata que ENTRA a la cuenta (un abono) y 'credit' es plata que SALE (un cargo). Es al revés de la lectura intuitiva y está así a propósito. El campo 'monto' es la magnitud SIN signo; el signo lo lleva 'display' ('credit' → negativo). 'null' significa que el banco no informó el tipo: no asumas ninguno de los dos."
},
"display": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El monto ya formateado a la chilena y CON signo, derivado de 'type' ('credit', plata que sale, se muestra negativo). Es una comodidad de presentación: se calcula en la lectura y no se persiste. Para operar con el número usa 'monto' (magnitud sin signo) junto con 'type'."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo de la cuenta después de este movimiento. Es un balance: no lleva 'type' y conserva su propio signo, así que un sobregiro es negativo."
},
"descripcion": {
"type": "string",
"description": "La glosa del movimiento, tal como la escribe el banco. Cadena vacía cuando el banco no la trae."
},
"documento": {
"type": "string",
"description": "El número de documento del movimiento, tal como lo entrega el banco. Ojo al compararlo: una ruta del banco lo manda con nueve dígitos y la otra truncado a los últimos ocho, así que dos textos distintos pueden ser el mismo documento. Para saber si dos filas son el mismo movimiento usa 'id'. Cadena vacía cuando el banco no lo trae."
}
},
"required": [
"id",
"numeroCuenta",
"currency",
"periodo",
"fecha",
"monto",
"type",
"display",
"saldoContable",
"descripcion",
"documento"
],
"additionalProperties": false
},
"description": "Los movimientos guardados, del más reciente al más antiguo. Una lista vacía significa que ese período todavía no se sincronizó, no que no haya movimientos."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página."
}
},
"required": [
"movimientos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bice_empresas.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar saldos de BICE Empresas
> Lee los saldos ya sincronizados de esta conexión, con el snapshot más reciente primero.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `bice_empresas.saldos.consultar` |
| **Nombre MCP** | `bice_empresas__saldos__consultar` |
| **Conector** | `bice_empresas` |
| **Plano** | `action` |
| **Lee el alcance** | `saldos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `bice_empresas:read` |
| **Auth** | `none` |
| **Versión** | `3` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO contacta al banco ni dispara una sincronización. Si nunca se sincronizó, devuelve una lista vacía. Para traer datos nuevos, usa 'bice\_empresas.conexion.sincronizar' primero. Los saldos son un snapshot POR DÍA, así que sin filtro de fecha la primera página ya son los saldos más recientes que hay guardados. Los dos saldos vienen como NÚMERO y conservan su signo: un sobregiro es negativo. Un saldo no lleva 'type' (no es una operación). Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas: reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ---------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `observedDay` | string `^\d{4}-\d{2}-\d{2}$` | no | Filtra por el día del snapshot, en formato AAAA-MM-DD. Sin él, la primera página ya trae los saldos más recientes que hay guardados de cada cuenta. |
| `numeroCuenta` | string | no | Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión. |
| `cursor` | string | no | Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas trae una página, entre 1 y 500. Por defecto, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"observedDay": {
"description": "Filtra por el día del snapshot, en formato AAAA-MM-DD. Sin él, la primera página ya trae los saldos más recientes que hay guardados de cada cuenta.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"numeroCuenta": {
"description": "Filtra por una sola cuenta, escrita igual que el 'numeroCuenta' de las filas. Sin él vienen todas las cuentas de la conexión.",
"type": "string"
},
"cursor": {
"description": "Para pedir la página siguiente: el valor que la respuesta anterior devolvió en 'cursor', tal cual. Nunca lo construyas ni lo edites a mano. Omítelo para empezar por la primera página.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas trae una página, entre 1 y 500. Por defecto, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/bice_empresas.saldos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.bice_empresas.saldos.consultar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "bice_empresas.saldos.consultar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"saldos": [
{
"numeroCuenta": "07203344",
"numProducto": "07203344",
"currency": "CLP",
"observedDay": "2026-08-07",
"observedAt": "2026-08-07T13:05:12.000Z",
"saldoContable": 5214890,
"saldoDisponible": 5158210
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "bice_empresas.saldos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> 'numeroCuenta' y 'numProducto' traen el mismo valor a propósito: ambos guardan el identificador estable de la cuenta (nunca la máscara de sesión del portal).
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| -------------------------- | --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `saldos` | lista de objeto | sí | Los snapshots de saldo guardados, el más reciente primero. Una lista vacía significa que esta conexión todavía no se sincronizó, no que la empresa no tenga cuentas. | |
| `saldos[].numeroCuenta` | string | sí | El identificador ESTABLE de la cuenta a la que pertenece esta fila. No es la máscara que muestra el portal en pantalla, que cambia en cada sesión: es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'. | |
| `saldos[].numProducto` | string | sí | El mismo identificador estable de la cuenta que 'numeroCuenta'. Los dos campos traen el mismo valor a propósito: es el nombre con el que el propio BICE lo pide en sus llamadas. | |
| `saldos[].currency` | string | sí | La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). BICE la manda a veces como código numérico y aquí ya viene traducida a las tres letras. | |
| `saldos[].observedDay` | string | sí | El día (AAAA-MM-DD) de esta foto de saldos. Los saldos se guardan como un snapshot por día, así que sin filtro de fecha la primera página ya trae el más reciente de cada cuenta. | |
| `saldos[].observedAt` | string | sí | El instante exacto (ISO 8601) en que se tomó la foto, dentro del día de 'observedDay'. | |
| `saldos[].saldoContable` | número | null | sí | El saldo contable de la cuenta, como número y con su propio signo (un sobregiro es negativo). 'null' significa que el banco no trajo la celda, nunca 0: un 0 es un saldo real. |
| `saldos[].saldoDisponible` | número | null | sí | El saldo disponible de la cuenta, con el mismo criterio de signo y de 'null' que 'saldoContable'. |
| `cursor` | string | null | sí | El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"saldos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroCuenta": {
"type": "string",
"description": "El identificador ESTABLE de la cuenta a la que pertenece esta fila. No es la máscara que muestra el portal en pantalla, que cambia en cada sesión: es el mismo valor en saldos y movimientos, y el que espera el filtro 'numeroCuenta'."
},
"numProducto": {
"type": "string",
"description": "El mismo identificador estable de la cuenta que 'numeroCuenta'. Los dos campos traen el mismo valor a propósito: es el nombre con el que el propio BICE lo pide en sus llamadas."
},
"currency": {
"type": "string",
"description": "La moneda de la cuenta, en código de tres letras (por ejemplo 'CLP'). BICE la manda a veces como código numérico y aquí ya viene traducida a las tres letras."
},
"observedDay": {
"type": "string",
"description": "El día (AAAA-MM-DD) de esta foto de saldos. Los saldos se guardan como un snapshot por día, así que sin filtro de fecha la primera página ya trae el más reciente de cada cuenta."
},
"observedAt": {
"type": "string",
"description": "El instante exacto (ISO 8601) en que se tomó la foto, dentro del día de 'observedDay'."
},
"saldoContable": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo contable de la cuenta, como número y con su propio signo (un sobregiro es negativo). 'null' significa que el banco no trajo la celda, nunca 0: un 0 es un saldo real."
},
"saldoDisponible": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El saldo disponible de la cuenta, con el mismo criterio de signo y de 'null' que 'saldoContable'."
}
},
"required": [
"numeroCuenta",
"numProducto",
"currency",
"observedDay",
"observedAt",
"saldoContable",
"saldoDisponible"
],
"additionalProperties": false
},
"description": "Los snapshots de saldo guardados, el más reciente primero. Una lista vacía significa que esta conexión todavía no se sincronizó, no que la empresa no tenga cuentas."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente. Distinto de null significa que quedan más filas: reenvíalo tal cual en 'cursor'. 'null' significa que esta es la última página."
}
},
"required": [
"saldos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`bice_empresas.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Crear un enlace para conectar un sistema
> Crea un enlace de un solo uso donde la persona entrega sus credenciales del sistema para conectarlo.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | -------------------------------------------------------------------- |
| **Tool ID** | `conexiones.enlace.crear` |
| **Nombre MCP** | `conexiones__enlace__crear` |
| **Conector** | `conexiones` |
| **Plano** | `action` |
| **Scope (permiso)** | `conexiones:write` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=false, openWorld=false |
## Qué hace [#qué-hace]
Devuelve el enlace SIEMPRE con su dominio completo visible y explicando quién lo pidió y para qué; nunca lo presentes como un aviso del banco ni del SII. La credencial se cifra en el vault de esta misma organización y no la ve nadie más, tampoco tú. El enlace vence y sirve una sola vez. Después de que la persona lo complete, usa 'conexiones.estado.consultar' para saber si ya hay datos.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------ | -------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------- |
| `sistema` | string | sí | Código del sistema, tal como lo devuelve conexiones.sistemas.listar. |
| `modo` | `"crear"` · `"reconectar"` | no · default `"crear"` | 'crear' para una conexión nueva; 'reconectar' para renovar la credencial de una que ya existe. |
| `conexionId` | string | no | conn\_…, obligatorio cuando modo es 'reconectar'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sistema": {
"type": "string",
"description": "Código del sistema, tal como lo devuelve conexiones.sistemas.listar."
},
"modo": {
"default": "crear",
"description": "'crear' para una conexión nueva; 'reconectar' para renovar la credencial de una que ya existe.",
"type": "string",
"enum": [
"crear",
"reconectar"
]
},
"conexionId": {
"description": "conn_…, obligatorio cuando modo es 'reconectar'.",
"type": "string"
}
},
"required": [
"sistema"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/conexiones.enlace.crear/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input":{"sistema":"sii"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.conexiones.enlace.crear({ sistema: "sii" });
```
```json title="MCP · meta-tool execute"
{
"tool": "conexiones.enlace.crear",
"params": {
"sistema": "sii"
}
}
```
**Salida esperada (200):**
```json
{
"data": {
"url": "https://connect.emisso.ai/c/mYw2kQ81xR4tPnZs",
"dominio": "connect.emisso.ai",
"sistema": "sii",
"sistemaNombre": "Servicio de Impuestos Internos",
"modo": "crear",
"expiraEn": "2026-08-07T15:32:11.000Z",
"intentosMaximos": 5,
"advertencia": "Este enlace pide credenciales de acceso al sistema. Muéstralo siempre con su dominio completo y di quién lo pidió y para qué. No lo presentes como un aviso del banco ni del SII."
},
"meta": {
"request_id": "req_…",
"tool_id": "conexiones.enlace.crear",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> El enlace vence una hora después de acuñado y sirve una sola vez.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ----------------- | -------------------------- | --------- | ------------------------------------------------------------------------------ |
| `url` | string | sí | El enlace de un solo uso. Compártelo tal cual, con el dominio visible. |
| `dominio` | string | sí | El dominio del enlace, ya extraído para mostrarlo junto a la URL. |
| `sistema` | string | sí | Código del sistema que se va a conectar. |
| `sistemaNombre` | string | sí | Nombre del sistema, para presentar el enlace con claridad. |
| `modo` | `"crear"` · `"reconectar"` | sí | Si el enlace crea una conexión nueva o renueva la credencial de una existente. |
| `expiraEn` | string | sí | Cuándo vence el enlace (ISO 8601). Vencido, hay que crear otro. |
| `intentosMaximos` | entero | sí | Cuántos intentos de login admite antes de agotarse. |
| `advertencia` | string | sí | Texto de presentación segura del enlace, listo para mostrar a la persona. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "El enlace de un solo uso. Compártelo tal cual, con el dominio visible."
},
"dominio": {
"type": "string",
"description": "El dominio del enlace, ya extraído para mostrarlo junto a la URL."
},
"sistema": {
"type": "string",
"description": "Código del sistema que se va a conectar."
},
"sistemaNombre": {
"type": "string",
"description": "Nombre del sistema, para presentar el enlace con claridad."
},
"modo": {
"type": "string",
"enum": [
"crear",
"reconectar"
],
"description": "Si el enlace crea una conexión nueva o renueva la credencial de una existente."
},
"expiraEn": {
"type": "string",
"description": "Cuándo vence el enlace (ISO 8601). Vencido, hay que crear otro."
},
"intentosMaximos": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos intentos de login admite antes de agotarse."
},
"advertencia": {
"type": "string",
"description": "Texto de presentación segura del enlace, listo para mostrar a la persona."
}
},
"required": [
"url",
"dominio",
"sistema",
"sistemaNombre",
"modo",
"expiraEn",
"intentosMaximos",
"advertencia"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Consultar el estado de las conexiones
> Dice en qué va cada conexión: si la credencial quedó vinculada, qué sincronizaciones corrieron y si YA HAY DATOS para consultar ('datosListos').
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `conexiones.estado.consultar` |
| **Nombre MCP** | `conexiones__estado__consultar` |
| **Conector** | `conexiones` |
| **Plano** | `action` |
| **Scope (permiso)** | `conexiones:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | no |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
## Qué hace [#qué-hace]
Úsala después de que la persona complete un enlace, y antes de intentar leer: un listado vacío no significa que no haya nada, puede ser que todavía no sincronizó. La primera sincronización de un sistema con navegador puede tardar cerca de un minuto. El campo 'herramientas' trae los ids que ya puedes invocar; si tu cliente MCP todavía no los muestra en su lista, invócalos con la herramienta 'execute' pasando el id en 'tool'.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------ | ------ | --------- | ------------------------------------- |
| `sistema` | string | no | Filtra por código de sistema. |
| `conexionId` | string | no | conn\_…, filtra una conexión puntual. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sistema": {
"description": "Filtra por código de sistema.",
"type": "string"
},
"conexionId": {
"description": "conn_…, filtra una conexión puntual.",
"type": "string"
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/conexiones.estado.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input":{"sistema":"sii"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.conexiones.estado.consultar({ sistema: "sii" });
```
```json title="MCP · meta-tool execute"
{
"tool": "conexiones.estado.consultar",
"params": {
"sistema": "sii"
}
}
```
**Salida esperada (200):**
```json
{
"data": {
"conexiones": [
{
"id": "conn_9tKfR2mQx4Vb",
"sistema": "sii",
"nombre": "Comercial Aurora SpA",
"estado": "active",
"credencial": "linked",
"verificadaEn": "2026-08-07T14:12:03.220Z",
"alcances": [
"rcv",
"boletas"
],
"cadencia": "daily",
"trabajos": [
{
"id": "sjb_k2Rw81QpLm3N",
"estado": "succeeded",
"periodo": "2026-07",
"alcances": [
"rcv"
],
"registros": 214,
"error": null
}
],
"datosListos": true,
"herramientas": [
"sii.conexion.sincronizar",
"sii.conexion.verificar",
"sii.rcv.consultar",
"sii.boletas.consultar"
]
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "conexiones.estado.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> datosListos true: al menos una sincronización terminó bien y no hay trabajos pendientes, así que las tools de consulta ya tienen qué responder.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ----------------------------------- | ------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conexiones` | lista de objeto | sí | Las conexiones de esta organización que pasan el filtro, con su estado y sus últimas sincronizaciones. | |
| `conexiones[].id` | string | sí | El id de la conexión (conn\_…). Es lo que va en la cabecera 'X-Connect-Connection' de cada llamada a este sistema. | |
| `conexiones[].sistema` | string | sí | El código del sistema al que pertenece esta conexión. | |
| `conexiones[].nombre` | string | sí | El nombre con que se creó la conexión, normalmente la empresa a la que pertenece la credencial. | |
| `conexiones[].estado` | `"active"` · `"disabled"` · `"pending"` | sí | En qué estado está la conexión. 'active' = utilizable. 'disabled' = pausada, sus tools responden 'connection\_disabled'. 'pending' = creada pero todavía sin credencial vinculada. | |
| `conexiones[].credencial` | `"linked"` · `"invalid"` · `"revoked"` · `"ausente"` | sí | En qué estado está la credencial de esta conexión. 'linked' = vinculada y utilizable. 'invalid' = el sistema externo la rechazó, hay que reconectar. 'revoked' = se revocó a propósito. 'ausente' = nunca se entregó. Todo lo que no sea 'linked' se arregla con 'conexiones.enlace.crear' en modo 'reconectar'. | |
| `conexiones[].verificadaEn` | string | null | sí | Cuándo se probó por última vez la credencial contra el sistema externo (ISO 8601). 'null' si nunca se probó. Una fecha vieja no invalida la credencial por sí sola. |
| `conexiones[].alcances` | lista de string | sí | Los módulos de datos habilitados en esta conexión. Pedir uno que no esté en esta lista responde 'alcance\_not\_enabled'. | |
| `conexiones[].cadencia` | `"off"` · `"daily"` · `"12h"` · `"6h"` | null | sí | Cada cuánto sincroniza sola esta conexión. 'off' o 'null' significan que nadie la sincroniza por ti: llama a la tool 'conexion.sincronizar' del sistema cuando quieras datos frescos. |
| `conexiones[].trabajos` | lista de objeto | sí | Las últimas sincronizaciones programadas de esta conexión, de la más reciente a la más antigua. | |
| `conexiones[].trabajos[].id` | string | sí | El id de esta sincronización programada (sjb\_…). | |
| `conexiones[].trabajos[].estado` | `"queued"` · `"running"` · `"succeeded"` · `"failed"` · `"partial"` | sí | En qué va la sincronización. 'queued' y 'running' significan que todavía está trabajando: espera antes de concluir que no hay datos. 'succeeded' terminó bien, 'partial' escribió una parte y 'failed' no escribió nada, con la causa en 'error'. | |
| `conexiones[].trabajos[].periodo` | string | sí | El mes que sincronizó esta corrida, en formato AAAA-MM. | |
| `conexiones[].trabajos[].alcances` | lista de string | sí | Qué módulos de datos abarcó esta corrida. | |
| `conexiones[].trabajos[].registros` | entero | null | sí | Cuántos registros escribió. 'null' significa que todavía no se sabe (la corrida no terminó), y es distinto de un 0, que sí es un resultado: ese período no tenía nada. |
| `conexiones[].trabajos[].error` | string | null | sí | La causa de la falla cuando 'estado' es 'failed'. 'null' en cualquier otro caso. |
| `conexiones[].datosListos` | booleano | sí | La respuesta a «¿ya puedo leer?»: alguna sincronización terminó bien y ninguna está en curso. Revísalo antes de concluir que no hay datos, porque un listado vacío con 'datosListos' en false significa «espera», no «no tienes nada». Un período legítimamente sin registros cuenta como sincronización exitosa: este campo no mira si hay filas. | |
| `conexiones[].herramientas` | lista de string | sí | Los ids de tool que ya puedes invocar sobre esta conexión. Si tu cliente MCP todavía no los muestra, llámalos con 'execute' pasando el id en 'tool'. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"conexiones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "El id de la conexión (conn_…). Es lo que va en la cabecera 'X-Connect-Connection' de cada llamada a este sistema."
},
"sistema": {
"type": "string",
"description": "El código del sistema al que pertenece esta conexión."
},
"nombre": {
"type": "string",
"description": "El nombre con que se creó la conexión, normalmente la empresa a la que pertenece la credencial."
},
"estado": {
"type": "string",
"enum": [
"active",
"disabled",
"pending"
],
"description": "En qué estado está la conexión. 'active' = utilizable. 'disabled' = pausada, sus tools responden 'connection_disabled'. 'pending' = creada pero todavía sin credencial vinculada."
},
"credencial": {
"type": "string",
"enum": [
"linked",
"invalid",
"revoked",
"ausente"
],
"description": "En qué estado está la credencial de esta conexión. 'linked' = vinculada y utilizable. 'invalid' = el sistema externo la rechazó, hay que reconectar. 'revoked' = se revocó a propósito. 'ausente' = nunca se entregó. Todo lo que no sea 'linked' se arregla con 'conexiones.enlace.crear' en modo 'reconectar'."
},
"verificadaEn": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo se probó por última vez la credencial contra el sistema externo (ISO 8601). 'null' si nunca se probó. Una fecha vieja no invalida la credencial por sí sola."
},
"alcances": {
"type": "array",
"items": {
"type": "string"
},
"description": "Los módulos de datos habilitados en esta conexión. Pedir uno que no esté en esta lista responde 'alcance_not_enabled'."
},
"cadencia": {
"anyOf": [
{
"type": "string",
"enum": [
"off",
"daily",
"12h",
"6h"
]
},
{
"type": "null"
}
],
"description": "Cada cuánto sincroniza sola esta conexión. 'off' o 'null' significan que nadie la sincroniza por ti: llama a la tool 'conexion.sincronizar' del sistema cuando quieras datos frescos."
},
"trabajos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "El id de esta sincronización programada (sjb_…)."
},
"estado": {
"type": "string",
"enum": [
"queued",
"running",
"succeeded",
"failed",
"partial"
],
"description": "En qué va la sincronización. 'queued' y 'running' significan que todavía está trabajando: espera antes de concluir que no hay datos. 'succeeded' terminó bien, 'partial' escribió una parte y 'failed' no escribió nada, con la causa en 'error'."
},
"periodo": {
"type": "string",
"description": "El mes que sincronizó esta corrida, en formato AAAA-MM."
},
"alcances": {
"type": "array",
"items": {
"type": "string"
},
"description": "Qué módulos de datos abarcó esta corrida."
},
"registros": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántos registros escribió. 'null' significa que todavía no se sabe (la corrida no terminó), y es distinto de un 0, que sí es un resultado: ese período no tenía nada."
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La causa de la falla cuando 'estado' es 'failed'. 'null' en cualquier otro caso."
}
},
"required": [
"id",
"estado",
"periodo",
"alcances",
"registros",
"error"
],
"additionalProperties": false
},
"description": "Las últimas sincronizaciones programadas de esta conexión, de la más reciente a la más antigua."
},
"datosListos": {
"type": "boolean",
"description": "La respuesta a «¿ya puedo leer?»: alguna sincronización terminó bien y ninguna está en curso. Revísalo antes de concluir que no hay datos, porque un listado vacío con 'datosListos' en false significa «espera», no «no tienes nada». Un período legítimamente sin registros cuenta como sincronización exitosa: este campo no mira si hay filas."
},
"herramientas": {
"type": "array",
"items": {
"type": "string"
},
"description": "Los ids de tool que ya puedes invocar sobre esta conexión. Si tu cliente MCP todavía no los muestra, llámalos con 'execute' pasando el id en 'tool'."
}
},
"required": [
"id",
"sistema",
"nombre",
"estado",
"credencial",
"verificadaEn",
"alcances",
"cadencia",
"trabajos",
"datosListos",
"herramientas"
],
"additionalProperties": false
},
"description": "Las conexiones de esta organización que pasan el filtro, con su estado y sus últimas sincronizaciones."
}
},
"required": [
"conexiones"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Conexiones de Emisso Connect
> Las 3 tools de Conexiones de Emisso Connect en el plan gratuito.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | ------------ |
| **Código** | `conexiones` |
| **Tipo** | `utility` |
| **Plan** | `free` |
| **Categoría** | `platform` |
| **Credenciales** | `none` |
| **Alcances** | ninguno |
| **Versión** | `0.1.0` |
## Tools [#tools]
* [`conexiones.enlace.crear`](./enlace-crear): Crea un enlace de un solo uso donde la persona entrega sus credenciales del sistema para conectarlo.
* [`conexiones.estado.consultar`](./estado-consultar): Dice en qué va cada conexión: si la credencial quedó vinculada, qué sincronizaciones corrieron y si YA HAY DATOS para consultar ('datosListos').
* [`conexiones.sistemas.listar`](./sistemas-listar): Devuelve el catálogo de sistemas chilenos que esta organización puede conectar (bancos, SII) con el estado de cada uno: si ya está conectado, sus conexiones, los módulos de datos que ofrece y las herramientas que quedan disponibles al conectarlo.
---
# Listar los sistemas que se pueden conectar
> Devuelve el catálogo de sistemas chilenos que esta organización puede conectar (bancos, SII) con el estado de cada uno: si ya está conectado, sus conexiones, los módulos de datos que ofrece y las herramientas que quedan disponibles al conectarlo.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `conexiones.sistemas.listar` |
| **Nombre MCP** | `conexiones__sistemas__listar` |
| **Conector** | `conexiones` |
| **Plano** | `action` |
| **Scope (permiso)** | `conexiones:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | no |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
## Qué hace [#qué-hace]
Úsala SIEMPRE antes de crear un enlace, para obtener el código exacto del sistema, no lo adivines. Si un sistema aparece con 'conectado' en false, el camino es 'conexiones.enlace.crear'.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------- | ------ | --------- | -------------------------------------------------------- |
| `sistema` | string | no | Filtra por código de sistema. Omítelo para verlos todos. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sistema": {
"description": "Filtra por código de sistema. Omítelo para verlos todos.",
"type": "string"
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/conexiones.sistemas.listar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.conexiones.sistemas.listar();
```
```json title="MCP · meta-tool execute"
{
"tool": "conexiones.sistemas.listar",
"params": {}
}
```
**Salida esperada (200):**
```json
{
"data": {
"sistemas": [
{
"codigo": "sii",
"nombre": "Servicio de Impuestos Internos",
"nombreCorto": "SII",
"insignia": "TRIBUTARIO",
"dominio": "sii.cl",
"requiereNavegador": false,
"alcances": [
{
"codigo": "rcv",
"etiqueta": "Compras y ventas (RCV)"
},
{
"codigo": "boletas",
"etiqueta": "Boletas electrónicas de venta (39/41)"
}
],
"conectado": false,
"conexiones": [],
"herramientas": [
"sii.conexion.sincronizar",
"sii.conexion.verificar",
"sii.rcv.consultar",
"sii.boletas.consultar"
]
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "conexiones.sistemas.listar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a un sistema y dos alcances; la respuesta real trae el catálogo completo.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ------------------------------------ | ---------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `sistemas` | lista de objeto | sí | El catálogo de sistemas conectables, con el estado de cada uno para esta organización. | |
| `sistemas[].codigo` | string | sí | El código del sistema. Es el valor exacto que espera 'conexiones.enlace.crear'; no lo adivines a partir del nombre. | |
| `sistemas[].nombre` | string | sí | El nombre largo del sistema, para escribirlo completo la primera vez. | |
| `sistemas[].nombreCorto` | string | sí | El nombre corto, para listas y selectores. | |
| `sistemas[].insignia` | string | null | sí | La categoría que muestra el selector (por ejemplo 'TRIBUTARIO' o 'BANCO EMPRESAS'). Solo presentación. |
| `sistemas[].dominio` | string | null | sí | El dominio del sistema externo, solo para mostrarlo. Nunca es la dirección a la que se llama. |
| `sistemas[].requiereNavegador` | booleano | sí | 'true' significa que el login de este sistema abre un navegador remoto y tarda decenas de segundos. Avísalo antes de empezar, para que la espera no parezca que algo se colgó. | |
| `sistemas[].alcances` | lista de objeto | sí | Los módulos de datos que este sistema ofrece. Al conectarlo eliges cuáles quedan habilitados. | |
| `sistemas[].alcances[].codigo` | string | sí | El código del módulo de datos, tal como se pasa en 'alcances' al sincronizar. | |
| `sistemas[].alcances[].etiqueta` | string | sí | El nombre del módulo en lenguaje de producto, para mostrárselo a una persona. | |
| `sistemas[].conectado` | booleano | sí | 'true' si esta organización ya tiene al menos una conexión de este sistema. Si es 'false', el camino es 'conexiones.enlace.crear'. | |
| `sistemas[].conexiones` | lista de objeto | sí | Las conexiones que esta organización ya tiene de este sistema. Vacío cuando 'conectado' es false. | |
| `sistemas[].conexiones[].id` | string | sí | El id de la conexión (conn\_…). Es lo que va en la cabecera 'X-Connect-Connection' de cada llamada a este sistema. | |
| `sistemas[].conexiones[].nombre` | string | sí | El nombre con que se creó la conexión, normalmente la empresa a la que pertenece la credencial. | |
| `sistemas[].conexiones[].estado` | `"active"` · `"disabled"` · `"pending"` | sí | En qué estado está la conexión. 'active' = utilizable. 'disabled' = pausada, sus tools responden 'connection\_disabled'. 'pending' = creada pero todavía sin credencial vinculada. | |
| `sistemas[].conexiones[].credencial` | `"linked"` · `"invalid"` · `"revoked"` · `"ausente"` | sí | En qué estado está la credencial de esta conexión. 'linked' = vinculada y utilizable. 'invalid' = el sistema externo la rechazó, hay que reconectar. 'revoked' = se revocó a propósito. 'ausente' = nunca se entregó. Todo lo que no sea 'linked' se arregla con 'conexiones.enlace.crear' en modo 'reconectar'. | |
| `sistemas[].herramientas` | lista de string | sí | Los ids de tool que quedan disponibles al conectar este sistema. Los puedes aprender ANTES de conectar e invocarlos con 'execute' pasando el id en 'tool', sin esperar a que tu cliente MCP refresque su lista. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sistemas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"codigo": {
"type": "string",
"description": "El código del sistema. Es el valor exacto que espera 'conexiones.enlace.crear'; no lo adivines a partir del nombre."
},
"nombre": {
"type": "string",
"description": "El nombre largo del sistema, para escribirlo completo la primera vez."
},
"nombreCorto": {
"type": "string",
"description": "El nombre corto, para listas y selectores."
},
"insignia": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La categoría que muestra el selector (por ejemplo 'TRIBUTARIO' o 'BANCO EMPRESAS'). Solo presentación."
},
"dominio": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El dominio del sistema externo, solo para mostrarlo. Nunca es la dirección a la que se llama."
},
"requiereNavegador": {
"type": "boolean",
"description": "'true' significa que el login de este sistema abre un navegador remoto y tarda decenas de segundos. Avísalo antes de empezar, para que la espera no parezca que algo se colgó."
},
"alcances": {
"type": "array",
"items": {
"type": "object",
"properties": {
"codigo": {
"type": "string",
"description": "El código del módulo de datos, tal como se pasa en 'alcances' al sincronizar."
},
"etiqueta": {
"type": "string",
"description": "El nombre del módulo en lenguaje de producto, para mostrárselo a una persona."
}
},
"required": [
"codigo",
"etiqueta"
],
"additionalProperties": false
},
"description": "Los módulos de datos que este sistema ofrece. Al conectarlo eliges cuáles quedan habilitados."
},
"conectado": {
"type": "boolean",
"description": "'true' si esta organización ya tiene al menos una conexión de este sistema. Si es 'false', el camino es 'conexiones.enlace.crear'."
},
"conexiones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "El id de la conexión (conn_…). Es lo que va en la cabecera 'X-Connect-Connection' de cada llamada a este sistema."
},
"nombre": {
"type": "string",
"description": "El nombre con que se creó la conexión, normalmente la empresa a la que pertenece la credencial."
},
"estado": {
"type": "string",
"enum": [
"active",
"disabled",
"pending"
],
"description": "En qué estado está la conexión. 'active' = utilizable. 'disabled' = pausada, sus tools responden 'connection_disabled'. 'pending' = creada pero todavía sin credencial vinculada."
},
"credencial": {
"type": "string",
"enum": [
"linked",
"invalid",
"revoked",
"ausente"
],
"description": "En qué estado está la credencial de esta conexión. 'linked' = vinculada y utilizable. 'invalid' = el sistema externo la rechazó, hay que reconectar. 'revoked' = se revocó a propósito. 'ausente' = nunca se entregó. Todo lo que no sea 'linked' se arregla con 'conexiones.enlace.crear' en modo 'reconectar'."
}
},
"required": [
"id",
"nombre",
"estado",
"credencial"
],
"additionalProperties": false
},
"description": "Las conexiones que esta organización ya tiene de este sistema. Vacío cuando 'conectado' es false."
},
"herramientas": {
"type": "array",
"items": {
"type": "string"
},
"description": "Los ids de tool que quedan disponibles al conectar este sistema. Los puedes aprender ANTES de conectar e invocarlos con 'execute' pasando el id en 'tool', sin esperar a que tu cliente MCP refresque su lista."
}
},
"required": [
"codigo",
"nombre",
"nombreCorto",
"insignia",
"dominio",
"requiereNavegador",
"alcances",
"conectado",
"conexiones",
"herramientas"
],
"additionalProperties": false
},
"description": "El catálogo de sistemas conectables, con el estado de cada uno para esta organización."
}
},
"required": [
"sistemas"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Core
> Las 1 tools de Core en el plan gratuito.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | --------- |
| **Código** | `core` |
| **Tipo** | `utility` |
| **Plan** | `free` |
| **Categoría** | `open` |
| **Credenciales** | `none` |
| **Alcances** | ninguno |
| **Versión** | `0.1.0` |
## Tools [#tools]
* [`core.timestamp.now`](./timestamp-now): Devuelve la marca de tiempo actual del servidor (ISO-8601 UTC, epoch Unix) y la zona horaria solicitada.
---
# Hora del servidor
> Devuelve la marca de tiempo actual del servidor (ISO-8601 UTC, epoch Unix) y la zona horaria solicitada.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `core.timestamp.now` |
| **Nombre MCP** | `core__timestamp__now` |
| **Conector** | `core` |
| **Plano** | `action` |
| **Scope (permiso)** | `core:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | no |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
## Qué hace [#qué-hace]
Prueba el camino E2E; no requiere credenciales.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------ | -------------------- | ------------------------------- |
| `timezone` | string | no · default `"UTC"` | IANA tz, p.ej. America/Santiago |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"timezone": {
"default": "UTC",
"description": "IANA tz, p.ej. America/Santiago",
"type": "string"
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
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"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.core.timestamp.now({ timezone: "America/Santiago" });
```
```json title="MCP · meta-tool execute"
{
"tool": "core.timestamp.now",
"params": {
"timezone": "America/Santiago"
}
}
```
**Salida esperada (200):**
```json
{
"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": 24,
"audit_status": "recorded"
}
}
```
> El iso siempre viene en UTC; timezone solo confirma la zona pedida.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------ | --------- | ----------------------------------------------------------------------------------------- |
| `iso` | string | sí | La hora del servidor en ISO 8601, SIEMPRE en UTC. No se convierte a la zona que pediste. |
| `unix` | entero | sí | La misma hora como epoch Unix, en segundos. |
| `timezone` | string | sí | Eco de la zona horaria que pediste. Confirma qué se recibió; no cambia el valor de 'iso'. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"iso": {
"type": "string",
"description": "La hora del servidor en ISO 8601, SIEMPRE en UTC. No se convierte a la zona que pediste."
},
"unix": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "La misma hora como epoch Unix, en segundos."
},
"timezone": {
"type": "string",
"description": "Eco de la zona horaria que pediste. Confirma qué se recibió; no cambia el valor de 'iso'."
}
},
"required": [
"iso",
"unix",
"timezone"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Echo
> Las 1 tools de Echo en el plan gratuito.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | --------- |
| **Código** | `echo` |
| **Tipo** | `utility` |
| **Plan** | `free` |
| **Categoría** | `open` |
| **Credenciales** | `none` |
| **Alcances** | ninguno |
| **Versión** | `0.1.0` |
## Tools [#tools]
* [`echo.message.reflect`](./message-reflect): Devuelve el texto recibido junto con su longitud.
---
# Reflejar mensaje
> Devuelve el texto recibido junto con su longitud.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `echo.message.reflect` |
| **Nombre MCP** | `echo__message__reflect` |
| **Conector** | `echo` |
| **Plano** | `action` |
| **Scope (permiso)** | `echo:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | no |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
## Qué hace [#qué-hace]
Conector de ejemplo que prueba que el patrón del registry generaliza; no requiere credenciales.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------ | ------ | --------- | ---------------- |
| `text` | string | sí | Texto a reflejar |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Texto a reflejar"
}
},
"required": [
"text"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/echo.message.reflect/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input":{"text":"hola connect"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.echo.message.reflect({ text: "hola connect" });
```
```json title="MCP · meta-tool execute"
{
"tool": "echo.message.reflect",
"params": {
"text": "hola connect"
}
}
```
**Salida esperada (200):**
```json
{
"data": {
"text": "hola connect",
"length": 12
},
"meta": {
"request_id": "req_…",
"tool_id": "echo.message.reflect",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------- | ------ | --------- | ----------------------------------------- |
| `text` | string | sí | El mismo texto que mandaste, sin cambios. |
| `length` | entero | sí | Cuántos caracteres tiene ese texto. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "El mismo texto que mandaste, sin cambios."
},
"length": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos caracteres tiene ese texto."
}
},
"required": [
"text",
"length"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Indicadores económicos
> Las 3 tools de Indicadores económicos en el plan gratuito.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | ------------- |
| **Código** | `indicadores` |
| **Tipo** | `utility` |
| **Plan** | `free` |
| **Categoría** | `open` |
| **Credenciales** | `none` |
| **Alcances** | ninguno |
| **Versión** | `0.1.0` |
## Tools [#tools]
* [`indicadores.serie.consultar`](./serie-consultar): Devuelve la serie de valores de un indicador entre dos fechas (AAAA-MM-DD), en orden ascendente, acotada por 'limite' (tope duro 1000), leída del almacén de referencia global.
* [`indicadores.valor.actual`](./valor-actual): Devuelve el ÚLTIMO valor disponible de un indicador económico chileno (UF, DÓLAR, EURO, IPC, UTM), leído del almacén de referencia global; no consulta fuentes externas en tiempo real.
* [`indicadores.valor.consultar`](./valor-consultar): Devuelve el valor de un indicador vigente a una fecha dada (AAAA-MM-DD) CON ARRASTRE: si esa fecha no tiene dato propio (un fin de semana, un feriado, o una fuente que no se ha actualizado) devuelve el último valor anterior.
---
# Serie histórica del indicador
> Devuelve la serie de valores de un indicador entre dos fechas (AAAA-MM-DD), en orden ascendente, acotada por 'limite' (tope duro 1000), leída del almacén de referencia global.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `indicadores.serie.consultar` |
| **Nombre MCP** | `indicadores__serie__consultar` |
| **Conector** | `indicadores` |
| **Plano** | `action` |
| **Scope (permiso)** | `indicadores:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | no |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------- | ------------------------------------------------- | ------------------- | ------------------------------------------------------- |
| `codigo` | `"UF"` · `"DOLAR"` · `"EURO"` · `"IPC"` · `"UTM"` | sí | Código del indicador económico |
| `desde` | string `^\d{4}-\d{2}-\d{2}$` | sí | Primer día del rango, inclusive, en formato AAAA-MM-DD. |
| `hasta` | string `^\d{4}-\d{2}-\d{2}$` | sí | Último día del rango, inclusive, en formato AAAA-MM-DD. |
| `limite` | entero 1-1000 | no · default `1000` | Máximo de puntos a devolver (tope duro 1000) |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"codigo": {
"type": "string",
"enum": [
"UF",
"DOLAR",
"EURO",
"IPC",
"UTM"
],
"description": "Código del indicador económico"
},
"desde": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Primer día del rango, inclusive, en formato AAAA-MM-DD."
},
"hasta": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Último día del rango, inclusive, en formato AAAA-MM-DD."
},
"limite": {
"default": 1000,
"description": "Máximo de puntos a devolver (tope duro 1000)",
"type": "integer",
"minimum": 1,
"maximum": 1000
}
},
"required": [
"codigo",
"desde",
"hasta"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/indicadores.serie.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input":{"codigo":"UF","desde":"2026-08-01","hasta":"2026-08-03"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.indicadores.serie.consultar({ codigo: "UF", desde: "2026-08-01", hasta: "2026-08-03" });
```
```json title="MCP · meta-tool execute"
{
"tool": "indicadores.serie.consultar",
"params": {
"codigo": "UF",
"desde": "2026-08-01",
"hasta": "2026-08-03"
}
}
```
**Salida esperada (200):**
```json
{
"data": {
"codigo": "UF",
"unidad": "CLP",
"valores": [
{
"fecha": "2026-08-01",
"valor": 39461.87
},
{
"fecha": "2026-08-02",
"valor": 39470.12
},
{
"fecha": "2026-08-03",
"valor": 39478.4
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "indicadores.serie.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ----------------- | --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `codigo` | string | sí | Eco del código del indicador al que corresponde este valor. |
| `unidad` | string | sí | En qué unidad está expresada la serie. UF, DOLAR, EURO y UTM vienen en pesos chilenos ('CLP'); el IPC viene en 'pct', porque es la variación mensual en por ciento y no un monto. Cuando 'valores' llega vacío este campo es la cadena vacía, porque la unidad se toma del primer punto. |
| `valores` | lista de objeto | sí | Los puntos de la serie dentro del rango, en orden ascendente por fecha. Solo trae los días que tienen dato propio: esta tool no arrastra, a diferencia de 'indicadores.valor.consultar', así que un rango con fines de semana devuelve menos puntos que días pedidos. Puede venir recortada por 'limite'. |
| `valores[].fecha` | string | sí | El día de este punto, en formato AAAA-MM-DD. |
| `valores[].valor` | número | sí | El valor de ese día, en la unidad que dice 'unidad'. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"codigo": {
"type": "string",
"description": "Eco del código del indicador al que corresponde este valor."
},
"unidad": {
"type": "string",
"description": "En qué unidad está expresada la serie. UF, DOLAR, EURO y UTM vienen en pesos chilenos ('CLP'); el IPC viene en 'pct', porque es la variación mensual en por ciento y no un monto. Cuando 'valores' llega vacío este campo es la cadena vacía, porque la unidad se toma del primer punto."
},
"valores": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fecha": {
"type": "string",
"description": "El día de este punto, en formato AAAA-MM-DD."
},
"valor": {
"type": "number",
"description": "El valor de ese día, en la unidad que dice 'unidad'."
}
},
"required": [
"fecha",
"valor"
],
"additionalProperties": false
},
"description": "Los puntos de la serie dentro del rango, en orden ascendente por fecha. Solo trae los días que tienen dato propio: esta tool no arrastra, a diferencia de 'indicadores.valor.consultar', así que un rango con fines de semana devuelve menos puntos que días pedidos. Puede venir recortada por 'limite'."
}
},
"required": [
"codigo",
"unidad",
"valores"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Valor actual del indicador
> Devuelve el ÚLTIMO valor disponible de un indicador económico chileno (UF, DÓLAR, EURO, IPC, UTM), leído del almacén de referencia global; no consulta fuentes externas en tiempo real.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `indicadores.valor.actual` |
| **Nombre MCP** | `indicadores__valor__actual` |
| **Conector** | `indicadores` |
| **Plano** | `action` |
| **Scope (permiso)** | `indicadores:read` |
| **Auth** | `none` |
| **Versión** | `2` |
| **Sensible** | no |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
## Qué hace [#qué-hace]
'último disponible' NO es lo mismo que 'el de hoy', y hay que mirar 'fecha' antes de usar el número: la UF y la UTM se publican por ADELANTADO, así que su fecha puede ser futura; y si la ingesta se atrasa, la fecha queda en el pasado. 'antiguedadDias' resuelve las dos de una vez: 0 = es el de hoy, positivo = días de atraso, negativo = está fechado en el futuro.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------- | ------------------------------------------------- | --------- | ------------------------------ |
| `codigo` | `"UF"` · `"DOLAR"` · `"EURO"` · `"IPC"` · `"UTM"` | sí | Código del indicador económico |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"codigo": {
"type": "string",
"enum": [
"UF",
"DOLAR",
"EURO",
"IPC",
"UTM"
],
"description": "Código del indicador económico"
}
},
"required": [
"codigo"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/indicadores.valor.actual/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input":{"codigo":"UF"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.indicadores.valor.actual({ codigo: "UF" });
```
```json title="MCP · meta-tool execute"
{
"tool": "indicadores.valor.actual",
"params": {
"codigo": "UF"
}
}
```
**Salida esperada (200):**
```json
{
"data": {
"codigo": "UF",
"fecha": "2026-08-08",
"valor": 39487.23,
"unidad": "CLP",
"antiguedadDias": -1
},
"meta": {
"request_id": "req_…",
"tool_id": "indicadores.valor.actual",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> antiguedadDias -1: la UF se publica por adelantado, el valor devuelto es el de mañana.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ---------------- | ------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `codigo` | string | sí | Eco del código del indicador al que corresponde este valor. |
| `fecha` | string | sí | La fecha del valor devuelto. Puede ser futura (UF/UTM se publican por adelantado) o pasada (ingesta atrasada). |
| `valor` | número | sí | El valor del indicador en esa fecha, en la unidad que dice 'unidad'. |
| `unidad` | string | sí | En qué unidad está expresado el valor. UF, DOLAR, EURO y UTM vienen en pesos chilenos ('CLP'); el IPC viene en 'pct', porque es la variación mensual en por ciento y no un monto. Lee este campo antes de tratar el número como plata. |
| `antiguedadDias` | entero | sí | Días entre 'fecha' y hoy en America/Santiago. 0 = el valor es de hoy; POSITIVO = está atrasado ese número de días (la fuente no se actualiza); NEGATIVO = está fechado en el futuro, normal en UF y UTM que se publican por adelantado. Compáralo contra tu propia tolerancia antes de calcular plata con este número. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"codigo": {
"type": "string",
"description": "Eco del código del indicador al que corresponde este valor."
},
"fecha": {
"type": "string",
"description": "La fecha del valor devuelto. Puede ser futura (UF/UTM se publican por adelantado) o pasada (ingesta atrasada)."
},
"valor": {
"type": "number",
"description": "El valor del indicador en esa fecha, en la unidad que dice 'unidad'."
},
"unidad": {
"type": "string",
"description": "En qué unidad está expresado el valor. UF, DOLAR, EURO y UTM vienen en pesos chilenos ('CLP'); el IPC viene en 'pct', porque es la variación mensual en por ciento y no un monto. Lee este campo antes de tratar el número como plata."
},
"antiguedadDias": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Días entre 'fecha' y hoy en America/Santiago. 0 = el valor es de hoy; POSITIVO = está atrasado ese número de días (la fuente no se actualiza); NEGATIVO = está fechado en el futuro, normal en UF y UTM que se publican por adelantado. Compáralo contra tu propia tolerancia antes de calcular plata con este número."
}
},
"required": [
"codigo",
"fecha",
"valor",
"unidad",
"antiguedadDias"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Valor del indicador a una fecha
> Devuelve el valor de un indicador vigente a una fecha dada (AAAA-MM-DD) CON ARRASTRE: si esa fecha no tiene dato propio (un fin de semana, un feriado, o una fuente que no se ha actualizado) devuelve el último valor anterior.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `indicadores.valor.consultar` |
| **Nombre MCP** | `indicadores__valor__consultar` |
| **Conector** | `indicadores` |
| **Plano** | `action` |
| **Scope (permiso)** | `indicadores:read` |
| **Auth** | `none` |
| **Versión** | `2` |
| **Sensible** | no |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
## Qué hace [#qué-hace]
La 'fecha' de la respuesta puede ser DISTINTA de la solicitada, y 'esArrastre' lo marca: false = ese día tiene dato propio; true = el valor corresponde a la 'fecha' devuelta, que es anterior. Pedir una fecha futura devuelve el último valor conocido con 'esArrastre: true', no un error. Todo se lee del almacén de referencia global.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------- | ------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `codigo` | `"UF"` · `"DOLAR"` · `"EURO"` · `"IPC"` · `"UTM"` | sí | Código del indicador económico |
| `fecha` | string `^\d{4}-\d{2}-\d{2}$` | sí | La fecha a la que quieres el valor vigente, en formato AAAA-MM-DD. Si ese día no tiene dato propio se arrastra el último anterior, así que pedir un fin de semana o una fecha futura responde 200 con 'esArrastre' en true, nunca un error. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"codigo": {
"type": "string",
"enum": [
"UF",
"DOLAR",
"EURO",
"IPC",
"UTM"
],
"description": "Código del indicador económico"
},
"fecha": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "La fecha a la que quieres el valor vigente, en formato AAAA-MM-DD. Si ese día no tiene dato propio se arrastra el último anterior, así que pedir un fin de semana o una fecha futura responde 200 con 'esArrastre' en true, nunca un error."
}
},
"required": [
"codigo",
"fecha"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/indicadores.valor.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "Content-Type: application/json" \
-d '{"input":{"codigo":"DOLAR","fecha":"2026-08-02"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.indicadores.valor.consultar({ codigo: "DOLAR", fecha: "2026-08-02" });
```
```json title="MCP · meta-tool execute"
{
"tool": "indicadores.valor.consultar",
"params": {
"codigo": "DOLAR",
"fecha": "2026-08-02"
}
}
```
**Salida esperada (200):**
```json
{
"data": {
"codigo": "DOLAR",
"fecha": "2026-07-31",
"valor": 943.18,
"unidad": "CLP",
"fechaSolicitada": "2026-08-02",
"esArrastre": true
},
"meta": {
"request_id": "req_…",
"tool_id": "indicadores.valor.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> El 2 de agosto de 2026 es domingo: no hay dato propio y el valor viene arrastrado del viernes anterior.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ----------------- | -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `codigo` | string | sí | Eco del código del indicador al que corresponde este valor. |
| `fecha` | string | sí | La fecha del valor DEVUELTO. Si 'esArrastre' es true, es anterior a 'fechaSolicitada'. |
| `valor` | número | sí | El valor del indicador vigente a esa fecha, en la unidad que dice 'unidad'. |
| `unidad` | string | sí | En qué unidad está expresado el valor. UF, DOLAR, EURO y UTM vienen en pesos chilenos ('CLP'); el IPC viene en 'pct', porque es la variación mensual en por ciento y no un monto. Lee este campo antes de tratar el número como plata. |
| `fechaSolicitada` | string | sí | Eco de la fecha que pediste, para poder compararla sin guardarla tú. |
| `esArrastre` | booleano | sí | true = la fecha pedida NO tiene dato propio y este valor viene de una fecha anterior ('fecha'). false = es el valor de esa fecha exacta. Míralo antes de usar el número: un arrastre de un día sobre un fin de semana es normal, uno de tres semanas significa que la fuente está caída. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"codigo": {
"type": "string",
"description": "Eco del código del indicador al que corresponde este valor."
},
"fecha": {
"type": "string",
"description": "La fecha del valor DEVUELTO. Si 'esArrastre' es true, es anterior a 'fechaSolicitada'."
},
"valor": {
"type": "number",
"description": "El valor del indicador vigente a esa fecha, en la unidad que dice 'unidad'."
},
"unidad": {
"type": "string",
"description": "En qué unidad está expresado el valor. UF, DOLAR, EURO y UTM vienen en pesos chilenos ('CLP'); el IPC viene en 'pct', porque es la variación mensual en por ciento y no un monto. Lee este campo antes de tratar el número como plata."
},
"fechaSolicitada": {
"type": "string",
"description": "Eco de la fecha que pediste, para poder compararla sin guardarla tú."
},
"esArrastre": {
"type": "boolean",
"description": "true = la fecha pedida NO tiene dato propio y este valor viene de una fecha anterior ('fecha'). false = es el valor de esa fecha exacta. Míralo antes de usar el número: un arrastre de un día sobre un fin de semana es normal, uno de tres semanas significa que la fuente está caída."
}
},
"required": [
"codigo",
"fecha",
"valor",
"unidad",
"fechaSolicitada",
"esArrastre"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Verificar conexión Notta
> Prueba las credenciales de la conexión contra Notta haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `notta.conexion.verificar` |
| **Nombre MCP** | `notta__conexion__verificar` |
| **Conector** | `notta` |
| **Plano** | `action` |
| **Scope (permiso)** | `notta:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/notta.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.notta.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "notta.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "notta.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`notta.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# Consultar un DTE
> Consulta el estado actual de un DTE en Notta por su id.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ----------------------------------------------------------------- |
| **Tool ID** | `notta.dte.consultar` |
| **Nombre MCP** | `notta__dte__consultar` |
| **Conector** | `notta` |
| **Plano** | `action` |
| **Scope (permiso)** | `notta:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Es el seguimiento del flujo asíncrono que abre notta.dte.emitir: el estado avanza de 'queued' a 'EPR' (aceptado por el SII) o a un rechazo terminal (RFR/RCT/RSC), y en ese caso sii\_glosa trae el motivo que dio el SII. Devuelve también folio, montos calculados y ambiente SII.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ----- | ------ | --------- | ----------------------------------------------------------------------------------------------------- |
| `id` | string | sí | El id del documento en Notta: el que devolvió notta.dte.emitir, o el de una fila de notta.dte.listar. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "El id del documento en Notta: el que devolvió notta.dte.emitir, o el de una fila de notta.dte.listar."
}
},
"required": [
"id"
]
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ---------------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | sí | El identificador del documento en Notta. Es lo que reciben notta.dte.consultar, notta.dte.descargar y notta.dte.reenviar. | |
| `tipo_dte` | entero | sí | El código de tipo de documento del catálogo del SII. Los que esta conexión emite son 33 (factura afecta), 34 (factura exenta), 56 (nota de débito) y 61 (nota de crédito). | |
| `folio` | entero | null | sí | El folio del documento: el correlativo que sale de los folios autorizados (CAF) y con el que el SII lo identifica. Se consume una sola vez y no se reutiliza, así que emitir dos veces por error gasta dos. Viene en null cuando Notta todavía no lo informó. |
| `estado` | string | sí | El estado del documento. 'queued' es recién encolado con el folio ya asignado; 'EPR' es aceptado por el SII; 'RPR' y 'aceptado\_con\_reparos' son aceptado con reparos; 'RFR', 'RCT' y 'RSC' son rechazos terminales que ya no cambian. Decide por este campo, nunca por 'estado\_legible'. | |
| `rut_receptor` | string | no | El RUT de quien recibe el documento, sin puntos y con guion. Ausente cuando Notta no lo informó. | |
| `montos` | objeto | sí | Los totales del documento, calculados por Notta a partir de las líneas. Quien emite no los manda. | |
| `montos.neto` | entero | sí | Suma de las líneas afectas, antes de IVA. | |
| `montos.exento` | entero | sí | Suma de las líneas marcadas con exento en true, que no pagan IVA. | |
| `montos.iva` | entero | sí | El IVA que corresponde al neto. | |
| `montos.total` | entero | sí | Lo que el receptor debe pagar: neto más exento más IVA. | |
| `sii_env` | string | sí | El ambiente del SII en el que vive el documento: 'cert' es certificación (pruebas) y 'prod' es producción. Lo decide la credencial de la conexión, nunca la llamada. | |
| `fecha_emision` | string | sí | La fecha de emisión declarada en el documento, en formato AAAA-MM-DD. | |
| `sii_glosa` | string | no | El texto con que el SII explicó un rechazo. Solo lo trae notta.dte.consultar, y solo cuando el SII dijo algo: la respuesta de la emisión nunca lo lleva. | |
| `estado_legible` | string | sí | El 'estado' traducido a una frase en español para mostrarle a una persona. Es texto de presentación, no contrato: para decidir usa 'estado'. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "El identificador del documento en Notta. Es lo que reciben notta.dte.consultar, notta.dte.descargar y notta.dte.reenviar."
},
"tipo_dte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El código de tipo de documento del catálogo del SII. Los que esta conexión emite son 33 (factura afecta), 34 (factura exenta), 56 (nota de débito) y 61 (nota de crédito)."
},
"folio": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "El folio del documento: el correlativo que sale de los folios autorizados (CAF) y con el que el SII lo identifica. Se consume una sola vez y no se reutiliza, así que emitir dos veces por error gasta dos. Viene en null cuando Notta todavía no lo informó."
},
"estado": {
"type": "string",
"description": "El estado del documento. 'queued' es recién encolado con el folio ya asignado; 'EPR' es aceptado por el SII; 'RPR' y 'aceptado_con_reparos' son aceptado con reparos; 'RFR', 'RCT' y 'RSC' son rechazos terminales que ya no cambian. Decide por este campo, nunca por 'estado_legible'."
},
"rut_receptor": {
"description": "El RUT de quien recibe el documento, sin puntos y con guion. Ausente cuando Notta no lo informó.",
"type": "string"
},
"montos": {
"type": "object",
"properties": {
"neto": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Suma de las líneas afectas, antes de IVA."
},
"exento": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Suma de las líneas marcadas con exento en true, que no pagan IVA."
},
"iva": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El IVA que corresponde al neto."
},
"total": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Lo que el receptor debe pagar: neto más exento más IVA."
}
},
"required": [
"neto",
"exento",
"iva",
"total"
],
"additionalProperties": false,
"description": "Los totales del documento, calculados por Notta a partir de las líneas. Quien emite no los manda."
},
"sii_env": {
"type": "string",
"description": "El ambiente del SII en el que vive el documento: 'cert' es certificación (pruebas) y 'prod' es producción. Lo decide la credencial de la conexión, nunca la llamada."
},
"fecha_emision": {
"type": "string",
"description": "La fecha de emisión declarada en el documento, en formato AAAA-MM-DD."
},
"sii_glosa": {
"description": "El texto con que el SII explicó un rechazo. Solo lo trae notta.dte.consultar, y solo cuando el SII dijo algo: la respuesta de la emisión nunca lo lleva.",
"type": "string"
},
"estado_legible": {
"type": "string",
"description": "El 'estado' traducido a una frase en español para mostrarle a una persona. Es texto de presentación, no contrato: para decidir usa 'estado'."
}
},
"required": [
"id",
"tipo_dte",
"folio",
"estado",
"montos",
"sii_env",
"fecha_emision",
"estado_legible"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Descargar el XML o el PDF de un DTE
> Devuelve el XML firmado o el PDF de un DTE como base64.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ----------------------------------------------------------------- |
| **Tool ID** | `notta.dte.descargar` |
| **Nombre MCP** | `notta__dte__descargar` |
| **Conector** | `notta` |
| **Plano** | `action` |
| **Scope (permiso)** | `notta:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Para consumo programático (REST/SDK). En conversación prefiere notta.dte.reenviar: el base64 de un PDF es inmanejable en chat. Un documento recién emitido todavía no está firmado: hasta que lo esté, la descarga falla de forma reintentable.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------- | ----------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | sí | El id del documento en Notta: el que devolvió notta.dte.emitir, o el de una fila de notta.dte.listar. |
| `formato` | `"xml"` · `"pdf"` | sí | 'xml' entrega el XML firmado que se le envió al SII y 'pdf' su representación impresa. Los dos existen recién cuando el documento está firmado. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "El id del documento en Notta: el que devolvió notta.dte.emitir, o el de una fila de notta.dte.listar."
},
"formato": {
"type": "string",
"enum": [
"xml",
"pdf"
],
"description": "'xml' entrega el XML firmado que se le envió al SII y 'pdf' su representación impresa. Los dos existen recién cuando el documento está firmado."
}
},
"required": [
"id",
"formato"
]
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ------------------ | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contenido_base64` | string | sí | El archivo completo, codificado en base64. Decodifícalo antes de guardarlo. En una conversación prefiere notta.dte.reenviar: el base64 de un PDF es inmanejable en un chat. |
| `content_type` | string | sí | El tipo MIME que Notta declaró para el archivo: application/xml o application/pdf. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"contenido_base64": {
"type": "string",
"description": "El archivo completo, codificado en base64. Decodifícalo antes de guardarlo. En una conversación prefiere notta.dte.reenviar: el base64 de un PDF es inmanejable en un chat."
},
"content_type": {
"type": "string",
"description": "El tipo MIME que Notta declaró para el archivo: application/xml o application/pdf."
}
},
"required": [
"contenido_base64",
"content_type"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Emitir DTE (factura o nota)
> Emite un DTE ante el SII vía Notta: factura afecta (33), exenta (34), nota de débito (56) o nota de crédito (61, que exige references[] al documento original, y este solo puede ser una factura 33 o 34).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ----------------------------------------------------------------- |
| **Tool ID** | `notta.dte.emitir` |
| **Nombre MCP** | `notta__dte__emitir` |
| **Conector** | `notta` |
| **Plano** | `action` |
| **Scope (permiso)** | `notta:write` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=true, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Cada item declara si es exento y su monto\_item (cantidad × precio\_unitario, ya con el descuento de la línea aplicado); los TOTALES del documento (neto, exento, IVA, total) los calcula Notta. Las NOTAS (56/61) exigen además rut\_emisor (el RUT de la empresa de esta conexión, que Notta no deriva en esa ruta) y la nota de débito (56) exige nd\_reason; a cambio, no llevan forma\_pago ni descuento\_global. La emisión es ASÍNCRONA: esta llamada devuelve el documento con folio asignado y estado 'queued'; haz el seguimiento con notta.dte.consultar hasta EPR (aceptado) o un rechazo. Si un intento anterior falló por transporte o timeout, revisa los documentos RECIENTES con notta.dte.listar antes de reintentar, para no duplicar. Con correo\_receptor, Notta envía el PDF+XML al receptor cuando el SII acepta.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tipo_dte` | valor | sí | Qué documento emitir: 33 factura afecta (con IVA), 34 factura exenta, 56 nota de débito, 61 nota de crédito. Una factura (33 o 34) exige forma\_pago y el giro, la dirección y la comuna del receptor. Una nota (56 o 61) exige references\[] al documento original y rut\_emisor, y no lleva forma\_pago ni descuento\_global. |
| `receptor` | objeto | sí | A quién se le emite el documento. Para una factura (33 o 34) el SII exige además giro, direccion y comuna; una nota (56 o 61) no los pide. |
| `receptor.rut` | string `^\d{1,8}-[\dkK]$` | sí | RUT de quien recibe el documento, sin puntos y con guion antes del dígito verificador. Si el dígito verificador no calza, la emisión se rechaza en validación y no se gasta folio. |
| `receptor.razon_social` | string | sí | Nombre legal de la empresa o persona que recibe el documento, tal como saldrá impreso. |
| `receptor.giro` | string | no | Giro o actividad económica. OBLIGATORIO para facturas (tipo\_dte 33 y 34) por norma del SII de junio 2026; opcional en notas (56/61). Pídeselo al usuario antes de emitir una factura: sin él la llamada se rechaza en validación. |
| `receptor.direccion` | string | no | Dirección del receptor. OBLIGATORIA para facturas (33/34) por norma del SII de junio 2026; opcional en notas (56/61). |
| `receptor.comuna` | string | no | Comuna del receptor. OBLIGATORIA para facturas (33/34) por norma del SII de junio 2026; opcional en notas (56/61). |
| `fecha_emision` | string `^\d{4}-\d{2}-\d{2}$` | sí | Fecha de emisión del documento, en formato AAAA-MM-DD. Es obligatoria: Notta no la deriva del día en curso. |
| `items` | lista de objeto | sí | Las líneas del documento, entre 1 y 60 (el máximo que admite el SII). Los totales del documento (neto, exento, IVA y total) los calcula Notta a partir de estas líneas: no se mandan. |
| `items[].nombre` | string | sí | Qué se está cobrando en esta línea, tal como saldrá impreso en el documento. |
| `items[].cantidad` | entero | sí | Cuántas unidades lleva la línea. Este conector la pide entera y tiene que ser MAYOR QUE CERO, incluso en una nota de crédito por devolución: lo que va en negativo ahí es 'monto\_item', nunca la cantidad. |
| `items[].precio_unitario` | entero | sí | Precio de UNA unidad, sin IVA: el impuesto se calcula después, sobre el total del documento. Este conector lo pide entero y no admite negativos (cero sí). |
| `items[].exento` | booleano | sí | true marca la línea como exenta de IVA; false, como afecta. Una factura exenta (tipo\_dte 34) no admite líneas afectas: o todas van con exento en true, o corresponde emitir una 33. |
| `items[].monto_item` | entero | sí | Total de la línea, sin IVA: cantidad por precio\_unitario, ya con el descuento\_pct de la línea aplicado. Lo declara quien emite y el SII valida esa relación línea por línea; si no cuadra, la respuesta es un error de validación 'amount\_arithmetic'. |
| `items[].descuento_pct` | número | no | Descuento de ESTA línea, en porcentaje entre 0 y 100. Si lo usas, monto\_item tiene que venir ya descontado. Omitirlo equivale a un descuento de cero. |
| `forma_pago` | valor | no | 1=Contado, 2=Crédito, 3=Sin costo. OBLIGATORIO para facturas (tipo\_dte 33 y 34); una nota (56/61) no lo lleva. Pregúntaselo al usuario antes de emitir: sin él la llamada se rechaza en validación. |
| `references` | lista de objeto | no | Los documentos que esta nota corrige o anula. Una nota (56 o 61) exige al menos uno; una factura (33 o 34) no admite ninguno en esta versión, porque sus referencias son de otra clase (orden de compra, contrato, HES) y este conector todavía no las expone. |
| `references[].line_num` | entero 1-40 | sí | Número de línea de esta referencia dentro del documento, empezando en 1. No se deriva solo: lo declara quien emite, para controlar el orden. |
| `references[].tipo_doc_ref` | entero | sí | Tipo del documento referenciado, con el código del catálogo del SII (33 factura afecta, 34 factura exenta). Una nota de crédito (61) solo puede referenciar una 33 o una 34. |
| `references[].folio_ref` | entero | sí | Folio del documento referenciado, o sea el número del documento que esta nota corrige o anula. |
| `references[].fecha_ref` | string `^\d{4}-\d{2}-\d{2}$` | sí | Fecha de emisión del documento referenciado, en formato AAAA-MM-DD. |
| `references[].cod_ref` | valor | sí | Qué le hace esta nota al documento referenciado: 1 lo anula, 2 corrige su texto (y entonces la nota no puede mover montos) y 3 corrige sus montos. En una nota de débito (56) tiene que calzar con nd\_reason. |
| `references[].razon_ref` | string | sí | Por qué se emite esta nota sobre el documento referenciado, en texto libre. Viaja al documento tal cual. |
| `correo_receptor` | string `^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$` | no | Correo del receptor. Si lo indicas, Notta le envía el PDF y el XML cuando el SII acepta el documento; si no, el documento se emite igual y se puede entregar después con notta.dte.reenviar. |
| `descuento_global` | lista de objeto | no | Descuentos o recargos que aplican al TOTAL del documento, no a una línea. Solo en facturas (33 y 34): una nota (56 o 61) que los lleve se rechaza en validación, porque la ruta de notas de Notta los descartaría en silencio. |
| `descuento_global[].tipo` | `"descuento"` · `"recargo"` | sí | 'descuento' resta del total del documento y 'recargo' se lo suma. El signo lo pone este campo, así que 'valor' va siempre positivo. |
| `descuento_global[].es_porcentaje` | booleano | sí | true lee 'valor' como un porcentaje; false lo lee como un monto fijo. |
| `descuento_global[].valor` | número | sí | Cuánto descontar o recargar, siempre positivo. Se interpreta como porcentaje o como monto fijo según 'es\_porcentaje'. |
| `descuento_global[].glosa` | string | no | Texto que explica el descuento o recargo. Viaja al documento tal cual. |
| `descuento_global[].aplica_exento` | booleano | no | true aplica el ajuste sobre la base EXENTA. Omitirlo o ponerlo en false lo aplica sobre la base AFECTA, o sea mueve el neto antes de que se calcule el IVA. |
| `rut_emisor` | string `^\d{1,8}-[\dkK]$` | no | Para notas de débito/crédito (56/61), el RUT emisor de la empresa: el de esta conexión. Las facturas (33/34) lo derivan solas. |
| `nd_reason` | `"correccion_monto"` · `"reposicion_nc_anulada"` · `"interese_moratorio_contractual"` | no | Solo para nota de débito (56); el motivo cruza con el cod\_ref de la referencia. correccion\_monto: sube el monto de una factura ya emitida (cod\_ref 3). reposicion\_nc\_anulada: repone una factura cuya nota de crédito se anuló (cod\_ref 2, o 1 para anular la NC). interese\_moratorio\_contractual: intereses por mora PACTADOS en el contrato (cod\_ref 3). |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"tipo_dte": {
"anyOf": [
{
"type": "number",
"const": 33
},
{
"type": "number",
"const": 34
},
{
"type": "number",
"const": 56
},
{
"type": "number",
"const": 61
}
],
"description": "Qué documento emitir: 33 factura afecta (con IVA), 34 factura exenta, 56 nota de débito, 61 nota de crédito. Una factura (33 o 34) exige forma_pago y el giro, la dirección y la comuna del receptor. Una nota (56 o 61) exige references[] al documento original y rut_emisor, y no lleva forma_pago ni descuento_global."
},
"receptor": {
"type": "object",
"properties": {
"rut": {
"type": "string",
"pattern": "^\\d{1,8}-[\\dkK]$",
"description": "RUT de quien recibe el documento, sin puntos y con guion antes del dígito verificador. Si el dígito verificador no calza, la emisión se rechaza en validación y no se gasta folio."
},
"razon_social": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Nombre legal de la empresa o persona que recibe el documento, tal como saldrá impreso."
},
"giro": {
"description": "Giro o actividad económica. OBLIGATORIO para facturas (tipo_dte 33 y 34) por norma del SII de junio 2026; opcional en notas (56/61). Pídeselo al usuario antes de emitir una factura: sin él la llamada se rechaza en validación.",
"type": "string",
"maxLength": 40
},
"direccion": {
"description": "Dirección del receptor. OBLIGATORIA para facturas (33/34) por norma del SII de junio 2026; opcional en notas (56/61).",
"type": "string",
"maxLength": 70
},
"comuna": {
"description": "Comuna del receptor. OBLIGATORIA para facturas (33/34) por norma del SII de junio 2026; opcional en notas (56/61).",
"type": "string",
"maxLength": 20
}
},
"required": [
"rut",
"razon_social"
],
"description": "A quién se le emite el documento. Para una factura (33 o 34) el SII exige además giro, direccion y comuna; una nota (56 o 61) no los pide."
},
"fecha_emision": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Fecha de emisión del documento, en formato AAAA-MM-DD. Es obligatoria: Notta no la deriva del día en curso."
},
"items": {
"minItems": 1,
"maxItems": 60,
"type": "array",
"items": {
"type": "object",
"properties": {
"nombre": {
"type": "string",
"minLength": 1,
"maxLength": 80,
"description": "Qué se está cobrando en esta línea, tal como saldrá impreso en el documento."
},
"cantidad": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántas unidades lleva la línea. Este conector la pide entera y tiene que ser MAYOR QUE CERO, incluso en una nota de crédito por devolución: lo que va en negativo ahí es 'monto_item', nunca la cantidad."
},
"precio_unitario": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Precio de UNA unidad, sin IVA: el impuesto se calcula después, sobre el total del documento. Este conector lo pide entero y no admite negativos (cero sí)."
},
"exento": {
"type": "boolean",
"description": "true marca la línea como exenta de IVA; false, como afecta. Una factura exenta (tipo_dte 34) no admite líneas afectas: o todas van con exento en true, o corresponde emitir una 33."
},
"monto_item": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Total de la línea, sin IVA: cantidad por precio_unitario, ya con el descuento_pct de la línea aplicado. Lo declara quien emite y el SII valida esa relación línea por línea; si no cuadra, la respuesta es un error de validación 'amount_arithmetic'."
},
"descuento_pct": {
"description": "Descuento de ESTA línea, en porcentaje entre 0 y 100. Si lo usas, monto_item tiene que venir ya descontado. Omitirlo equivale a un descuento de cero.",
"type": "number",
"minimum": 0,
"maximum": 100
}
},
"required": [
"nombre",
"cantidad",
"precio_unitario",
"exento",
"monto_item"
]
},
"description": "Las líneas del documento, entre 1 y 60 (el máximo que admite el SII). Los totales del documento (neto, exento, IVA y total) los calcula Notta a partir de estas líneas: no se mandan."
},
"forma_pago": {
"description": "1=Contado, 2=Crédito, 3=Sin costo. OBLIGATORIO para facturas (tipo_dte 33 y 34); una nota (56/61) no lo lleva. Pregúntaselo al usuario antes de emitir: sin él la llamada se rechaza en validación.",
"anyOf": [
{
"type": "number",
"const": 1
},
{
"type": "number",
"const": 2
},
{
"type": "number",
"const": 3
}
]
},
"references": {
"description": "Los documentos que esta nota corrige o anula. Una nota (56 o 61) exige al menos uno; una factura (33 o 34) no admite ninguno en esta versión, porque sus referencias son de otra clase (orden de compra, contrato, HES) y este conector todavía no las expone.",
"maxItems": 40,
"type": "array",
"items": {
"type": "object",
"properties": {
"line_num": {
"type": "integer",
"minimum": 1,
"maximum": 40,
"description": "Número de línea de esta referencia dentro del documento, empezando en 1. No se deriva solo: lo declara quien emite, para controlar el orden."
},
"tipo_doc_ref": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Tipo del documento referenciado, con el código del catálogo del SII (33 factura afecta, 34 factura exenta). Una nota de crédito (61) solo puede referenciar una 33 o una 34."
},
"folio_ref": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991,
"description": "Folio del documento referenciado, o sea el número del documento que esta nota corrige o anula."
},
"fecha_ref": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Fecha de emisión del documento referenciado, en formato AAAA-MM-DD."
},
"cod_ref": {
"anyOf": [
{
"type": "number",
"const": 1
},
{
"type": "number",
"const": 2
},
{
"type": "number",
"const": 3
}
],
"description": "Qué le hace esta nota al documento referenciado: 1 lo anula, 2 corrige su texto (y entonces la nota no puede mover montos) y 3 corrige sus montos. En una nota de débito (56) tiene que calzar con nd_reason."
},
"razon_ref": {
"type": "string",
"minLength": 1,
"description": "Por qué se emite esta nota sobre el documento referenciado, en texto libre. Viaja al documento tal cual."
}
},
"required": [
"line_num",
"tipo_doc_ref",
"folio_ref",
"fecha_ref",
"cod_ref",
"razon_ref"
]
}
},
"correo_receptor": {
"description": "Correo del receptor. Si lo indicas, Notta le envía el PDF y el XML cuando el SII acepta el documento; si no, el documento se emite igual y se puede entregar después con notta.dte.reenviar.",
"type": "string",
"maxLength": 200,
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
},
"descuento_global": {
"description": "Descuentos o recargos que aplican al TOTAL del documento, no a una línea. Solo en facturas (33 y 34): una nota (56 o 61) que los lleve se rechaza en validación, porque la ruta de notas de Notta los descartaría en silencio.",
"type": "array",
"items": {
"type": "object",
"properties": {
"tipo": {
"type": "string",
"enum": [
"descuento",
"recargo"
],
"description": "'descuento' resta del total del documento y 'recargo' se lo suma. El signo lo pone este campo, así que 'valor' va siempre positivo."
},
"es_porcentaje": {
"type": "boolean",
"description": "true lee 'valor' como un porcentaje; false lo lee como un monto fijo."
},
"valor": {
"type": "number",
"exclusiveMinimum": 0,
"description": "Cuánto descontar o recargar, siempre positivo. Se interpreta como porcentaje o como monto fijo según 'es_porcentaje'."
},
"glosa": {
"description": "Texto que explica el descuento o recargo. Viaja al documento tal cual.",
"type": "string"
},
"aplica_exento": {
"description": "true aplica el ajuste sobre la base EXENTA. Omitirlo o ponerlo en false lo aplica sobre la base AFECTA, o sea mueve el neto antes de que se calcule el IVA.",
"type": "boolean"
}
},
"required": [
"tipo",
"es_porcentaje",
"valor"
]
}
},
"rut_emisor": {
"description": "Para notas de débito/crédito (56/61), el RUT emisor de la empresa: el de esta conexión. Las facturas (33/34) lo derivan solas.",
"type": "string",
"pattern": "^\\d{1,8}-[\\dkK]$"
},
"nd_reason": {
"description": "Solo para nota de débito (56); el motivo cruza con el cod_ref de la referencia. correccion_monto: sube el monto de una factura ya emitida (cod_ref 3). reposicion_nc_anulada: repone una factura cuya nota de crédito se anuló (cod_ref 2, o 1 para anular la NC). interese_moratorio_contractual: intereses por mora PACTADOS en el contrato (cod_ref 3).",
"type": "string",
"enum": [
"correccion_monto",
"reposicion_nc_anulada",
"interese_moratorio_contractual"
]
}
},
"required": [
"tipo_dte",
"receptor",
"fecha_emision",
"items"
]
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ---------------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | sí | El identificador del documento en Notta. Es lo que reciben notta.dte.consultar, notta.dte.descargar y notta.dte.reenviar. | |
| `tipo_dte` | entero | sí | El código de tipo de documento del catálogo del SII. Los que esta conexión emite son 33 (factura afecta), 34 (factura exenta), 56 (nota de débito) y 61 (nota de crédito). | |
| `folio` | entero | null | sí | El folio del documento: el correlativo que sale de los folios autorizados (CAF) y con el que el SII lo identifica. Se consume una sola vez y no se reutiliza, así que emitir dos veces por error gasta dos. Viene en null cuando Notta todavía no lo informó. |
| `estado` | string | sí | El estado del documento. 'queued' es recién encolado con el folio ya asignado; 'EPR' es aceptado por el SII; 'RPR' y 'aceptado\_con\_reparos' son aceptado con reparos; 'RFR', 'RCT' y 'RSC' son rechazos terminales que ya no cambian. Decide por este campo, nunca por 'estado\_legible'. | |
| `rut_receptor` | string | no | El RUT de quien recibe el documento, sin puntos y con guion. Ausente cuando Notta no lo informó. | |
| `montos` | objeto | sí | Los totales del documento, calculados por Notta a partir de las líneas. Quien emite no los manda. | |
| `montos.neto` | entero | sí | Suma de las líneas afectas, antes de IVA. | |
| `montos.exento` | entero | sí | Suma de las líneas marcadas con exento en true, que no pagan IVA. | |
| `montos.iva` | entero | sí | El IVA que corresponde al neto. | |
| `montos.total` | entero | sí | Lo que el receptor debe pagar: neto más exento más IVA. | |
| `sii_env` | string | sí | El ambiente del SII en el que vive el documento: 'cert' es certificación (pruebas) y 'prod' es producción. Lo decide la credencial de la conexión, nunca la llamada. | |
| `fecha_emision` | string | sí | La fecha de emisión declarada en el documento, en formato AAAA-MM-DD. | |
| `sii_glosa` | string | no | El texto con que el SII explicó un rechazo. Solo lo trae notta.dte.consultar, y solo cuando el SII dijo algo: la respuesta de la emisión nunca lo lleva. | |
| `estado_legible` | string | sí | El 'estado' traducido a una frase en español para mostrarle a una persona. Es texto de presentación, no contrato: para decidir usa 'estado'. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "El identificador del documento en Notta. Es lo que reciben notta.dte.consultar, notta.dte.descargar y notta.dte.reenviar."
},
"tipo_dte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El código de tipo de documento del catálogo del SII. Los que esta conexión emite son 33 (factura afecta), 34 (factura exenta), 56 (nota de débito) y 61 (nota de crédito)."
},
"folio": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "El folio del documento: el correlativo que sale de los folios autorizados (CAF) y con el que el SII lo identifica. Se consume una sola vez y no se reutiliza, así que emitir dos veces por error gasta dos. Viene en null cuando Notta todavía no lo informó."
},
"estado": {
"type": "string",
"description": "El estado del documento. 'queued' es recién encolado con el folio ya asignado; 'EPR' es aceptado por el SII; 'RPR' y 'aceptado_con_reparos' son aceptado con reparos; 'RFR', 'RCT' y 'RSC' son rechazos terminales que ya no cambian. Decide por este campo, nunca por 'estado_legible'."
},
"rut_receptor": {
"description": "El RUT de quien recibe el documento, sin puntos y con guion. Ausente cuando Notta no lo informó.",
"type": "string"
},
"montos": {
"type": "object",
"properties": {
"neto": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Suma de las líneas afectas, antes de IVA."
},
"exento": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Suma de las líneas marcadas con exento en true, que no pagan IVA."
},
"iva": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El IVA que corresponde al neto."
},
"total": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Lo que el receptor debe pagar: neto más exento más IVA."
}
},
"required": [
"neto",
"exento",
"iva",
"total"
],
"additionalProperties": false,
"description": "Los totales del documento, calculados por Notta a partir de las líneas. Quien emite no los manda."
},
"sii_env": {
"type": "string",
"description": "El ambiente del SII en el que vive el documento: 'cert' es certificación (pruebas) y 'prod' es producción. Lo decide la credencial de la conexión, nunca la llamada."
},
"fecha_emision": {
"type": "string",
"description": "La fecha de emisión declarada en el documento, en formato AAAA-MM-DD."
},
"sii_glosa": {
"description": "El texto con que el SII explicó un rechazo. Solo lo trae notta.dte.consultar, y solo cuando el SII dijo algo: la respuesta de la emisión nunca lo lleva.",
"type": "string"
},
"estado_legible": {
"type": "string",
"description": "El 'estado' traducido a una frase en español para mostrarle a una persona. Es texto de presentación, no contrato: para decidir usa 'estado'."
}
},
"required": [
"id",
"tipo_dte",
"folio",
"estado",
"montos",
"sii_env",
"fecha_emision",
"estado_legible"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Listar los DTEs recientes
> Lista los DTEs MÁS RECIENTES emitidos por esta empresa en Notta (del más nuevo al más antiguo).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ----------------------------------------------------------------- |
| **Tool ID** | `notta.dte.listar` |
| **Nombre MCP** | `notta__dte__listar` |
| **Conector** | `notta` |
| **Plano** | `action` |
| **Scope (permiso)** | `notta:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
El API de Notta NO ofrece filtros por tipo, fecha, estado ni receptor: solo un límite de cuántos traer, así que si buscas uno concreto pide más documentos y descarta tú los que no son. Úsalo como red antes de reintentar una emisión que falló por transporte o timeout: si el documento ya aparece entre los recientes, no lo vuelvas a emitir. Devuelve menos campos que notta.dte.consultar (sin neto, IVA ni ambiente): para el detalle completo consulta por id.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------- | ------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limite` | entero 1-100 | no | Cuántos documentos traer, entre 1 y 100; si se omite, Notta trae 20. Es el único filtro que el API ofrece: no hay filtro por tipo, fecha, estado ni receptor, así que para encontrar uno concreto pide más y descarta tú. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"limite": {
"description": "Cuántos documentos traer, entre 1 y 100; si se omite, Notta trae 20. Es el único filtro que el API ofrece: no hay filtro por tipo, fecha, estado ni receptor, así que para encontrar uno concreto pide más y descarta tú.",
"type": "integer",
"minimum": 1,
"maximum": 100
}
}
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ----------------------- | --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dtes` | lista de objeto | sí | Los documentos, del más nuevo al más antiguo. Traen menos campos que notta.dte.consultar: sin neto, exento, IVA ni ambiente del SII. | |
| `dtes[].id` | string | sí | El identificador del documento en Notta. Es lo que reciben notta.dte.consultar, notta.dte.descargar y notta.dte.reenviar. | |
| `dtes[].tipo_dte` | entero | sí | El código de tipo de documento del catálogo del SII. Los que esta conexión emite son 33 (factura afecta), 34 (factura exenta), 56 (nota de débito) y 61 (nota de crédito). | |
| `dtes[].folio` | entero | null | sí | El folio del documento: el correlativo que sale de los folios autorizados (CAF) y con el que el SII lo identifica. Se consume una sola vez y no se reutiliza, así que emitir dos veces por error gasta dos. Viene en null cuando Notta todavía no lo informó. |
| `dtes[].estado` | string | sí | El estado del documento. 'queued' es recién encolado con el folio ya asignado; 'EPR' es aceptado por el SII; 'RPR' y 'aceptado\_con\_reparos' son aceptado con reparos; 'RFR', 'RCT' y 'RSC' son rechazos terminales que ya no cambian. Decide por este campo, nunca por 'estado\_legible'. | |
| `dtes[].estado_legible` | string | sí | El 'estado' traducido a una frase en español para mostrarle a una persona. Es texto de presentación, no contrato: para decidir usa 'estado'. | |
| `dtes[].monto_total` | entero | null | sí | El total del documento. Viene en null cuando Notta no lo informó, que no es lo mismo que un documento por cero. |
| `dtes[].fecha_emision` | string | sí | La fecha de emisión declarada en el documento, en formato AAAA-MM-DD. | |
| `dtes[].rut_receptor` | string | no | El RUT de quien recibe el documento, sin puntos y con guion. Ausente cuando Notta no lo informó. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"dtes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "El identificador del documento en Notta. Es lo que reciben notta.dte.consultar, notta.dte.descargar y notta.dte.reenviar."
},
"tipo_dte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El código de tipo de documento del catálogo del SII. Los que esta conexión emite son 33 (factura afecta), 34 (factura exenta), 56 (nota de débito) y 61 (nota de crédito)."
},
"folio": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "El folio del documento: el correlativo que sale de los folios autorizados (CAF) y con el que el SII lo identifica. Se consume una sola vez y no se reutiliza, así que emitir dos veces por error gasta dos. Viene en null cuando Notta todavía no lo informó."
},
"estado": {
"type": "string",
"description": "El estado del documento. 'queued' es recién encolado con el folio ya asignado; 'EPR' es aceptado por el SII; 'RPR' y 'aceptado_con_reparos' son aceptado con reparos; 'RFR', 'RCT' y 'RSC' son rechazos terminales que ya no cambian. Decide por este campo, nunca por 'estado_legible'."
},
"estado_legible": {
"type": "string",
"description": "El 'estado' traducido a una frase en español para mostrarle a una persona. Es texto de presentación, no contrato: para decidir usa 'estado'."
},
"monto_total": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "El total del documento. Viene en null cuando Notta no lo informó, que no es lo mismo que un documento por cero."
},
"fecha_emision": {
"type": "string",
"description": "La fecha de emisión declarada en el documento, en formato AAAA-MM-DD."
},
"rut_receptor": {
"description": "El RUT de quien recibe el documento, sin puntos y con guion. Ausente cuando Notta no lo informó.",
"type": "string"
}
},
"required": [
"id",
"tipo_dte",
"folio",
"estado",
"estado_legible",
"monto_total",
"fecha_emision"
],
"additionalProperties": false
},
"description": "Los documentos, del más nuevo al más antiguo. Traen menos campos que notta.dte.consultar: sin neto, exento, IVA ni ambiente del SII."
}
},
"required": [
"dtes"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Reenviar un DTE por correo
> Reenvía el PDF y el XML de un DTE ya emitido al correo del receptor.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------- |
| **Tool ID** | `notta.dte.reenviar` |
| **Nombre MCP** | `notta__dte__reenviar` |
| **Conector** | `notta` |
| **Plano** | `action` |
| **Scope (permiso)** | `notta:write` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=false, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Sin correo\_receptor usa el que ya tiene guardado el documento; con él, lo envía a esa dirección y la recuerda. Es el camino conversacional para entregar un documento: no descarga nada, lo envía. No emite ni modifica el DTE.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ----------------- | --------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | string | sí | El id del documento en Notta: el que devolvió notta.dte.emitir, o el de una fila de notta.dte.listar. |
| `correo_receptor` | string `^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$` | no | A qué dirección enviarlo. Si la indicas, Notta manda el documento ahí y la recuerda para la próxima. Si la omites, usa el correo que el documento ya tiene guardado; si no tiene ninguno, la llamada responde un error de validación pidiéndolo. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "El id del documento en Notta: el que devolvió notta.dte.emitir, o el de una fila de notta.dte.listar."
},
"correo_receptor": {
"description": "A qué dirección enviarlo. Si la indicas, Notta manda el documento ahí y la recuerda para la próxima. Si la omites, usa el correo que el documento ya tiene guardado; si no tiene ninguno, la llamada responde un error de validación pidiéndolo.",
"type": "string",
"maxLength": 200,
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
}
},
"required": [
"id"
]
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ----------------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reenviado` | booleano | sí | Siempre true: recibir esta respuesta ya significa que Notta aceptó el reenvío. Un fallo llega como un error con su código de catálogo, nunca como false. |
| `correo_receptor` | string | no | La dirección a la que se envió, cuando se pudo saber cuál fue. Ausente si no se indicó una en la llamada y Notta tampoco la informó. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"reenviado": {
"type": "boolean",
"description": "Siempre true: recibir esta respuesta ya significa que Notta aceptó el reenvío. Un fallo llega como un error con su código de catálogo, nunca como false."
},
"correo_receptor": {
"description": "La dirección a la que se envió, cuando se pudo saber cuál fue. Ausente si no se indicó una en la llamada y Notta tampoco la informó.",
"type": "string"
}
},
"required": [
"reenviado"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
---
# Notta
> Las 6 tools de Notta en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | --------------------------- |
| **Código** | `notta` |
| **Tipo** | `billing` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `client_provided` |
| **Alcances** | ninguno |
| **Versión** | `1.0.0` |
## Tools [#tools]
* [`notta.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra Notta haciendo un login real (y su logout, a cargo del pipeline).
* [`notta.dte.consultar`](./dte-consultar): Consulta el estado actual de un DTE en Notta por su id.
* [`notta.dte.descargar`](./dte-descargar): Devuelve el XML firmado o el PDF de un DTE como base64.
* [`notta.dte.emitir`](./dte-emitir): Emite un DTE ante el SII vía Notta: factura afecta (33), exenta (34), nota de débito (56) o nota de crédito (61, que exige references\[] al documento original, y este solo puede ser una factura 33 o 34).
* [`notta.dte.listar`](./dte-listar): Lista los DTEs MÁS RECIENTES emitidos por esta empresa en Notta (del más nuevo al más antiguo).
* [`notta.dte.reenviar`](./dte-reenviar): Reenvía el PDF y el XML de un DTE ya emitido al correo del receptor.
---
# Consultar certificados de cotizaciones de Previred
> Lee los certificados oficiales de cotizaciones ya emitidos para esta conexión, uno por trabajador.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `previred.certificados.consultar` |
| **Nombre MCP** | `previred__certificados__consultar` |
| **Conector** | `previred` |
| **Plano** | `action` |
| **Lee el alcance** | `certificados` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Es el documento que Previred firma y que una persona pide para probar lo que se le cotizó: 'certificadoUrl' es un enlace firmado para descargarlo. Hay un certificado VIGENTE por trabajador, que cada sincronización reemplaza, y cubre la ventana máxima que Previred admite terminando en el período sincronizado; 'periodoDesde' y 'periodoHasta' dicen cuál es. Si lo que buscas son los montos y no el documento, 'previred.cotizaciones.consultar' los tiene sin descargar nada. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------------- | ------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rutTrabajador` | string `^\d{1,8}-[\dkK]$` | no | Filtra por UN trabajador. El RUT va sin puntos y con guion antes del dígito verificador: se guarda cifrado y el filtro corre sobre un índice ciego, así que solo calza escrito exactamente en esa forma. |
| `cursor` | string | no | Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"rutTrabajador": {
"type": "string",
"pattern": "^\\d{1,8}-[\\dkK]$",
"description": "Filtra por UN trabajador. El RUT va sin puntos y con guion antes del dígito verificador: se guarda cifrado y el filtro corre sobre un índice ciego, así que solo calza escrito exactamente en esa forma."
},
"cursor": {
"description": "Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.certificados.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"rutTrabajador":"12345678-5"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.certificados.consultar({ rutTrabajador: "12345678-5" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.certificados.consultar",
"params": {
"rutTrabajador": "12345678-5"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"certificados": [
{
"rutTrabajador": "12345678-5",
"nombreTrabajador": "PEREZ SOTO JUAN ANDRES",
"periodoDesde": "2023-07",
"periodoHasta": "2026-06",
"bytes": 25147,
"certificadoUrl": null,
"syncedAt": "2026-08-11T14:02:11.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.certificados.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> El RUT se guarda cifrado y el filtro corre sobre un índice ciego: mándalo sin puntos y con guion.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------- | --------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `certificados` | lista de objeto | sí | Los certificados guardados: hay uno vigente por trabajador, y cada sincronización lo reemplaza por uno más nuevo en vez de acumular uno por mes. | |
| `certificados[].rutTrabajador` | string | sí | El RUT del trabajador al que pertenece el certificado, sin puntos y con guion. | |
| `certificados[].nombreTrabajador` | string | null | sí | El nombre del trabajador según Previred, o null si el portal no lo trajo. |
| `certificados[].periodoDesde` | string | sí | Primer mes que cubre el certificado, en formato AAAA-MM. No lo elige quien llama: el conector usa la ventana más ancha que Previred admite, terminando en el período que se sincronizó. | |
| `certificados[].periodoHasta` | string | sí | Último mes que cubre el certificado, en formato AAAA-MM: el período que se sincronizó. | |
| `certificados[].bytes` | entero | sí | Tamaño del PDF en bytes. | |
| `certificados[].certificadoUrl` | string | null | sí | Enlace firmado de vida corta para descargar el PDF del certificado, o null si todavía no se ha emitido. Caduca a los pocos minutos y no sirve para compartir. |
| `certificados[].syncedAt` | string | sí | Cuándo se guardó esta fila en Connect (ISO 8601). Dice qué tan fresca está la caché: si la última sincronización es vieja, lo que falta puede existir en Previred y todavía no haberse traído. | |
| `cursor` | string | null | sí | Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"certificados": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rutTrabajador": {
"type": "string",
"description": "El RUT del trabajador al que pertenece el certificado, sin puntos y con guion."
},
"nombreTrabajador": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El nombre del trabajador según Previred, o null si el portal no lo trajo."
},
"periodoDesde": {
"type": "string",
"description": "Primer mes que cubre el certificado, en formato AAAA-MM. No lo elige quien llama: el conector usa la ventana más ancha que Previred admite, terminando en el período que se sincronizó."
},
"periodoHasta": {
"type": "string",
"description": "Último mes que cubre el certificado, en formato AAAA-MM: el período que se sincronizó."
},
"bytes": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Tamaño del PDF en bytes."
},
"certificadoUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Enlace firmado de vida corta para descargar el PDF del certificado, o null si todavía no se ha emitido. Caduca a los pocos minutos y no sirve para compartir."
},
"syncedAt": {
"type": "string",
"description": "Cuándo se guardó esta fila en Connect (ISO 8601). Dice qué tan fresca está la caché: si la última sincronización es vieja, lo que falta puede existir en Previred y todavía no haberse traído."
}
},
"required": [
"rutTrabajador",
"nombreTrabajador",
"periodoDesde",
"periodoHasta",
"bytes",
"certificadoUrl",
"syncedAt"
],
"additionalProperties": false
},
"description": "Los certificados guardados: hay uno vigente por trabajador, y cada sincronización lo reemplaza por uno más nuevo en vez de acumular uno por mes."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última."
}
},
"required": [
"certificados",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Sincronizar conexión Previred
> Sincroniza los alcances solicitados (planillas, cotizaciones, deuda, f301) para un período en una sola sesión de portal.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------------ |
| **Tool ID** | `previred.conexion.sincronizar` |
| **Nombre MCP** | `previred__conexion__sincronizar` |
| **Conector** | `previred` |
| **Plano** | `read` |
| **Alcances** | `planillas`, `cotizaciones`, `deuda`, `f301`, `certificados`, `empresas` |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Es el ÚNICO camino que trae datos de Previred: las tools '.consultar' leen lo que esto haya guardado. 'deuda' es el estado del momento y no del período, así que solo se sincroniza cuando se pide el período corriente. 'f301' trae el archivo de 106 campos con que la Dirección del Trabajo emite el Certificado F30-1 de ese período. 'certificados' emite el certificado oficial de cotizaciones de CADA trabajador y por eso es el alcance más caro: cuesta una petición al portal por persona. 'empresas' lista las empresas que la credencial administra y no cuesta ninguna petición: ese listado ya llega al iniciar sesión.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | -------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | sí | El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes. |
| `alcances` | lista de `"planillas"` · `"cotizaciones"` · `"deuda"` · `"f301"` · `"certificados"` · `"empresas"` | sí | Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance\_not\_enabled'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes."
},
"alcances": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"planillas",
"cotizaciones",
"deuda",
"f301",
"certificados",
"empresas"
]
},
"description": "Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance_not_enabled'."
}
},
"required": [
"periodo",
"alcances"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-06","alcances":["planillas","cotizaciones"]}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.conexion.sincronizar({ periodo: "2026-06", alcances: ["planillas", "cotizaciones"] }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.conexion.sincronizar",
"params": {
"periodo": "2026-06",
"alcances": [
"planillas",
"cotizaciones"
]
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"periodo": "2026-06",
"results": [
{
"alcance": "planillas",
"status": "ok",
"recordsSynced": 4
},
{
"alcance": "cotizaciones",
"status": "ok",
"recordsSynced": 4
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.conexion.sincronizar",
"plane": "read",
"latency_ms": 58240,
"audit_status": "recorded"
}
}
```
> Un pago de un período se abre en varias planillas, una por institución previsional: cuatro registros para un solo trabajador es lo normal.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string | sí | Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú. |
| `results` | lista de objeto | sí | El resultado de cada alcance por separado: una fila por cada uno de los que pediste. |
| `results[].alcance` | string | sí | Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste. |
| `results[].status` | `"ok"` · `"failed"` | sí | 'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].recordsSynced` | entero | sí | Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'. |
| `results[].error` | string | no | Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos\_truncated', 'cartolas\_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre. |
| `results[].detalle` | string | no | Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú."
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"alcance": {
"type": "string",
"description": "Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste."
},
"status": {
"type": "string",
"enum": [
"ok",
"failed"
],
"description": "'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'failed' = no terminó bien, y la causa va en 'error'. Ojo con un 'failed': NO garantiza que no se haya escrito nada. Cuando el sistema externo trunca un listado, el alcance queda 'failed' con las filas que alcanzó en 'recordsSynced'. Mira siempre las dos cosas juntas. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
},
"recordsSynced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'."
},
"error": {
"description": "Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos_truncated', 'cartolas_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre.",
"type": "string"
},
"detalle": {
"description": "Explicación en lenguaje llano, presente solo cuando el resultado necesita una. Existe para que un cero se pueda transmitir tal cual en vez de concluir «no hay datos»: transmítelo a quien pregunte en lugar de resumir el número solo.",
"type": "string"
}
},
"required": [
"alcance",
"status",
"recordsSynced"
],
"additionalProperties": false
},
"description": "El resultado de cada alcance por separado: una fila por cada uno de los que pediste."
}
},
"required": [
"periodo",
"results"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_sync_in_progress` | 409 | sí | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `too_many_pending` | 429 | sí | Deja terminar los trabajos en curso antes de encolar más. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.planillas.consultar`](./planillas-consultar): lee el alcance `planillas` que esta sincronización escribe.
* [`previred.cotizaciones.consultar`](./cotizaciones-consultar): lee el alcance `cotizaciones` que esta sincronización escribe.
* [`previred.deuda.consultar`](./deuda-consultar): lee el alcance `deuda` que esta sincronización escribe.
* [`previred.f301.consultar`](./f301-consultar): lee el alcance `f301` que esta sincronización escribe.
* [`previred.certificados.consultar`](./certificados-consultar): lee el alcance `certificados` que esta sincronización escribe.
* [`previred.empresas.consultar`](./empresas-consultar): lee el alcance `empresas` que esta sincronización escribe.
---
# Verificar conexión Previred
> Prueba las credenciales de la conexión contra Previred haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `previred.conexion.verificar` |
| **Nombre MCP** | `previred__conexion__verificar` |
| **Conector** | `previred` |
| **Plano** | `action` |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# Consultar cotizaciones por trabajador de Previred
> Lee las cotizaciones ya sincronizadas de esta conexión, por trabajador, período e institución.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `previred.cotizaciones.consultar` |
| **Nombre MCP** | `previred__cotizaciones__consultar` |
| **Conector** | `previred` |
| **Plano** | `action` |
| **Lee el alcance** | `cotizaciones` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Es la materia prima del «certificado de cotizaciones» que emite Previred: el certificado en sí es un PDF que se genera para el rango que se pida, así que aquí viven los HECHOS (quién cotizó cuánto, a qué institución, en qué mes) y no el documento. Un mismo trabajador y mes trae varias filas, una por institución. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------------- | ------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión. |
| `rutTrabajador` | string `^\d{1,8}-[\dkK]$` | no | Filtra por UN trabajador. El RUT va sin puntos y con guion antes del dígito verificador: se guarda cifrado y el filtro corre sobre un índice ciego, así que solo calza escrito exactamente en esa forma. |
| `cursor` | string | no | Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión."
},
"rutTrabajador": {
"type": "string",
"pattern": "^\\d{1,8}-[\\dkK]$",
"description": "Filtra por UN trabajador. El RUT va sin puntos y con guion antes del dígito verificador: se guarda cifrado y el filtro corre sobre un índice ciego, así que solo calza escrito exactamente en esa forma."
},
"cursor": {
"description": "Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.cotizaciones.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-06"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.cotizaciones.consultar({ periodo: "2026-06" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.cotizaciones.consultar",
"params": {
"periodo": "2026-06"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"cotizaciones": [
{
"rutTrabajador": "11111111-1",
"nombreTrabajador": "PERSONA DE EJEMPLO",
"periodo": "2026-06",
"institucion": "AFP Modelo",
"tipoInstitucion": "AFP",
"rentaImponible": 1890835,
"montoCotizacion": 189084,
"diasTrabajados": 30,
"folio": "2004202606000002"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.cotizaciones.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------- | --------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cotizaciones` | lista de objeto | sí | Las cotizaciones guardadas. Un mismo trabajador y mes traen varias filas, una por institución. | |
| `cotizaciones[].rutTrabajador` | string | sí | El RUT del trabajador, sin puntos y con guion. | |
| `cotizaciones[].nombreTrabajador` | string | null | sí | El nombre del trabajador tal como lo informa el comprobante, o null si no venía. |
| `cotizaciones[].periodo` | string | sí | El mes de remuneraciones al que corresponde la cotización, en formato AAAA-MM. No es el mes en que se pagó: eso lo dice la fecha de pago de su planilla, que cae al mes siguiente. | |
| `cotizaciones[].institucion` | string | sí | La institución previsional, con el nombre que le da Previred (la AFP, Fonasa o la isapre, el seguro de cesantía, la mutual, la caja de compensación). | |
| `cotizaciones[].tipoInstitucion` | string | null | sí | La familia de la institución, que es el eje por el que Previred agrupa y filtra (por ejemplo 'AFP' o 'MUTUAL'). null cuando el portal no la informó. |
| `cotizaciones[].rentaImponible` | número | null | sí | La renta imponible sobre la que se calculó ESTA cotización, en pesos. Un mismo trabajador y mes tienen una renta imponible distinta por institución, cada una con su propio tope: no las sumes ni las trates como el sueldo. null cuando el comprobante no traía el dato. |
| `cotizaciones[].montoCotizacion` | número | null | sí | Lo cotizado a esa institución en el período, en pesos. null cuando el comprobante no traía el dato. |
| `cotizaciones[].diasTrabajados` | entero | null | sí | Días trabajados informados en el período. Solo algunas instituciones los declaran (lo hace el Seguro Social y las demás no), así que en la mayoría de las filas viene null: eso es lo que dice el comprobante, no un hueco. |
| `cotizaciones[].folio` | string | null | sí | El folio de la planilla que incluye esta cotización: el puente hacia previred.planillas.consultar. null cuando no se pudo determinar. |
| `cursor` | string | null | sí | Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"cotizaciones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rutTrabajador": {
"type": "string",
"description": "El RUT del trabajador, sin puntos y con guion."
},
"nombreTrabajador": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El nombre del trabajador tal como lo informa el comprobante, o null si no venía."
},
"periodo": {
"type": "string",
"description": "El mes de remuneraciones al que corresponde la cotización, en formato AAAA-MM. No es el mes en que se pagó: eso lo dice la fecha de pago de su planilla, que cae al mes siguiente."
},
"institucion": {
"type": "string",
"description": "La institución previsional, con el nombre que le da Previred (la AFP, Fonasa o la isapre, el seguro de cesantía, la mutual, la caja de compensación)."
},
"tipoInstitucion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La familia de la institución, que es el eje por el que Previred agrupa y filtra (por ejemplo 'AFP' o 'MUTUAL'). null cuando el portal no la informó."
},
"rentaImponible": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "La renta imponible sobre la que se calculó ESTA cotización, en pesos. Un mismo trabajador y mes tienen una renta imponible distinta por institución, cada una con su propio tope: no las sumes ni las trates como el sueldo. null cuando el comprobante no traía el dato."
},
"montoCotizacion": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Lo cotizado a esa institución en el período, en pesos. null cuando el comprobante no traía el dato."
},
"diasTrabajados": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Días trabajados informados en el período. Solo algunas instituciones los declaran (lo hace el Seguro Social y las demás no), así que en la mayoría de las filas viene null: eso es lo que dice el comprobante, no un hueco."
},
"folio": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El folio de la planilla que incluye esta cotización: el puente hacia previred.planillas.consultar. null cuando no se pudo determinar."
}
},
"required": [
"rutTrabajador",
"nombreTrabajador",
"periodo",
"institucion",
"tipoInstitucion",
"rentaImponible",
"montoCotizacion",
"diasTrabajados",
"folio"
],
"additionalProperties": false
},
"description": "Las cotizaciones guardadas. Un mismo trabajador y mes traen varias filas, una por institución."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última."
}
},
"required": [
"cotizaciones",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar deuda previsional en Previred
> Lee la deuda previsional ya sincronizada de esta conexión, y responde la pregunta del mes: ¿está al día? Trae las dos mitades.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `previred.deuda.consultar` |
| **Nombre MCP** | `previred__deuda__consultar` |
| **Conector** | `previred` |
| **Plano** | `action` |
| **Lee el alcance** | `deuda` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
'dnp' son declaraciones sin pago, con su institución y sus cargos legales. 'por\_pagar' son las nóminas cuyo plazo CORRE y aún no se pagan: ahí 'institucion' es "Todas" y solo viene 'montoTotal', porque el portal da un total por nómina sin desglosarlo. Recuerda el calendario: el plazo vence el día 13 del mes siguiente al de las remuneraciones. Ojo con 'montoTotal': Previred lo recalcula según la fecha en que efectivamente se pague, así que el valor guardado es el del momento de la sincronización (por eso cada fila trae 'observadoEn') y NO una cifra a la que uno pueda comprometerse. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------- | ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión. |
| `tipo` | `"dnp"` · `"por_pagar"` | no | Filtra una de las dos mitades de la deuda: 'dnp' son las declaraciones sin pago y 'por\_pagar' las nóminas cuyo plazo todavía corre. Sin él, trae las dos. |
| `cursor` | string | no | Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión."
},
"tipo": {
"type": "string",
"enum": [
"dnp",
"por_pagar"
],
"description": "Filtra una de las dos mitades de la deuda: 'dnp' son las declaraciones sin pago y 'por_pagar' las nóminas cuyo plazo todavía corre. Sin él, trae las dos."
},
"cursor": {
"description": "Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.deuda.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.deuda.consultar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.deuda.consultar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"deudas": [
{
"periodo": "2026-05",
"institucion": "AFP Modelo",
"tipo": "dnp",
"montoNominal": 189084,
"cargosLegales": 4521,
"montoTotal": 193605,
"observadoEn": "2026-08-10T14:02:11.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.deuda.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Una lista vacía tras un sync exitoso sí es informativa: significa que Previred no reporta nada pendiente. Una fila 'por\_pagar' con institución "Todas" es una nómina completa por pagar, no un dato incompleto.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ------------------------ | ----------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `deudas` | lista de objeto | sí | Las filas de deuda guardadas, de las dos mitades ('dnp' y 'por\_pagar'). Una lista vacía después de un sync exitoso sí es informativa: significa que Previred no reporta nada pendiente. | |
| `deudas[].periodo` | string | sí | El mes de remuneraciones al que corresponde la deuda, en formato AAAA-MM. | |
| `deudas[].institucion` | string | sí | La institución a la que se le debe, con el nombre que le da Previred. En una fila 'por\_pagar' dice 'Todas': esa pantalla da un total por nómina sin desglosarlo por institución. | |
| `deudas[].tipo` | `"dnp"` · `"por_pagar"` | sí | 'dnp' es una declaración sin pago: la empresa declaró lo que debía y no lo pagó, y la fila trae su institución y sus cargos legales. 'por\_pagar' es una nómina cuyo plazo todavía corre y aún no se paga; ahí solo llega 'montoTotal'. El plazo vence el día 13 del mes siguiente al de las remuneraciones, así que una fila 'por\_pagar' más vieja que eso ya es deuda aunque Previred no la haya movido. | |
| `deudas[].montoNominal` | número | null | sí | Lo adeudado sin reajustes ni multas, en pesos. Viene en null en las filas 'por\_pagar', porque esa pantalla no desglosa el total. |
| `deudas[].cargosLegales` | número | null | sí | Reajustes, intereses y multas acumulados, en pesos. Previred los recalcula según la fecha en que se pague, así que es el valor del momento en que se sincronizó. Viene en null en las filas 'por\_pagar'. |
| `deudas[].montoTotal` | número | null | sí | Lo adeudado con sus cargos legales incluidos, en pesos. Es el valor del momento en que se sincronizó (lo dice 'observadoEn') y no una cifra a la que se pueda comprometer nadie: Previred lo recalcula según la fecha de pago. |
| `deudas[].observadoEn` | string | sí | Instante (ISO 8601) en que se observó esta deuda. Importa porque 'montoTotal' se mueve con el tiempo: un total viejo ya no es el que hay que pagar. | |
| `cursor` | string | null | sí | Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"deudas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "El mes de remuneraciones al que corresponde la deuda, en formato AAAA-MM."
},
"institucion": {
"type": "string",
"description": "La institución a la que se le debe, con el nombre que le da Previred. En una fila 'por_pagar' dice 'Todas': esa pantalla da un total por nómina sin desglosarlo por institución."
},
"tipo": {
"type": "string",
"enum": [
"dnp",
"por_pagar"
],
"description": "'dnp' es una declaración sin pago: la empresa declaró lo que debía y no lo pagó, y la fila trae su institución y sus cargos legales. 'por_pagar' es una nómina cuyo plazo todavía corre y aún no se paga; ahí solo llega 'montoTotal'. El plazo vence el día 13 del mes siguiente al de las remuneraciones, así que una fila 'por_pagar' más vieja que eso ya es deuda aunque Previred no la haya movido."
},
"montoNominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Lo adeudado sin reajustes ni multas, en pesos. Viene en null en las filas 'por_pagar', porque esa pantalla no desglosa el total."
},
"cargosLegales": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Reajustes, intereses y multas acumulados, en pesos. Previred los recalcula según la fecha en que se pague, así que es el valor del momento en que se sincronizó. Viene en null en las filas 'por_pagar'."
},
"montoTotal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Lo adeudado con sus cargos legales incluidos, en pesos. Es el valor del momento en que se sincronizó (lo dice 'observadoEn') y no una cifra a la que se pueda comprometer nadie: Previred lo recalcula según la fecha de pago."
},
"observadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que se observó esta deuda. Importa porque 'montoTotal' se mueve con el tiempo: un total viejo ya no es el que hay que pagar."
}
},
"required": [
"periodo",
"institucion",
"tipo",
"montoNominal",
"cargosLegales",
"montoTotal",
"observadoEn"
],
"additionalProperties": false
},
"description": "Las filas de deuda guardadas, de las dos mitades ('dnp' y 'por_pagar'). Una lista vacía después de un sync exitoso sí es informativa: significa que Previred no reporta nada pendiente."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última."
}
},
"required": [
"deudas",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar empresas de la credencial de Previred
> Lista las empresas que la credencial de esta conexión administra en Previred.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `previred.empresas.consultar` |
| **Nombre MCP** | `previred__empresas__consultar` |
| **Conector** | `previred` |
| **Plano** | `action` |
| **Lee el alcance** | `empresas` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Sirve para saber qué OTRAS empresas se podrían conectar con la misma clave, que es la pregunta típica de un contador con varias empresas a cargo. Ojo: cada conexión de Connect es UNA empresa, así que ver una empresa aquí no significa poder leer sus datos; para eso hay que crear su propia conexión. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| -------- | ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rut` | string `^\d{1,8}-[\dkK]$` | no | Filtra por el RUT de la EMPRESA, no el de un trabajador. Va sin puntos y con guion antes del dígito verificador. |
| `cursor` | string | no | Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"rut": {
"type": "string",
"pattern": "^\\d{1,8}-[\\dkK]$",
"description": "Filtra por el RUT de la EMPRESA, no el de un trabajador. Va sin puntos y con guion antes del dígito verificador."
},
"cursor": {
"description": "Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.empresas.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.empresas.consultar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.empresas.consultar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"empresas": [
{
"rut": "76543210-3",
"razonSocial": "COMERCIAL EJEMPLO SPA",
"codDivision": "00",
"syncedAt": "2026-08-11T14:02:11.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.empresas.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Sincronizar este alcance no cuesta ninguna petición a Previred: el listado ya llega al iniciar sesión, así que se puede pedir junto a cualquier otro sin costo adicional.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ------------------------ | --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `empresas` | lista de objeto | sí | Las empresas que la credencial de esta conexión administra en Previred. Ver una empresa aquí no es poder leer sus datos: cada conexión de Connect es UNA empresa, y para las demás hay que crear su propia conexión. | |
| `empresas[].rut` | string | sí | El RUT de la empresa, sin puntos y con guion. | |
| `empresas[].razonSocial` | string | sí | El nombre legal de la empresa, según Previred. | |
| `empresas[].codDivision` | string | sí | La división dentro de la empresa, que Previred trata como parte de la selección. '00' es la empresa sin divisiones, que es el caso general. | |
| `empresas[].syncedAt` | string | sí | Cuándo se guardó esta fila en Connect (ISO 8601). Dice qué tan fresca está la caché: si la última sincronización es vieja, lo que falta puede existir en Previred y todavía no haberse traído. | |
| `cursor` | string | null | sí | Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"empresas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rut": {
"type": "string",
"description": "El RUT de la empresa, sin puntos y con guion."
},
"razonSocial": {
"type": "string",
"description": "El nombre legal de la empresa, según Previred."
},
"codDivision": {
"type": "string",
"description": "La división dentro de la empresa, que Previred trata como parte de la selección. '00' es la empresa sin divisiones, que es el caso general."
},
"syncedAt": {
"type": "string",
"description": "Cuándo se guardó esta fila en Connect (ISO 8601). Dice qué tan fresca está la caché: si la última sincronización es vieja, lo que falta puede existir en Previred y todavía no haberse traído."
}
},
"required": [
"rut",
"razonSocial",
"codDivision",
"syncedAt"
],
"additionalProperties": false
},
"description": "Las empresas que la credencial de esta conexión administra en Previred. Ver una empresa aquí no es poder leer sus datos: cada conexión de Connect es UNA empresa, y para las demás hay que crear su propia conexión."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última."
}
},
"required": [
"empresas",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar archivos para el F30-1 de Previred
> Lee los archivos ya sincronizados con que la Dirección del Trabajo emite el Certificado F30-1 de Cumplimiento de Obligaciones Laborales y Previsionales, el que una empresa contratista tiene que entregarle a su mandante para que le paguen.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `previred.f301.consultar` |
| **Nombre MCP** | `previred__f301__consultar` |
| **Conector** | `previred` |
| **Plano** | `action` |
| **Lee el alcance** | `f301` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Cada fila es el archivo de 106 campos de un período y una nómina, y 'archivoUrl' es un enlace firmado para descargarlo y subirlo al sitio de la Dirección del Trabajo. Connect NO emite el certificado: entrega el archivo con que se pide. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------- | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión. |
| `cursor` | string | no | Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión."
},
"cursor": {
"description": "Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.f301.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-06"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.f301.consultar({ periodo: "2026-06" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.f301.consultar",
"params": {
"periodo": "2026-06"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"archivos": [
{
"periodo": "2026-06",
"nomina": "Junio 2026",
"centroCosto": "total",
"trabajadores": 12,
"bytes": 3288,
"archivoUrl": null,
"syncedAt": "2026-08-11T14:02:11.000Z"
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.f301.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Un período puede traer varias filas si la empresa carga más de una nómina. 'archivoUrl' viene en null hasta que el archivo se descarga, y cuando existe caduca a los pocos minutos.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| ------------------------- | --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `archivos` | lista de objeto | sí | Los archivos guardados. Cada uno cubre UNA nómina de un período, así que un período con dos nóminas trae dos filas. | |
| `archivos[].periodo` | string | sí | El período que cubre el archivo, en formato AAAA-MM. | |
| `archivos[].nomina` | string | sí | El nombre que la nómina tiene en Previred. Es lo que distingue dos archivos del mismo período. | |
| `archivos[].centroCosto` | string | sí | 'total' significa Total Empresa, que es lo único que este conector emite hoy. Está en la respuesta porque Previred también permite emitir el archivo por centro de costo, y ese sería otro archivo. | |
| `archivos[].trabajadores` | entero | sí | Cuántos trabajadores informa el archivo, una línea por cada uno. | |
| `archivos[].bytes` | entero | sí | Tamaño del archivo en bytes. | |
| `archivos[].archivoUrl` | string | null | sí | Enlace firmado de vida corta para descargar el archivo y subirlo al sitio de la Dirección del Trabajo, o null si todavía no se ha descargado. Caduca a los pocos minutos y no sirve para compartir: el archivo trae el RUT, el nombre y la renta de cada trabajador. |
| `archivos[].syncedAt` | string | sí | Cuándo se guardó esta fila en Connect (ISO 8601). Dice qué tan fresca está la caché: si la última sincronización es vieja, lo que falta puede existir en Previred y todavía no haberse traído. | |
| `cursor` | string | null | sí | Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"archivos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "El período que cubre el archivo, en formato AAAA-MM."
},
"nomina": {
"type": "string",
"description": "El nombre que la nómina tiene en Previred. Es lo que distingue dos archivos del mismo período."
},
"centroCosto": {
"type": "string",
"description": "'total' significa Total Empresa, que es lo único que este conector emite hoy. Está en la respuesta porque Previred también permite emitir el archivo por centro de costo, y ese sería otro archivo."
},
"trabajadores": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos trabajadores informa el archivo, una línea por cada uno."
},
"bytes": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Tamaño del archivo en bytes."
},
"archivoUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Enlace firmado de vida corta para descargar el archivo y subirlo al sitio de la Dirección del Trabajo, o null si todavía no se ha descargado. Caduca a los pocos minutos y no sirve para compartir: el archivo trae el RUT, el nombre y la renta de cada trabajador."
},
"syncedAt": {
"type": "string",
"description": "Cuándo se guardó esta fila en Connect (ISO 8601). Dice qué tan fresca está la caché: si la última sincronización es vieja, lo que falta puede existir en Previred y todavía no haberse traído."
}
},
"required": [
"periodo",
"nomina",
"centroCosto",
"trabajadores",
"bytes",
"archivoUrl",
"syncedAt"
],
"additionalProperties": false
},
"description": "Los archivos guardados. Cada uno cubre UNA nómina de un período, así que un período con dos nóminas trae dos filas."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última."
}
},
"required": [
"archivos",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Previred
> Las 8 tools de Previred en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | ------------------------------------------------------------------------ |
| **Código** | `previred` |
| **Tipo** | `tax_authority` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `portal_credentials` |
| **Alcances** | `planillas`, `cotizaciones`, `deuda`, `f301`, `certificados`, `empresas` |
| **Versión** | `1.3.0` |
## Tools [#tools]
* [`previred.certificados.consultar`](./certificados-consultar): Lee los certificados oficiales de cotizaciones ya emitidos para esta conexión, uno por trabajador.
* [`previred.conexion.sincronizar`](./conexion-sincronizar): Sincroniza los alcances solicitados (planillas, cotizaciones, deuda, f301) para un período en una sola sesión de portal.
* [`previred.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra Previred haciendo un login real (y su logout, a cargo del pipeline).
* [`previred.cotizaciones.consultar`](./cotizaciones-consultar): Lee las cotizaciones ya sincronizadas de esta conexión, por trabajador, período e institución.
* [`previred.deuda.consultar`](./deuda-consultar): Lee la deuda previsional ya sincronizada de esta conexión, y responde la pregunta del mes: ¿está al día? Trae las dos mitades.
* [`previred.empresas.consultar`](./empresas-consultar): Lista las empresas que la credencial de esta conexión administra en Previred.
* [`previred.f301.consultar`](./f301-consultar): Lee los archivos ya sincronizados con que la Dirección del Trabajo emite el Certificado F30-1 de Cumplimiento de Obligaciones Laborales y Previsionales, el que una empresa contratista tiene que entregarle a su mandante para que le paguen.
* [`previred.planillas.consultar`](./planillas-consultar): Lee las planillas de cotizaciones ya sincronizadas de esta conexión, de la más reciente a la más antigua, filtrables por período (AAAA-MM) y por institución.
---
# Consultar planillas pagadas de Previred
> Lee las planillas de cotizaciones ya sincronizadas de esta conexión, de la más reciente a la más antigua, filtrables por período (AAAA-MM) y por institución.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `previred.planillas.consultar` |
| **Nombre MCP** | `previred__planillas__consultar` |
| **Conector** | `previred` |
| **Plano** | `action` |
| **Lee el alcance** | `planillas` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `previred:read` |
| **Auth** | `none` |
| **Versión** | `1` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Un pago de un período se abre en VARIAS planillas, una por cada institución previsional (AFP, Fonasa o Isapre, AFC, mutual, CCAF): por eso un mismo período trae varias filas y eso es lo normal, no una duplicación. Cada fila trae su 'folio', que es el identificador con que Previred la direcciona, y 'comprobanteUrl' cuando el PDF ya está descargado. Lectura pura: NO contacta a Previred ni dispara una sincronización. Si el período nunca se sincronizó devuelve una lista vacía, que NO significa que no haya datos en Previred. Para traer datos nuevos, usa 'previred.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null hay más filas, reenvía ese valor tal cual; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------- | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión. |
| `institucion` | string | no | Filtra por institución previsional, con el nombre exacto que trae el campo 'institucion' de las filas. Sin él, trae todas. |
| `cursor` | string | no | Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio. |
| `limit` | entero 1-500 | no · default `100` | Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Filtra por un mes, en formato AAAA-MM. Sin él, la consulta trae todas las filas guardadas de esta conexión."
},
"institucion": {
"type": "string",
"description": "Filtra por institución previsional, con el nombre exacto que trae el campo 'institucion' de las filas. Sin él, trae todas."
},
"cursor": {
"description": "Continúa desde donde quedó la página anterior: reenvía tal cual el 'cursor' que vino en la respuesta. Es opaco, así que nunca lo construyas a mano. Sin él, la consulta empieza por el principio.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Cuántas filas traer como máximo, entre 1 y 500. Si se omite, 100.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/previred.planillas.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-06"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.previred.planillas.consultar({ periodo: "2026-06" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "previred.planillas.consultar",
"params": {
"periodo": "2026-06"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"planillas": [
{
"folio": "2055260600000004",
"periodo": "2026-06",
"institucion": "Instituto de Seguridad Laboral (ISL)",
"tipoInstitucion": "MUTUAL",
"idNomina": "75128352",
"montoPagado": 17585,
"fechaPago": "2026-07-13",
"afiliadosInformados": 1,
"comprobanteUrl": null
}
],
"cursor": null
},
"meta": {
"request_id": "req_…",
"tool_id": "previred.planillas.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> 'comprobanteUrl' viene en null cuando el PDF todavía no se ha descargado; cuando existe, es un enlace firmado de vida corta, no una ruta permanente.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------- | --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `planillas` | lista de objeto | sí | Las planillas guardadas, de la más reciente a la más antigua. Un pago de un período se abre en varias planillas, una por institución previsional: varias filas del mismo período es lo normal, no una duplicación. | |
| `planillas[].folio` | string | sí | El identificador que Previred le da a la planilla, y con el que el portal la direcciona. Son 16 dígitos y es opaco: no lo descompongas, porque los folios reales no siguen un patrón parejo. | |
| `planillas[].periodo` | string | sí | El mes de remuneraciones que paga esta planilla, en formato AAAA-MM. | |
| `planillas[].institucion` | string | sí | La institución previsional, con el nombre que le da Previred (la AFP, Fonasa o la isapre, el seguro de cesantía, la mutual, la caja de compensación). | |
| `planillas[].tipoInstitucion` | string | null | sí | La familia de la institución, que es el eje por el que Previred agrupa y filtra (por ejemplo 'AFP' o 'MUTUAL'). null cuando el portal no la informó. |
| `planillas[].idNomina` | string | null | sí | Identificador de la nómina dentro del período. Una empresa puede tener varias en el mismo mes, así que agrupar solo por período las mezcla. |
| `planillas[].montoPagado` | número | null | sí | Lo pagado a esa institución, en pesos. null cuando el portal no trajo la celda, nunca 0: un 0 es un monto real y confundirlos mentiría sobre la plata. |
| `planillas[].fechaPago` | string | null | sí | Cuándo se pagó la planilla, según el comprobante. null cuando el portal no lo informó. |
| `planillas[].afiliadosInformados` | entero | null | sí | Cuántos trabajadores informa esta planilla. null cuando el portal no trajo el dato. |
| `planillas[].comprobanteUrl` | string | null | sí | Enlace firmado de vida corta al comprobante de pago en PDF, o null si todavía no se ha descargado (la planilla vale igual y el próximo sync lo reintenta). Caduca a los pocos minutos y no sirve para compartir: el documento trae el RUT, el nombre y la renta de los trabajadores. |
| `cursor` | string | null | sí | Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"planillas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"folio": {
"type": "string",
"description": "El identificador que Previred le da a la planilla, y con el que el portal la direcciona. Son 16 dígitos y es opaco: no lo descompongas, porque los folios reales no siguen un patrón parejo."
},
"periodo": {
"type": "string",
"description": "El mes de remuneraciones que paga esta planilla, en formato AAAA-MM."
},
"institucion": {
"type": "string",
"description": "La institución previsional, con el nombre que le da Previred (la AFP, Fonasa o la isapre, el seguro de cesantía, la mutual, la caja de compensación)."
},
"tipoInstitucion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La familia de la institución, que es el eje por el que Previred agrupa y filtra (por ejemplo 'AFP' o 'MUTUAL'). null cuando el portal no la informó."
},
"idNomina": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Identificador de la nómina dentro del período. Una empresa puede tener varias en el mismo mes, así que agrupar solo por período las mezcla."
},
"montoPagado": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Lo pagado a esa institución, en pesos. null cuando el portal no trajo la celda, nunca 0: un 0 es un monto real y confundirlos mentiría sobre la plata."
},
"fechaPago": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo se pagó la planilla, según el comprobante. null cuando el portal no lo informó."
},
"afiliadosInformados": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántos trabajadores informa esta planilla. null cuando el portal no trajo el dato."
},
"comprobanteUrl": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Enlace firmado de vida corta al comprobante de pago en PDF, o null si todavía no se ha descargado (la planilla vale igual y el próximo sync lo reintenta). Caduca a los pocos minutos y no sirve para compartir: el documento trae el RUT, el nombre y la renta de los trabajadores."
}
},
"required": [
"folio",
"periodo",
"institucion",
"tipoInstitucion",
"idNomina",
"montoPagado",
"fechaPago",
"afiliadosInformados",
"comprobanteUrl"
],
"additionalProperties": false
},
"description": "Las planillas guardadas, de la más reciente a la más antigua. Un pago de un período se abre en varias planillas, una por institución previsional: varias filas del mismo período es lo normal, no una duplicación."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuando no es null quedan más filas: reenvíalo tal cual en 'cursor' para pedir la página siguiente. En null significa que esta fue la última."
}
},
"required": [
"planillas",
"cursor"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`previred.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar boletas electrónicas del SII
> Lee el resumen diario de boletas electrónicas ya sincronizado para esta conexión, filtrado por período.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.boletas.consultar` |
| **Nombre MCP** | `sii__boletas__consultar` |
| **Conector** | `sii` |
| **Plano** | `action` |
| **Lee el alcance** | `boletas` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `none` |
| **Versión** | `4` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, use 'sii.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| --------- | ---------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `cursor` | string | no | Paginación: el valor que devolvió la respuesta anterior, tal cual. |
| `limit` | entero 1-500 | no · default `100` | Filas por página. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"cursor": {
"description": "Paginación: el valor que devolvió la respuesta anterior, tal cual.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Filas por página.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.boletas.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.boletas.consultar({ periodo: "2026-07" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.boletas.consultar",
"params": {
"periodo": "2026-07"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"documentos": [
{
"period": "2026-07",
"documentType": "39",
"day": 13,
"date": "2026-07-13",
"totalDocumentos": 42,
"netAmount": 389500,
"exemptAmount": 0,
"vatAmount": 74005,
"totalAmount": 463505,
"currency": "CLP",
"channel": null
},
{
"period": "2026-07",
"documentType": "39",
"day": 14,
"date": "2026-07-14",
"totalDocumentos": 51,
"netAmount": 452000,
"exemptAmount": 0,
"vatAmount": 85880,
"totalAmount": 537880,
"currency": "CLP",
"channel": null
}
],
"cursor": null,
"sincronizacion": {
"sincronizadoEn": "2026-08-06T03:15:42.000Z",
"completo": true,
"incompletos": 0,
"fueraDeVentana": null,
"perspectivasFallidas": []
}
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.boletas.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a dos días. Cada fila es el agregado de un día y un tipo de documento (39 = boleta afecta, 41 = boleta exenta), nunca una boleta individual.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------------------------- | ---------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `documentos` | lista de objeto | sí | El resumen diario de boletas electrónicas que calza con el filtro. Cada fila es el agregado de un día y un tipo de boleta (39 afecta, 41 exenta), nunca una boleta individual. Los montos son enteros en pesos chilenos, y un monto que el SII no informó llega como 0, no como 'null'. | |
| `documentos[].period` | string | sí | El período tributario del agregado, en formato AAAA-MM. | |
| `documentos[].documentType` | string | sí | Tipo de boleta, como texto: '39' es la boleta afecta y '41' la exenta. | |
| `documentos[].day` | entero | sí | El día del mes que resume esta fila. Cada fila es el agregado de un día y un tipo de boleta, nunca una boleta individual. | |
| `documentos[].date` | string | null | sí | El mismo día en formato AAAA-MM-DD, o 'null' si el SII mandó un día fuera de rango. |
| `documentos[].totalDocumentos` | entero | null | sí | Cuántas boletas de ese tipo se emitieron ese día. 'null' significa que el SII no informó el conteo, distinto de un 0 informado. |
| `documentos[].netAmount` | entero | sí | Monto neto del día en pesos chilenos, entero. | |
| `documentos[].exemptAmount` | entero | sí | Monto exento del día en pesos chilenos, entero. | |
| `documentos[].vatAmount` | entero | sí | IVA del día en pesos chilenos, entero. | |
| `documentos[].totalAmount` | entero | sí | Monto total del día en pesos chilenos, entero. | |
| `documentos[].currency` | string | sí | Siempre 'CLP': este resumen del SII sólo viene en pesos chilenos. | |
| `documentos[].channel` | string | null | sí | Canal de venta: 'presencial' o 'internet'. 'null' cuando el SII no desglosa por canal, que es lo habitual en boletas 39 y 41. |
| `cursor` | string | null | sí | El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano. |
| `sincronizacion` | objeto | null | sí | Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a). |
| `sincronizacion.sincronizadoEn` | string | sí | Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo. | |
| `sincronizacion.completo` | booleano | null | sí | 'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento. |
| `sincronizacion.incompletos` | entero | null | sí | Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber. |
| `sincronizacion.fueraDeVentana` | entero | null | sí | Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce. |
| `sincronizacion.perspectivasFallidas` | lista de objeto | sí | Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía. | |
| `sincronizacion.perspectivasFallidas[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron. | |
| `sincronizacion.perspectivasFallidas[].code` | string | sí | El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"documentos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"period": {
"type": "string",
"description": "El período tributario del agregado, en formato AAAA-MM."
},
"documentType": {
"type": "string",
"description": "Tipo de boleta, como texto: '39' es la boleta afecta y '41' la exenta."
},
"day": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El día del mes que resume esta fila. Cada fila es el agregado de un día y un tipo de boleta, nunca una boleta individual."
},
"date": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El mismo día en formato AAAA-MM-DD, o 'null' si el SII mandó un día fuera de rango."
},
"totalDocumentos": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántas boletas de ese tipo se emitieron ese día. 'null' significa que el SII no informó el conteo, distinto de un 0 informado."
},
"netAmount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto neto del día en pesos chilenos, entero."
},
"exemptAmount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto exento del día en pesos chilenos, entero."
},
"vatAmount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "IVA del día en pesos chilenos, entero."
},
"totalAmount": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto total del día en pesos chilenos, entero."
},
"currency": {
"type": "string",
"description": "Siempre 'CLP': este resumen del SII sólo viene en pesos chilenos."
},
"channel": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Canal de venta: 'presencial' o 'internet'. 'null' cuando el SII no desglosa por canal, que es lo habitual en boletas 39 y 41."
}
},
"required": [
"period",
"documentType",
"day",
"date",
"totalDocumentos",
"netAmount",
"exemptAmount",
"vatAmount",
"totalAmount",
"currency",
"channel"
],
"additionalProperties": false
},
"description": "El resumen diario de boletas electrónicas que calza con el filtro. Cada fila es el agregado de un día y un tipo de boleta (39 afecta, 41 exenta), nunca una boleta individual. Los montos son enteros en pesos chilenos, y un monto que el SII no informó llega como 0, no como 'null'."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano."
},
"sincronizacion": {
"anyOf": [
{
"type": "object",
"properties": {
"sincronizadoEn": {
"type": "string",
"description": "Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo."
},
"completo": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento."
},
"incompletos": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber."
},
"fueraDeVentana": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce."
},
"perspectivasFallidas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron."
},
"code": {
"type": "string",
"description": "El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto."
}
},
"required": [
"perspectiva",
"code"
],
"additionalProperties": false
},
"description": "Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía."
}
},
"required": [
"sincronizadoEn",
"completo",
"incompletos",
"fueraDeVentana",
"perspectivasFallidas"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a)."
}
},
"required": [
"documentos",
"cursor",
"sincronizacion"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar boletas de honorarios del SII
> Lee las boletas de honorarios electrónicas (BHE) ya sincronizadas para esta conexión, filtradas por período y/o perspectiva (emitidas = las que emitió esta empresa; recibidas = las que le emitieron, donde esta empresa es el agente retenedor).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.boletas_honorarios.consultar` |
| **Nombre MCP** | `sii__boletas_honorarios__consultar` |
| **Conector** | `sii` |
| **Plano** | `action` |
| **Lee el alcance** | `boletas_honorarios` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `none` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, usa 'sii.conexion.sincronizar' primero. El filtro tributario canónico es 'estado' distinto de 'S' sobre el código crudo: 'V' (anulación pendiente), 'R' y 'U' (observadas) siguen VIGENTES; solo 'S' está anulada: nunca filtres por 'estadoNormalizado' igual a 'vigente'. El 'estado' es el observado en la última sincronización del período, no el estado final: una BHE puede anularse, o revertir de anulación pendiente a vigente, hasta el 1 de marzo del año siguiente, y por petición administrativa sin plazo después. Resincroniza el período para refrescarlo; 'ultimaLecturaEn' dice cuándo se observó cada fila. La suma de 'retencion\_receptor' es el insumo para cuadrar el F29 código 151, no el código 151: ese además incluye las retenciones por BTE y se imputa al mes del pago. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------- | ---------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `perspectiva` | `"emitidas"` · `"recibidas"` | no | emitidas = las que emitió esta empresa; recibidas = las que le emitieron, donde esta empresa es el agente retenedor. Sin este filtro vienen las dos. |
| `cursor` | string | no | Paginación: el valor que devolvió la respuesta anterior, tal cual. |
| `limit` | entero 1-500 | no · default `100` | Filas por página. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"perspectiva": {
"description": "emitidas = las que emitió esta empresa; recibidas = las que le emitieron, donde esta empresa es el agente retenedor. Sin este filtro vienen las dos.",
"type": "string",
"enum": [
"emitidas",
"recibidas"
]
},
"cursor": {
"description": "Paginación: el valor que devolvió la respuesta anterior, tal cual.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Filas por página.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.boletas_honorarios.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","perspectiva":"recibidas"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.boletas_honorarios.consultar({ periodo: "2026-07", perspectiva: "recibidas" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.boletas_honorarios.consultar",
"params": {
"periodo": "2026-07",
"perspectiva": "recibidas"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"documentos": [
{
"folio": "153",
"perspectiva": "recibidas",
"periodo": "2026-07",
"fechaBoleta": "15/07/2026",
"fechaBoletaDate": "2026-07-15",
"rutContraparte": "12345678-5",
"razonSocialContraparte": "María José Riquelme Fuentes",
"codigoBarras": "108452276390415387",
"honorariosBrutos": 500000,
"retencionEmisor": 0,
"retencionReceptor": 76250,
"honorariosLiquidos": 423750,
"estado": "N",
"estadoNormalizado": "vigente",
"esSocProfesional": "NO",
"fechaEventoEstado": null,
"fechaEventoEstadoDate": null,
"ultimaLecturaEn": "2026-08-06T03:15:42.000Z"
}
],
"cursor": null,
"sincronizacion": {
"sincronizadoEn": "2026-08-06T03:15:42.000Z",
"completo": true,
"incompletos": 0,
"fueraDeVentana": null,
"perspectivasFallidas": []
}
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.boletas_honorarios.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a una boleta. En 'recibidas' esta empresa es el agente retenedor: 'retencionReceptor' se descuenta de 'honorariosBrutos' y 'honorariosLiquidos' es lo que recibe el profesional.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `documentos` | lista de objeto | sí | Las boletas de honorarios que calzan con el filtro, una por fila. Sale de la caché ya sincronizada, nunca de una consulta en vivo al SII. | |
| `documentos[].folio` | string | sí | El número de la boleta, tal cual lo manda el SII y como texto: puede traer ceros a la izquierda o no ser numérico, y no se normaliza. | |
| `documentos[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | emitidas = las boletas que emitió esta empresa; recibidas = las que le emitieron, donde esta empresa es el agente retenedor. | |
| `documentos[].periodo` | string | sí | El período tributario de la boleta, en formato AAAA-MM. | |
| `documentos[].fechaBoleta` | string | sí | La fecha de la boleta tal cual la manda el SII, en formato DD/MM/AAAA. Para ordenar o comparar usa 'fechaBoletaDate'. | |
| `documentos[].fechaBoletaDate` | string | null | sí | La misma fecha en formato AAAA-MM-DD, o 'null' si no se pudo parsear. |
| `documentos[].razonSocialContraparte` | string | sí | El nombre o razón social del otro lado: en 'recibidas' es el profesional que emitió, en 'emitidas' es el receptor. | |
| `documentos[].codigoBarras` | string | sí | El código de barras con que el SII identifica la boleta. Es único por boleta dentro del informe. | |
| `documentos[].honorariosBrutos` | entero | sí | El honorario bruto en pesos chilenos: lo facturado antes de descontar la retención. | |
| `documentos[].retencionEmisor` | entero | sí | La retención declarada por el propio emisor, en pesos chilenos. En 'recibidas' llega siempre en 0 porque ese informe no expone el campo: ahí la retención que importa es 'retencionReceptor'. | |
| `documentos[].retencionReceptor` | entero | sí | La retención que hizo el receptor como agente retenedor, en pesos chilenos. Su suma es el insumo para cuadrar el código 151 del F29, no el código 151 en sí. | |
| `documentos[].honorariosLiquidos` | entero | sí | Lo que recibe el profesional: el bruto menos la retención, en pesos chilenos. | |
| `documentos[].estado` | string | sí | El código de estado tal cual lo manda el SII: 'N' vigente, 'S' anulada, 'V' anulación pendiente, 'R' y 'U' observadas. El filtro tributario correcto es 'estado' distinto de 'S', porque 'V', 'R' y 'U' siguen vigentes. | |
| `documentos[].estadoNormalizado` | `"vigente"` · `"anulada"` · `"vigente_anulacion_pendiente"` · `"observada_receptor"` · `"observada_unidad"` · `"desconocido"` | sí | El mismo estado traducido a un enum estable. No lo uses para filtrar lo vigente: 'vigente\_anulacion\_pendiente', 'observada\_receptor' y 'observada\_unidad' también lo están. Un código que no reconocemos sale 'desconocido' y nunca se omite de un cómputo. | |
| `documentos[].esSocProfesional` | string | null | sí | Si el emisor es una sociedad de profesionales, tal cual lo manda el SII. Es texto crudo, no un booleano, y puede venir 'null'. |
| `documentos[].fechaEventoEstado` | string | null | sí | Cuándo el SII registró el evento que dejó la boleta en su estado actual. Cubre anulación, solicitud de anulación y observación, no sólo la anulación. 'null' mientras no hubo evento. |
| `documentos[].fechaEventoEstadoDate` | string | null | sí | La misma fecha en formato AAAA-MM-DD, o 'null' si no vino o no se pudo parsear. |
| `documentos[].rutContraparte` | string | null | sí | El RUT del otro lado: en 'recibidas' es el profesional que emitió y en 'emitidas' es el receptor. 'null' cuando la boleta se emitió sin receptor, que el SII permite. |
| `documentos[].ultimaLecturaEn` | string | sí | Cuándo se observó esta fila por última vez, en ISO 8601 UTC. Una boleta de honorarios es mutable hasta el 1 de marzo del año siguiente: si esta marca es vieja, resincroniza el período antes de decidir sobre su estado. | |
| `cursor` | string | null | sí | El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano. |
| `sincronizacion` | objeto | null | sí | Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a). |
| `sincronizacion.sincronizadoEn` | string | sí | Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo. | |
| `sincronizacion.completo` | booleano | null | sí | 'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento. |
| `sincronizacion.incompletos` | entero | null | sí | Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber. |
| `sincronizacion.fueraDeVentana` | entero | null | sí | Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce. |
| `sincronizacion.perspectivasFallidas` | lista de objeto | sí | Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía. | |
| `sincronizacion.perspectivasFallidas[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron. | |
| `sincronizacion.perspectivasFallidas[].code` | string | sí | El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"documentos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"folio": {
"type": "string",
"description": "El número de la boleta, tal cual lo manda el SII y como texto: puede traer ceros a la izquierda o no ser numérico, y no se normaliza."
},
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "emitidas = las boletas que emitió esta empresa; recibidas = las que le emitieron, donde esta empresa es el agente retenedor."
},
"periodo": {
"type": "string",
"description": "El período tributario de la boleta, en formato AAAA-MM."
},
"fechaBoleta": {
"type": "string",
"description": "La fecha de la boleta tal cual la manda el SII, en formato DD/MM/AAAA. Para ordenar o comparar usa 'fechaBoletaDate'."
},
"fechaBoletaDate": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La misma fecha en formato AAAA-MM-DD, o 'null' si no se pudo parsear."
},
"razonSocialContraparte": {
"type": "string",
"description": "El nombre o razón social del otro lado: en 'recibidas' es el profesional que emitió, en 'emitidas' es el receptor."
},
"codigoBarras": {
"type": "string",
"description": "El código de barras con que el SII identifica la boleta. Es único por boleta dentro del informe."
},
"honorariosBrutos": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El honorario bruto en pesos chilenos: lo facturado antes de descontar la retención."
},
"retencionEmisor": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "La retención declarada por el propio emisor, en pesos chilenos. En 'recibidas' llega siempre en 0 porque ese informe no expone el campo: ahí la retención que importa es 'retencionReceptor'."
},
"retencionReceptor": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "La retención que hizo el receptor como agente retenedor, en pesos chilenos. Su suma es el insumo para cuadrar el código 151 del F29, no el código 151 en sí."
},
"honorariosLiquidos": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Lo que recibe el profesional: el bruto menos la retención, en pesos chilenos."
},
"estado": {
"type": "string",
"description": "El código de estado tal cual lo manda el SII: 'N' vigente, 'S' anulada, 'V' anulación pendiente, 'R' y 'U' observadas. El filtro tributario correcto es 'estado' distinto de 'S', porque 'V', 'R' y 'U' siguen vigentes."
},
"estadoNormalizado": {
"type": "string",
"enum": [
"vigente",
"anulada",
"vigente_anulacion_pendiente",
"observada_receptor",
"observada_unidad",
"desconocido"
],
"description": "El mismo estado traducido a un enum estable. No lo uses para filtrar lo vigente: 'vigente_anulacion_pendiente', 'observada_receptor' y 'observada_unidad' también lo están. Un código que no reconocemos sale 'desconocido' y nunca se omite de un cómputo."
},
"esSocProfesional": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Si el emisor es una sociedad de profesionales, tal cual lo manda el SII. Es texto crudo, no un booleano, y puede venir 'null'."
},
"fechaEventoEstado": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo el SII registró el evento que dejó la boleta en su estado actual. Cubre anulación, solicitud de anulación y observación, no sólo la anulación. 'null' mientras no hubo evento."
},
"fechaEventoEstadoDate": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La misma fecha en formato AAAA-MM-DD, o 'null' si no vino o no se pudo parsear."
},
"rutContraparte": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El RUT del otro lado: en 'recibidas' es el profesional que emitió y en 'emitidas' es el receptor. 'null' cuando la boleta se emitió sin receptor, que el SII permite."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se observó esta fila por última vez, en ISO 8601 UTC. Una boleta de honorarios es mutable hasta el 1 de marzo del año siguiente: si esta marca es vieja, resincroniza el período antes de decidir sobre su estado."
}
},
"required": [
"folio",
"perspectiva",
"periodo",
"fechaBoleta",
"fechaBoletaDate",
"razonSocialContraparte",
"codigoBarras",
"honorariosBrutos",
"retencionEmisor",
"retencionReceptor",
"honorariosLiquidos",
"estado",
"estadoNormalizado",
"esSocProfesional",
"fechaEventoEstado",
"fechaEventoEstadoDate",
"rutContraparte",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Las boletas de honorarios que calzan con el filtro, una por fila. Sale de la caché ya sincronizada, nunca de una consulta en vivo al SII."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano."
},
"sincronizacion": {
"anyOf": [
{
"type": "object",
"properties": {
"sincronizadoEn": {
"type": "string",
"description": "Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo."
},
"completo": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento."
},
"incompletos": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber."
},
"fueraDeVentana": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce."
},
"perspectivasFallidas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron."
},
"code": {
"type": "string",
"description": "El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto."
}
},
"required": [
"perspectiva",
"code"
],
"additionalProperties": false
},
"description": "Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía."
}
},
"required": [
"sincronizadoEn",
"completo",
"incompletos",
"fueraDeVentana",
"perspectivasFallidas"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a)."
}
},
"required": [
"documentos",
"cursor",
"sincronizacion"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Sincronizar conexión SII
> Sincroniza los alcances solicitados (rcv, boletas, guias, boletas_honorarios, documentos) para un período en una sola sesión (un login, un logout).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.conexion.sincronizar` |
| **Nombre MCP** | `sii__conexion__sincronizar` |
| **Conector** | `sii` |
| **Plano** | `read` |
| **Alcances** | `rcv`, `boletas`, `guias`, `boletas_honorarios`, `documentos` |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `6` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ---------- | ------------------------------------------------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | sí | El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes. |
| `alcances` | lista de `"rcv"` · `"boletas"` · `"guias"` · `"boletas_honorarios"` · `"documentos"` | sí | Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance\_not\_enabled'. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "El mes que se va a sincronizar, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Traer varios meses son varias llamadas, una por mes."
},
"alcances": {
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"rcv",
"boletas",
"guias",
"boletas_honorarios",
"documentos"
]
},
"description": "Qué módulos de datos traer en esta corrida, al menos uno. Todos se sincronizan sobre UNA sola sesión (un login, un logout), así que pedir varios en una llamada cuesta menos que llamar una vez por cada uno. Un alcance debe estar habilitado en la conexión; si no lo está, la llamada responde 'alcance_not_enabled'."
}
},
"required": [
"periodo",
"alcances"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.conexion.sincronizar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","alcances":["rcv","boletas"]}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.conexion.sincronizar({ periodo: "2026-07", alcances: ["rcv", "boletas"] }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.conexion.sincronizar",
"params": {
"periodo": "2026-07",
"alcances": [
"rcv",
"boletas"
]
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"periodo": "2026-07",
"results": [
{
"alcance": "rcv",
"status": "ok",
"recordsSynced": 214,
"incompletos": 0,
"completo": true,
"reconMismatches": 0,
"dedupCollisions": 0,
"filasDescartadas": 0
},
{
"alcance": "boletas",
"status": "ok",
"recordsSynced": 27
}
]
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.conexion.sincronizar",
"plane": "read",
"latency_ms": 58240,
"audit_status": "recorded"
}
}
```
> Todos los alcances pedidos comparten una sola sesión contra el SII: un login al empezar y un logout al final. Los contadores adicionales varían por alcance; 'boletas' solo reporta 'recordsSynced'.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| ---------------------------------------------- | --------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string | sí | Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú. |
| `results` | lista de objeto | sí | El resultado de cada alcance pedido, una fila por alcance. Los alcances son independientes: uno puede fallar mientras los otros de la misma corrida terminan bien, así que revisa la lista entera. |
| `results[].alcance` | string | sí | Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste. |
| `results[].status` | `"ok"` · `"partial"` · `"failed"` | sí | 'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'partial' = trajo datos pero alguna casilla quedó incompleta, y 'incompletos' dice cuántas: lo sincronizado sirve, y reintentar el mismo período más tarde puede completarlo. 'failed' = no terminó bien, y la causa va en 'error'; mira igual 'recordsSynced', porque un 'failed' no garantiza que no se haya escrito nada. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien. |
| `results[].recordsSynced` | entero | sí | Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'. |
| `results[].incompletos` | entero | no | Cuántas casillas de este alcance quedaron sin traer. Es lo que vuelve 'partial' al status: lo sincronizado sirve, y reintentar el mismo período más tarde puede completarlo. Una casilla legítimamente vacía no cuenta. |
| `results[].completo` | booleano | no | 'true' sólo si ninguna casilla de este alcance falló. No alcanza por sí solo para dar el período por cerrado: revísalo junto con 'reconMismatches' y 'filasDescartadas', porque un documento puede faltar por esas dos vías sin que 'completo' se entere. |
| `results[].reconMismatches` | entero | no | Veces que las filas del detalle no coincidieron con el total que el resumen del SII declaraba. Es observabilidad y no detiene el sync, pero un valor distinto de 0 dice que el período puede estar incompleto. |
| `results[].dedupCollisions` | entero | no | Cuántas filas llegaron repetidas dentro de esta misma corrida (misma clave natural) y se colapsaron en una. No se cuentan dos veces en 'recordsSynced'. |
| `results[].filasDescartadas` | entero | no | Filas que llegaron con una forma inesperada (sin tipo ni folio resoluble) y no se pudieron guardar. Un valor distinto de 0 significa que el alcance corrió entero pero se perdieron filas, aunque 'completo' diga 'true'. |
| `results[].fueraDeVentana` | entero | no | Sólo en 'guias': cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. No es una falla y el status igual sale 'ok', pero es lo único que distingue 'no había guías' de 'no pudimos verlas'. Reintentar no lo arregla. |
| `results[].perspectivasFallidas` | lista de objeto | no | Sólo en 'boletas\_honorarios': qué direcciones fallaron enteras, con su código de error. Ese alcance la emite siempre, aunque quede vacía; ningún otro la emite. |
| `results[].perspectivasFallidas[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron. |
| `results[].perspectivasFallidas[].code` | string | sí | El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto. |
| `results[].total` | entero | no | Sólo en 'documentos': cuántos DTE anunció el índice del SII para el período. Es lo ESPERADO, no lo descargado. |
| `results[].ventanas` | entero | no | Sólo en 'documentos': cuántas ventanas de descarga (hasta 20 folios cada una) hicieron falta para bajar el período. |
| `results[].documentos` | entero | no | Sólo en 'documentos': cuántos DTE se descargaron de verdad. Compáralo con 'total': la diferencia es 'faltantes'. |
| `results[].faltantes` | entero | no | Sólo en 'documentos': cuántos DTE prometió el índice y la descarga no trajo. Es lo que distingue un hueco del SII de un hueco nuestro; lo que sí bajó se guarda igual. |
| `results[].sinIndice` | entero | no | Sólo en 'documentos': cuántos DTE se descargaron sin que su clave apareciera en el índice del listado. Significa que el índice quedó corto, distinto de que la fila no trajera estado (eso llega como 'estado' en null). |
| `results[].hashMismatches` | entero | no | Sólo en 'documentos': cuántos documentos repetidos traían un XML distinto. Un DTE firmado es inmutable, así que un valor distinto de 0 es una anomalía para reportar, nunca un documento que cambió. |
| `results[].error` | string | no | Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos\_truncated', 'cartolas\_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"description": "Eco del período que se pidió, para poder correlacionar la respuesta sin guardarlo tú."
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"alcance": {
"type": "string",
"description": "Cuál de los alcances pedidos describe esta fila. Hay una fila por alcance solicitado, en el orden canónico del conector, no en el orden en que los pediste."
},
"status": {
"type": "string",
"enum": [
"ok",
"partial",
"failed"
],
"description": "'ok' = el alcance terminó bien; que 'recordsSynced' sea 0 no lo vuelve un fallo. 'partial' = trajo datos pero alguna casilla quedó incompleta, y 'incompletos' dice cuántas: lo sincronizado sirve, y reintentar el mismo período más tarde puede completarlo. 'failed' = no terminó bien, y la causa va en 'error'; mira igual 'recordsSynced', porque un 'failed' no garantiza que no se haya escrito nada. Y revisa fila por fila: un alcance puede fallar mientras los otros de la misma corrida terminan bien."
},
"recordsSynced": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Cuántos registros de este alcance escribió ESTA corrida. Es el trabajo de esta llamada, no el total acumulado que tienes guardado: para saber cuánto hay, consulta. Un 0 no significa por sí solo «no hay datos»; cuando el cero tiene una explicación, viene en 'detalle'."
},
"incompletos": {
"description": "Cuántas casillas de este alcance quedaron sin traer. Es lo que vuelve 'partial' al status: lo sincronizado sirve, y reintentar el mismo período más tarde puede completarlo. Una casilla legítimamente vacía no cuenta.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"completo": {
"description": "'true' sólo si ninguna casilla de este alcance falló. No alcanza por sí solo para dar el período por cerrado: revísalo junto con 'reconMismatches' y 'filasDescartadas', porque un documento puede faltar por esas dos vías sin que 'completo' se entere.",
"type": "boolean"
},
"reconMismatches": {
"description": "Veces que las filas del detalle no coincidieron con el total que el resumen del SII declaraba. Es observabilidad y no detiene el sync, pero un valor distinto de 0 dice que el período puede estar incompleto.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"dedupCollisions": {
"description": "Cuántas filas llegaron repetidas dentro de esta misma corrida (misma clave natural) y se colapsaron en una. No se cuentan dos veces en 'recordsSynced'.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"filasDescartadas": {
"description": "Filas que llegaron con una forma inesperada (sin tipo ni folio resoluble) y no se pudieron guardar. Un valor distinto de 0 significa que el alcance corrió entero pero se perdieron filas, aunque 'completo' diga 'true'.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"fueraDeVentana": {
"description": "Sólo en 'guias': cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. No es una falla y el status igual sale 'ok', pero es lo único que distingue 'no había guías' de 'no pudimos verlas'. Reintentar no lo arregla.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"perspectivasFallidas": {
"description": "Sólo en 'boletas_honorarios': qué direcciones fallaron enteras, con su código de error. Ese alcance la emite siempre, aunque quede vacía; ningún otro la emite.",
"type": "array",
"items": {
"type": "object",
"properties": {
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron."
},
"code": {
"type": "string",
"description": "El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto."
}
},
"required": [
"perspectiva",
"code"
],
"additionalProperties": false
}
},
"total": {
"description": "Sólo en 'documentos': cuántos DTE anunció el índice del SII para el período. Es lo ESPERADO, no lo descargado.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"ventanas": {
"description": "Sólo en 'documentos': cuántas ventanas de descarga (hasta 20 folios cada una) hicieron falta para bajar el período.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"documentos": {
"description": "Sólo en 'documentos': cuántos DTE se descargaron de verdad. Compáralo con 'total': la diferencia es 'faltantes'.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"faltantes": {
"description": "Sólo en 'documentos': cuántos DTE prometió el índice y la descarga no trajo. Es lo que distingue un hueco del SII de un hueco nuestro; lo que sí bajó se guarda igual.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"sinIndice": {
"description": "Sólo en 'documentos': cuántos DTE se descargaron sin que su clave apareciera en el índice del listado. Significa que el índice quedó corto, distinto de que la fila no trajera estado (eso llega como 'estado' en null).",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"hashMismatches": {
"description": "Sólo en 'documentos': cuántos documentos repetidos traían un XML distinto. Un DTE firmado es inmutable, así que un valor distinto de 0 es una anomalía para reportar, nunca un documento que cambió.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"error": {
"description": "Por qué este alcance no terminó bien. Presente solo cuando 'status' es 'failed'. Normalmente es un código del catálogo de errores; cuando el sistema externo truncó el listado es una etiqueta de resultado ('movimientos_truncated', 'cartolas_truncated') que no está en ese catálogo y que significa «se escribió lo que alcanzó a venir». Decide por el valor, nunca por el texto libre.",
"type": "string"
}
},
"required": [
"alcance",
"status",
"recordsSynced"
],
"additionalProperties": false
},
"description": "El resultado de cada alcance pedido, una fila por alcance. Los alcances son independientes: uno puede fallar mientras los otros de la misma corrida terminan bien, así que revisa la lista entera."
}
},
"required": [
"periodo",
"results"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
| `connection_sync_in_progress` | 409 | sí | Espera a que termine y reintenta, o consulta directamente: puede que ya haya datos. |
| `too_many_pending` | 429 | sí | Deja terminar los trabajos en curso antes de encolar más. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.rcv.consultar`](./rcv-consultar): lee el alcance `rcv` que esta sincronización escribe.
* [`sii.boletas.consultar`](./boletas-consultar): lee el alcance `boletas` que esta sincronización escribe.
* [`sii.guias.consultar`](./guias-consultar): lee el alcance `guias` que esta sincronización escribe.
* [`sii.boletas_honorarios.consultar`](./boletas_honorarios-consultar): lee el alcance `boletas_honorarios` que esta sincronización escribe.
* [`sii.documentos.consultar`](./documentos-consultar): lee el alcance `documentos` que esta sincronización escribe.
* [`sii.documentos.detallar`](./documentos-detallar): lee el alcance `documentos` que esta sincronización escribe.
---
# Verificar conexión SII
> Prueba las credenciales de la conexión contra el SII haciendo un login real (y su logout, a cargo del pipeline).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.conexion.verificar` |
| **Nombre MCP** | `sii__conexion__verificar` |
| **Conector** | `sii` |
| **Plano** | `action` |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `connection_credentials` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=false, destructive=false, idempotent=true, openWorld=true |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
No sincroniza ni devuelve datos: solo confirma si las credenciales sirven.
## Entrada [#entrada]
Sin parámetros: envía `{}`.
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.conexion.verificar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.conexion.verificar({}, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.conexion.verificar",
"params": {},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"verificadoEn": "2026-08-07T14:12:03.220Z"
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.conexion.verificar",
"plane": "action",
"latency_ms": 7410,
"audit_status": "recorded"
}
}
```
> Si la credencial no sirve, la respuesta es un error connection\_credential\_required con su suggested\_fix; esta tool nunca devuelve un booleano.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción |
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verificadoEn` | string | sí | Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"verificadoEn": {
"type": "string",
"description": "Instante (ISO 8601) en que el login de prueba terminó bien. Es la única salida de esta tool: recibirla ya significa que la credencial sirve. Si no sirviera, la respuesta sería un error con su código de catálogo, nunca este objeto con un booleano en false."
}
},
"required": [
"verificadoEn"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| -------------------------------- | ---- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `connection_credential_required` | 428 | no | Crea un enlace con conexiones.enlace.crear (modo reconectar si la conexión ya existe) y pide a la persona que entregue la credencial de nuevo. No reintentes con la credencial anterior. |
| `connection_busy` | 409 | sí | Espera unos segundos y reintenta. El candado es por conexión y se suelta solo. |
| `upstream_error` | 502 | sí | Reintenta más tarde. Si persiste, el problema está en el sistema externo, no en tu integración. |
| `timeout` | 504 | sí | Reintenta. Para sincronizaciones largas usa la vía asíncrona y consulta el estado del trabajo. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.conexion.sincronizar`](./conexion-sincronizar): si la credencial verifica bien, el paso siguiente es traer datos.
---
# Consultar documentos respaldados del SII
> Lista los documentos tributarios (DTE) cuyo XML firmado ya se respaldó para esta conexión, filtrables por período, perspectiva y tipo de documento.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.documentos.consultar` |
| **Nombre MCP** | `sii__documentos__consultar` |
| **Conector** | `sii` |
| **Plano** | `action` |
| **Lee el alcance** | `documentos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `none` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Devuelve SOLO las columnas de cabecera: ni el XML ni el detalle de ítems viaja aquí. Para el detalle de UN documento (sus ítems con cantidad, unidad y precio, los giros y direcciones de emisor y receptor, y la forma de pago) usa 'sii.documentos.detallar' con el 'tipoDte', el 'folio' y el 'rutEmisor' de la fila correspondiente. Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'; para traer datos nuevos usa 'sii.conexion.sincronizar' primero. Este respaldo es lo que el RCV no tiene y no puede tener: el RCV dice qué documentos EXISTEN, este respaldo trae el documento. Sólo lo sirven las conexiones cuya credencial es la clave tributaria de una persona que representa a la empresa. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. Reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------- | ---------------------------- | ------------------ | ---------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Período tributario AAAA-MM. Sin él, la respuesta cruza períodos y 'sincronizacion' llega null. |
| `perspectiva` | `"emitidos"` · `"recibidos"` | no | emitidos = la empresa es el emisor; recibidos = es el receptor. |
| `tipoDte` | entero | no | Filtra por tipo de documento: 33, 34, 46, 52, 56 o 61. El portal no respalda boletas (39/41). |
| `cursor` | string | no | Paginación: el valor que devolvió la respuesta anterior, tal cual. |
| `limit` | entero 1-500 | no · default `100` | Filas por página. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"description": "Período tributario AAAA-MM. Sin él, la respuesta cruza períodos y 'sincronizacion' llega null.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}$"
},
"perspectiva": {
"description": "emitidos = la empresa es el emisor; recibidos = es el receptor.",
"type": "string",
"enum": [
"emitidos",
"recibidos"
]
},
"tipoDte": {
"description": "Filtra por tipo de documento: 33, 34, 46, 52, 56 o 61. El portal no respalda boletas (39/41).",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"cursor": {
"description": "Paginación: el valor que devolvió la respuesta anterior, tal cual.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Filas por página.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.documentos.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-06","perspectiva":"recibidos","tipoDte":33}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.documentos.consultar({ periodo: "2026-06", perspectiva: "recibidos", tipoDte: 33 }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.documentos.consultar",
"params": {
"periodo": "2026-06",
"perspectiva": "recibidos",
"tipoDte": 33
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"documentos": [
{
"tipoDte": 33,
"folio": "4712",
"rutEmisor": "76111222-8",
"rutReceptor": "77777777-7",
"razonSocialContraparte": "Proveedor Ejemplo SpA",
"perspectiva": "recibidos",
"periodo": "2026-06",
"fechaEmision": "2026-06-14",
"montoNeto": 1000000,
"iva": 190000,
"montoTotal": 1190000,
"estado": "REGISTRADO",
"dteHash": "9f2c1b7a4e5d8c3f0a6b9e2d4c7f1a8b5e3d6c9f2a4b7e1d8c5f3a6b9e2d4c7f",
"ultimaLecturaEn": "2026-08-09T03:15:42.000Z"
}
],
"cursor": null,
"sincronizacion": {
"sincronizadoEn": "2026-08-09T03:15:42.000Z",
"completo": true,
"incompletos": 0,
"fueraDeVentana": null,
"perspectivasFallidas": []
}
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.documentos.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a un documento. Para ver sus ítems, giros y forma de pago, llama a 'sii.documentos.detallar' con \{ tipoDte: 33, folio: '4712', rutEmisor: '76111222-8' }.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------------------------- | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `documentos` | lista de objeto | sí | Los documentos respaldados que calzan con el filtro, sólo con sus columnas de cabecera. Ni el XML ni el detalle de ítems viaja aquí: para eso usa 'sii.documentos.detallar'. | |
| `documentos[].tipoDte` | entero | sí | Código del tipo de DTE: 33 factura, 34 exenta, 46 factura de compra, 52 guía, 56 nota de débito, 61 nota de crédito. | |
| `documentos[].folio` | string | sí | El folio del documento, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'tipoDte' y 'rutEmisor' lo identifica de forma única, y es el valor que 'sii.documentos.detallar' espera TAL CUAL. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits. | |
| `documentos[].rutEmisor` | string | sí | Quien EMITIÓ el documento. Junto con tipoDte y folio identifica al documento de forma única. | |
| `documentos[].rutReceptor` | string | sí | Quien RECIBIÓ el documento: en una fila 'emitidos' es la contraparte, en 'recibidos' es la empresa de esta conexión. | |
| `documentos[].razonSocialContraparte` | string | sí | La razón social del lado que NO es la empresa de esta conexión. | |
| `documentos[].perspectiva` | `"emitidos"` · `"recibidos"` | sí | emitidos = esta empresa es el emisor; recibidos = es el receptor. Es DERIVADA del documento, no del filtro. | |
| `documentos[].periodo` | string | sí | El período tributario con que se sincronizó el documento, en formato AAAA-MM. | |
| `documentos[].fechaEmision` | string | null | sí | La fecha de emisión que declara el XML del documento, en formato AAAA-MM-DD. 'null' si la fila guardada no la trae. |
| `documentos[].montoNeto` | entero | null | sí | 'null' cuando el DTE no declaró \: un documento sólo exento no lo trae. Nunca se fabrica un 0. |
| `documentos[].iva` | entero | null | sí | 'null' cuando el DTE no declaró \, por el mismo motivo que montoNeto. |
| `documentos[].montoTotal` | entero | sí | Puede ser 0 legítimamente: una guía de traslado interno o una nota que corrige sólo texto lo exige por XSD. | |
| `documentos[].estado` | string | null | sí | El estado crudo del listado del portal, tal cual. Es el observado en la última sincronización, no el final. 'null' cuando el sync no pudo emparejar este documento con su fila del índice. |
| `documentos[].dteHash` | string | sí | sha256 del XML guardado. Un DTE firmado es inmutable: si cambia entre sincronizaciones, algo se movió. | |
| `documentos[].ultimaLecturaEn` | string | sí | Cuándo se observó esta fila por última vez, en ISO 8601 UTC. El 'estado' es el de esa lectura y no el final: resincroniza el período para refrescarlo. | |
| `cursor` | string | null | sí | El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano. |
| `sincronizacion` | objeto | null | sí | Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a). |
| `sincronizacion.sincronizadoEn` | string | sí | Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo. | |
| `sincronizacion.completo` | booleano | null | sí | 'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento. |
| `sincronizacion.incompletos` | entero | null | sí | Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber. |
| `sincronizacion.fueraDeVentana` | entero | null | sí | Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce. |
| `sincronizacion.perspectivasFallidas` | lista de objeto | sí | Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía. | |
| `sincronizacion.perspectivasFallidas[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron. | |
| `sincronizacion.perspectivasFallidas[].code` | string | sí | El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"documentos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tipoDte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Código del tipo de DTE: 33 factura, 34 exenta, 46 factura de compra, 52 guía, 56 nota de débito, 61 nota de crédito."
},
"folio": {
"type": "string",
"description": "El folio del documento, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'tipoDte' y 'rutEmisor' lo identifica de forma única, y es el valor que 'sii.documentos.detallar' espera TAL CUAL. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits."
},
"rutEmisor": {
"type": "string",
"description": "Quien EMITIÓ el documento. Junto con tipoDte y folio identifica al documento de forma única."
},
"rutReceptor": {
"type": "string",
"description": "Quien RECIBIÓ el documento: en una fila 'emitidos' es la contraparte, en 'recibidos' es la empresa de esta conexión."
},
"razonSocialContraparte": {
"type": "string",
"description": "La razón social del lado que NO es la empresa de esta conexión."
},
"perspectiva": {
"type": "string",
"enum": [
"emitidos",
"recibidos"
],
"description": "emitidos = esta empresa es el emisor; recibidos = es el receptor. Es DERIVADA del documento, no del filtro."
},
"periodo": {
"type": "string",
"description": "El período tributario con que se sincronizó el documento, en formato AAAA-MM."
},
"fechaEmision": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La fecha de emisión que declara el XML del documento, en formato AAAA-MM-DD. 'null' si la fila guardada no la trae."
},
"montoNeto": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "'null' cuando el DTE no declaró : un documento sólo exento no lo trae. Nunca se fabrica un 0."
},
"iva": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "'null' cuando el DTE no declaró , por el mismo motivo que montoNeto."
},
"montoTotal": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Puede ser 0 legítimamente: una guía de traslado interno o una nota que corrige sólo texto lo exige por XSD."
},
"estado": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El estado crudo del listado del portal, tal cual. Es el observado en la última sincronización, no el final. 'null' cuando el sync no pudo emparejar este documento con su fila del índice."
},
"dteHash": {
"type": "string",
"description": "sha256 del XML guardado. Un DTE firmado es inmutable: si cambia entre sincronizaciones, algo se movió."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se observó esta fila por última vez, en ISO 8601 UTC. El 'estado' es el de esa lectura y no el final: resincroniza el período para refrescarlo."
}
},
"required": [
"tipoDte",
"folio",
"rutEmisor",
"rutReceptor",
"razonSocialContraparte",
"perspectiva",
"periodo",
"fechaEmision",
"montoNeto",
"iva",
"montoTotal",
"estado",
"dteHash",
"ultimaLecturaEn"
],
"additionalProperties": false
},
"description": "Los documentos respaldados que calzan con el filtro, sólo con sus columnas de cabecera. Ni el XML ni el detalle de ítems viaja aquí: para eso usa 'sii.documentos.detallar'."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano."
},
"sincronizacion": {
"anyOf": [
{
"type": "object",
"properties": {
"sincronizadoEn": {
"type": "string",
"description": "Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo."
},
"completo": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento."
},
"incompletos": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber."
},
"fueraDeVentana": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce."
},
"perspectivasFallidas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron."
},
"code": {
"type": "string",
"description": "El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto."
}
},
"required": [
"perspectiva",
"code"
],
"additionalProperties": false
},
"description": "Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía."
}
},
"required": [
"sincronizadoEn",
"completo",
"incompletos",
"fueraDeVentana",
"perspectivasFallidas"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a)."
}
},
"required": [
"documentos",
"cursor",
"sincronizacion"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Detallar un documento respaldado del SII
> Devuelve UN documento tributario respaldado, con su detalle completo: los ítems (nombre, cantidad, unidad, precio unitario y monto), los giros, direcciones y comunas de emisor y receptor, y la forma de pago.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.documentos.detallar` |
| **Nombre MCP** | `sii__documentos__detallar` |
| **Conector** | `sii` |
| **Plano** | `action` |
| **Lee el alcance** | `documentos` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `none` |
| **Versión** | `2` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
El documento se identifica con las tres partes que lo hacen único ('tipoDte', 'folio' y 'rutEmisor'), y las tres salen de una fila de 'sii.documentos.consultar'. Lectura pura: NO contacta al SII, lee el XML que ya se respaldó y lo parsea en el momento. Si ese documento no está sincronizado, devuelve 'documento: null'. No es un error: es que no lo tenemos, así que sincroniza su período con 'sii.conexion.sincronizar' y vuelve a preguntar. El XML firmado sólo viaja si se pide 'incluirXml: true'; sin eso la respuesta trae el detalle ya estructurado, que es lo que casi siempre se necesita. Un ítem con 'cantidad', 'unidad' o 'precioUnitario' en null es un ítem que no los declaró (un flete, un descuento): no debe leerse como cero.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------ | -------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tipoDte` | entero | sí | El tipo del documento, de la fila de 'sii.documentos.consultar'. |
| `folio` | string | sí | El folio del documento, TAL CUAL viene en la fila de 'sii.documentos.consultar' (es texto: un identificador, no un número). Junto con tipoDte y rutEmisor lo identifica de forma única. |
| `rutEmisor` | string | sí | El RUT de QUIEN EMITIÓ el documento, no el de la contraparte (salvo que el documento haya sido emitido por esta empresa). Con o sin puntos, da igual. |
| `incluirXml` | booleano | no · default `false` | Si es true, la respuesta incluye además el XML firmado completo. Son unos 7 KB por documento. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"tipoDte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "El tipo del documento, de la fila de 'sii.documentos.consultar'."
},
"folio": {
"type": "string",
"minLength": 1,
"description": "El folio del documento, TAL CUAL viene en la fila de 'sii.documentos.consultar' (es texto: un identificador, no un número). Junto con tipoDte y rutEmisor lo identifica de forma única."
},
"rutEmisor": {
"type": "string",
"minLength": 1,
"description": "El RUT de QUIEN EMITIÓ el documento, no el de la contraparte (salvo que el documento haya sido emitido por esta empresa). Con o sin puntos, da igual."
},
"incluirXml": {
"default": false,
"description": "Si es true, la respuesta incluye además el XML firmado completo. Son unos 7 KB por documento.",
"type": "boolean"
}
},
"required": [
"tipoDte",
"folio",
"rutEmisor"
]
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.documentos.detallar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"tipoDte":33,"folio":"4712","rutEmisor":"76111222-8"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.documentos.detallar({ tipoDte: 33, folio: "4712", rutEmisor: "76111222-8" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.documentos.detallar",
"params": {
"tipoDte": 33,
"folio": "4712",
"rutEmisor": "76111222-8"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"documento": {
"tipoDte": 33,
"folio": "4712",
"rutEmisor": "76111222-8",
"rutReceptor": "77777777-7",
"razonSocialContraparte": "Proveedor Ejemplo SpA",
"perspectiva": "recibidos",
"periodo": "2026-06",
"fechaEmision": "2026-06-14",
"montoNeto": 300000,
"iva": 57000,
"montoTotal": 357000,
"estado": "REGISTRADO",
"dteHash": "9f2c1b7a4e5d8c3f0a6b9e2d4c7f1a8b5e3d6c9f2a4b7e1d8c5f3a6b9e2d4c7f",
"ultimaLecturaEn": "2026-08-09T03:15:42.000Z",
"detalle": {
"formaPago": "2",
"razonSocialEmisor": "Proveedor Ejemplo SpA",
"giroEmisor": "Venta de materiales de construcción",
"dirEmisor": "Av. Siempre Viva 742",
"cmnaEmisor": "Providencia",
"razonSocialReceptor": "Constructora Los Robles Ltda",
"giroReceptor": "Construcción de edificios",
"dirReceptor": "Los Robles 1200",
"cmnaReceptor": "Ñuñoa",
"items": [
{
"numeroLinea": 1,
"nombre": "Cemento 25 kg",
"cantidad": 40,
"unidad": "SACO",
"precioUnitario": 6500,
"montoItem": 260000
},
{
"numeroLinea": 2,
"nombre": "Flete",
"cantidad": null,
"unidad": null,
"precioUnitario": null,
"montoItem": 40000
}
]
}
},
"xml": null
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.documentos.detallar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a dos ítems. 'xml' llega null porque no se pidió 'incluirXml'. El segundo ítem no declara cantidad ni precio: es un cargo de línea. Sus null son literales, no deben leerse como cero.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------------- | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `documento` | objeto | null | sí | El documento pedido, con su detalle completo. 'null' significa que ese documento no está sincronizado: no es un error, sincroniza su período con 'sii.conexion.sincronizar' y vuelve a preguntar. |
| `documento.tipoDte` | entero | sí | Código del tipo de DTE: 33 factura, 34 exenta, 46 factura de compra, 52 guía, 56 nota de débito, 61 nota de crédito. | |
| `documento.folio` | string | sí | El folio del documento, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'tipoDte' y 'rutEmisor' lo identifica de forma única, y es el valor que 'sii.documentos.detallar' espera TAL CUAL. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits. | |
| `documento.rutEmisor` | string | sí | Quien EMITIÓ el documento. Junto con tipoDte y folio identifica al documento de forma única. | |
| `documento.rutReceptor` | string | sí | Quien RECIBIÓ el documento: en una fila 'emitidos' es la contraparte, en 'recibidos' es la empresa de esta conexión. | |
| `documento.razonSocialContraparte` | string | sí | La razón social del lado que NO es la empresa de esta conexión. | |
| `documento.perspectiva` | `"emitidos"` · `"recibidos"` | sí | emitidos = esta empresa es el emisor; recibidos = es el receptor. Es DERIVADA del documento, no del filtro. | |
| `documento.periodo` | string | sí | El período tributario con que se sincronizó el documento, en formato AAAA-MM. | |
| `documento.fechaEmision` | string | null | sí | La fecha de emisión que declara el XML del documento, en formato AAAA-MM-DD. 'null' si la fila guardada no la trae. |
| `documento.montoNeto` | entero | null | sí | 'null' cuando el DTE no declaró \: un documento sólo exento no lo trae. Nunca se fabrica un 0. |
| `documento.iva` | entero | null | sí | 'null' cuando el DTE no declaró \, por el mismo motivo que montoNeto. |
| `documento.montoTotal` | entero | sí | Puede ser 0 legítimamente: una guía de traslado interno o una nota que corrige sólo texto lo exige por XSD. | |
| `documento.estado` | string | null | sí | El estado crudo del listado del portal, tal cual. Es el observado en la última sincronización, no el final. 'null' cuando el sync no pudo emparejar este documento con su fila del índice. |
| `documento.dteHash` | string | sí | sha256 del XML guardado. Un DTE firmado es inmutable: si cambia entre sincronizaciones, algo se movió. | |
| `documento.ultimaLecturaEn` | string | sí | Cuándo se observó esta fila por última vez, en ISO 8601 UTC. El 'estado' es el de esa lectura y no el final: resincroniza el período para refrescarlo. | |
| `documento.detalle` | objeto | sí | Lo que el XML firmado trae y la cabecera no: los ítems, los giros, direcciones y comunas de emisor y receptor, y la forma de pago. Se parsea al leer, no se guarda aparte. | |
| `documento.detalle.formaPago` | string | null | sí | Código de forma de pago del DTE: 1 contado, 2 crédito, 3 sin costo. 'null' significa que el documento no lo declaró: nunca asumir contado. |
| `documento.detalle.razonSocialEmisor` | string | null | sí | La razón social de quien emitió el documento, según el XML. 'null' si el documento no la declaró. |
| `documento.detalle.giroEmisor` | string | null | sí | El giro (la actividad económica) de quien emitió el documento. 'null' si no lo declaró. |
| `documento.detalle.dirEmisor` | string | null | sí | La dirección del emisor según el XML. 'null' si no la declaró. |
| `documento.detalle.cmnaEmisor` | string | null | sí | La comuna del emisor según el XML. 'null' si no la declaró. |
| `documento.detalle.razonSocialReceptor` | string | null | sí | La razón social de quien recibió el documento, según el XML. 'null' si el documento no la declaró. |
| `documento.detalle.giroReceptor` | string | null | sí | El giro (la actividad económica) de quien recibió el documento. 'null' si no lo declaró. |
| `documento.detalle.dirReceptor` | string | null | sí | La dirección del receptor según el XML. 'null' si no la declaró. |
| `documento.detalle.cmnaReceptor` | string | null | sí | La comuna del receptor según el XML. 'null' si no la declaró. |
| `documento.detalle.items` | lista de objeto | sí | Las líneas del detalle del documento, en el orden en que vienen en el XML. Un documento sin líneas legibles llega con la lista vacía. | |
| `xml` | string | null | sí | El XML firmado del DTE, tal cual se respaldó. Sólo viaja si se pidió 'incluirXml: true'. |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"documento": {
"anyOf": [
{
"type": "object",
"properties": {
"tipoDte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Código del tipo de DTE: 33 factura, 34 exenta, 46 factura de compra, 52 guía, 56 nota de débito, 61 nota de crédito."
},
"folio": {
"type": "string",
"description": "El folio del documento, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'tipoDte' y 'rutEmisor' lo identifica de forma única, y es el valor que 'sii.documentos.detallar' espera TAL CUAL. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits."
},
"rutEmisor": {
"type": "string",
"description": "Quien EMITIÓ el documento. Junto con tipoDte y folio identifica al documento de forma única."
},
"rutReceptor": {
"type": "string",
"description": "Quien RECIBIÓ el documento: en una fila 'emitidos' es la contraparte, en 'recibidos' es la empresa de esta conexión."
},
"razonSocialContraparte": {
"type": "string",
"description": "La razón social del lado que NO es la empresa de esta conexión."
},
"perspectiva": {
"type": "string",
"enum": [
"emitidos",
"recibidos"
],
"description": "emitidos = esta empresa es el emisor; recibidos = es el receptor. Es DERIVADA del documento, no del filtro."
},
"periodo": {
"type": "string",
"description": "El período tributario con que se sincronizó el documento, en formato AAAA-MM."
},
"fechaEmision": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La fecha de emisión que declara el XML del documento, en formato AAAA-MM-DD. 'null' si la fila guardada no la trae."
},
"montoNeto": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "'null' cuando el DTE no declaró : un documento sólo exento no lo trae. Nunca se fabrica un 0."
},
"iva": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "'null' cuando el DTE no declaró , por el mismo motivo que montoNeto."
},
"montoTotal": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Puede ser 0 legítimamente: una guía de traslado interno o una nota que corrige sólo texto lo exige por XSD."
},
"estado": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El estado crudo del listado del portal, tal cual. Es el observado en la última sincronización, no el final. 'null' cuando el sync no pudo emparejar este documento con su fila del índice."
},
"dteHash": {
"type": "string",
"description": "sha256 del XML guardado. Un DTE firmado es inmutable: si cambia entre sincronizaciones, algo se movió."
},
"ultimaLecturaEn": {
"type": "string",
"description": "Cuándo se observó esta fila por última vez, en ISO 8601 UTC. El 'estado' es el de esa lectura y no el final: resincroniza el período para refrescarlo."
},
"detalle": {
"type": "object",
"properties": {
"formaPago": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Código de forma de pago del DTE: 1 contado, 2 crédito, 3 sin costo. 'null' significa que el documento no lo declaró: nunca asumir contado."
},
"razonSocialEmisor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La razón social de quien emitió el documento, según el XML. 'null' si el documento no la declaró."
},
"giroEmisor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El giro (la actividad económica) de quien emitió el documento. 'null' si no lo declaró."
},
"dirEmisor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La dirección del emisor según el XML. 'null' si no la declaró."
},
"cmnaEmisor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La comuna del emisor según el XML. 'null' si no la declaró."
},
"razonSocialReceptor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La razón social de quien recibió el documento, según el XML. 'null' si el documento no la declaró."
},
"giroReceptor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El giro (la actividad económica) de quien recibió el documento. 'null' si no lo declaró."
},
"dirReceptor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La dirección del receptor según el XML. 'null' si no la declaró."
},
"cmnaReceptor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La comuna del receptor según el XML. 'null' si no la declaró."
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"numeroLinea": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "El número de línea del ítem dentro del detalle. 'null' si el documento no lo declaró."
},
"nombre": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El nombre del ítem tal como lo escribió el emisor. 'null' si el documento no lo declaró."
},
"cantidad": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "La cantidad del ítem. El 'null' es literal: un flete o un descuento suelen no declararla, y no debe leerse como cero."
},
"unidad": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La unidad de medida del ítem, tal cual la escribió el emisor. 'null' si no la declaró."
},
"precioUnitario": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El precio unitario del ítem. El 'null' es literal, igual que en 'cantidad': no debe leerse como cero."
},
"montoItem": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "El monto de la línea. 'null' significa que el documento no lo declaró de forma legible, nunca un cero."
}
},
"required": [
"numeroLinea",
"nombre",
"cantidad",
"unidad",
"precioUnitario",
"montoItem"
],
"additionalProperties": false
},
"description": "Las líneas del detalle del documento, en el orden en que vienen en el XML. Un documento sin líneas legibles llega con la lista vacía."
}
},
"required": [
"formaPago",
"razonSocialEmisor",
"giroEmisor",
"dirEmisor",
"cmnaEmisor",
"razonSocialReceptor",
"giroReceptor",
"dirReceptor",
"cmnaReceptor",
"items"
],
"additionalProperties": false,
"description": "Lo que el XML firmado trae y la cabecera no: los ítems, los giros, direcciones y comunas de emisor y receptor, y la forma de pago. Se parsea al leer, no se guarda aparte."
}
},
"required": [
"tipoDte",
"folio",
"rutEmisor",
"rutReceptor",
"razonSocialContraparte",
"perspectiva",
"periodo",
"fechaEmision",
"montoNeto",
"iva",
"montoTotal",
"estado",
"dteHash",
"ultimaLecturaEn",
"detalle"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "El documento pedido, con su detalle completo. 'null' significa que ese documento no está sincronizado: no es un error, sincroniza su período con 'sii.conexion.sincronizar' y vuelve a preguntar."
},
"xml": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El XML firmado del DTE, tal cual se respaldó. Sólo viaja si se pidió 'incluirXml: true'."
}
},
"required": [
"documento",
"xml"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Consultar guías de despacho del SII
> Lee las guías de despacho electrónicas (DTE 52) ya sincronizadas para esta conexión, filtradas por período y/o perspectiva (emitidas = las que emitió esta empresa; recibidas = las que le emitieron).
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.guias.consultar` |
| **Nombre MCP** | `sii__guias__consultar` |
| **Conector** | `sii` |
| **Plano** | `action` |
| **Lee el alcance** | `guias` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `none` |
| **Versión** | `4` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, use 'sii.conexion.sincronizar' primero. Ojo: el SII solo conserva el detalle de guías de los últimos 6 meses, así que un período más viejo no se puede sincronizar aunque exista. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------- | ---------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato. |
| `perspectiva` | `"emitidas"` · `"recibidas"` | no | emitidas = las que emitió esta empresa; recibidas = las que le emitieron. Sin este filtro vienen las dos. |
| `cursor` | string | no | Paginación: el valor que devolvió la respuesta anterior, tal cual. |
| `limit` | entero 1-500 | no · default `100` | Filas por página. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}$",
"description": "Un mes, en formato AAAA-MM y en calendario chileno (por ejemplo '2026-07'). Cada sitio de uso afina qué significa ahí: en una sincronización es el mes que se va a traer, y en una consulta a la caché es el filtro. Esta descripción base existe para que el campo nunca llegue pelado a quien lee el contrato."
},
"perspectiva": {
"description": "emitidas = las que emitió esta empresa; recibidas = las que le emitieron. Sin este filtro vienen las dos.",
"type": "string",
"enum": [
"emitidas",
"recibidas"
]
},
"cursor": {
"description": "Paginación: el valor que devolvió la respuesta anterior, tal cual.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Filas por página.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.guias.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","perspectiva":"emitidas"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.guias.consultar({ periodo: "2026-07", perspectiva: "emitidas" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.guias.consultar",
"params": {
"periodo": "2026-07",
"perspectiva": "emitidas"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"documentos": [
{
"perspectiva": "emitidas",
"tipoDte": 52,
"periodo": "2026-07",
"folio": "1580",
"rutContraparte": "76543210-3",
"razonSocialContraparte": "Constructora Los Robles Ltda",
"montoNeto": 830000,
"montoExento": 0,
"montoIva": 157700,
"montoTotal": 987700,
"tasaIva": 1900,
"fechaEmision": "21/07/2026",
"fechaEmisionDate": "2026-07-21",
"fechaRecepcion": "2026-07-22",
"eventoOrden": null,
"eventoDescripcion": null,
"dhdrCodigo": null
},
{
"perspectiva": "emitidas",
"tipoDte": 52,
"periodo": "2026-07",
"folio": "1583",
"rutContraparte": "78900400-5",
"razonSocialContraparte": "Ferretería El Volcán SpA",
"montoNeto": 240000,
"montoExento": 0,
"montoIva": 45600,
"montoTotal": 285600,
"tasaIva": 1900,
"fechaEmision": "28/07/2026",
"fechaEmisionDate": "2026-07-28",
"fechaRecepcion": null,
"eventoOrden": null,
"eventoDescripcion": null,
"dhdrCodigo": null
}
],
"cursor": null,
"sincronizacion": {
"sincronizadoEn": "2026-08-06T03:15:42.000Z",
"completo": true,
"incompletos": 0,
"fueraDeVentana": 0,
"perspectivasFallidas": []
}
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.guias.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a dos guías. 'tasaIva' viaja como entero por cien (1900 = 19%); 'fueraDeVentana: 0' confirma que el período cae dentro de los 6 meses de detalle que conserva el SII.
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------------------------- | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `documentos` | lista de objeto | sí | Las guías de despacho que calzan con el filtro, una por fila. Sale de la caché ya sincronizada: el SII sólo conserva el detalle de los últimos 6 meses, así que un período más viejo no se puede traer aunque la guía exista. | |
| `documentos[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | emitidas = las guías que emitió esta empresa; recibidas = las que le emitieron. | |
| `documentos[].tipoDte` | entero | sí | Siempre 52: guía de despacho electrónica. | |
| `documentos[].periodo` | string | sí | El período tributario de la guía, en formato AAAA-MM. | |
| `documentos[].folio` | string | sí | El folio de la guía, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'perspectiva' y 'rutContraparte' la identifica. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits. Compáralo como cadena. | |
| `documentos[].rutContraparte` | string | sí | El RUT del otro lado: en 'emitidas' es el cliente y en 'recibidas' es quien emitió la guía. El RUT propio no viaja en la fila porque ya lo define la conexión. | |
| `documentos[].razonSocialContraparte` | string | sí | La razón social de ese mismo lado, tal como la informó el SII. | |
| `documentos[].montoNeto` | entero | sí | Monto neto en pesos chilenos, entero. | |
| `documentos[].montoExento` | entero | sí | Monto exento de IVA en pesos chilenos, entero. | |
| `documentos[].montoIva` | entero | sí | IVA en pesos chilenos, entero. | |
| `documentos[].montoTotal` | entero | sí | Monto total de la guía en pesos chilenos, entero. | |
| `documentos[].tasaIva` | entero | null | sí | La tasa de IVA multiplicada por cien: 1900 es 19%. 'null' cuando el SII no la informó. |
| `documentos[].fechaEmision` | string | sí | La fecha de emisión tal cual la manda el SII, en formato DD/MM/AAAA. Para ordenar o comparar usa 'fechaEmisionDate'. | |
| `documentos[].fechaEmisionDate` | string | null | sí | La misma fecha en formato AAAA-MM-DD, o 'null' si no se pudo parsear. |
| `documentos[].fechaRecepcion` | string | null | sí | Cuándo el SII recibió la guía, en formato AAAA-MM-DD. 'null' si no vino. |
| `documentos[].eventoOrden` | string | null | sí | El código del evento que registró el receptor sobre la guía, como texto. 'null' cuando no hubo evento. |
| `documentos[].eventoDescripcion` | string | null | sí | La descripción de ese mismo evento, por ejemplo 'Acuse recibo'. 'null' cuando no hubo evento o el SII no la mandó. |
| `documentos[].dhdrCodigo` | string | null | sí | Un identificador interno del SII para la guía. Sirve para correlacionar contra el portal, pero su estabilidad entre sincronizaciones no está verificada: no lo uses para identificar el documento. |
| `cursor` | string | null | sí | El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano. |
| `sincronizacion` | objeto | null | sí | Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a). |
| `sincronizacion.sincronizadoEn` | string | sí | Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo. | |
| `sincronizacion.completo` | booleano | null | sí | 'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento. |
| `sincronizacion.incompletos` | entero | null | sí | Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber. |
| `sincronizacion.fueraDeVentana` | entero | null | sí | Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce. |
| `sincronizacion.perspectivasFallidas` | lista de objeto | sí | Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía. | |
| `sincronizacion.perspectivasFallidas[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron. | |
| `sincronizacion.perspectivasFallidas[].code` | string | sí | El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"documentos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "emitidas = las guías que emitió esta empresa; recibidas = las que le emitieron."
},
"tipoDte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Siempre 52: guía de despacho electrónica."
},
"periodo": {
"type": "string",
"description": "El período tributario de la guía, en formato AAAA-MM."
},
"folio": {
"type": "string",
"description": "El folio de la guía, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'perspectiva' y 'rutContraparte' la identifica. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits. Compáralo como cadena."
},
"rutContraparte": {
"type": "string",
"description": "El RUT del otro lado: en 'emitidas' es el cliente y en 'recibidas' es quien emitió la guía. El RUT propio no viaja en la fila porque ya lo define la conexión."
},
"razonSocialContraparte": {
"type": "string",
"description": "La razón social de ese mismo lado, tal como la informó el SII."
},
"montoNeto": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto neto en pesos chilenos, entero."
},
"montoExento": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto exento de IVA en pesos chilenos, entero."
},
"montoIva": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "IVA en pesos chilenos, entero."
},
"montoTotal": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto total de la guía en pesos chilenos, entero."
},
"tasaIva": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "La tasa de IVA multiplicada por cien: 1900 es 19%. 'null' cuando el SII no la informó."
},
"fechaEmision": {
"type": "string",
"description": "La fecha de emisión tal cual la manda el SII, en formato DD/MM/AAAA. Para ordenar o comparar usa 'fechaEmisionDate'."
},
"fechaEmisionDate": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La misma fecha en formato AAAA-MM-DD, o 'null' si no se pudo parsear."
},
"fechaRecepcion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo el SII recibió la guía, en formato AAAA-MM-DD. 'null' si no vino."
},
"eventoOrden": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El código del evento que registró el receptor sobre la guía, como texto. 'null' cuando no hubo evento."
},
"eventoDescripcion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La descripción de ese mismo evento, por ejemplo 'Acuse recibo'. 'null' cuando no hubo evento o el SII no la mandó."
},
"dhdrCodigo": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Un identificador interno del SII para la guía. Sirve para correlacionar contra el portal, pero su estabilidad entre sincronizaciones no está verificada: no lo uses para identificar el documento."
}
},
"required": [
"perspectiva",
"tipoDte",
"periodo",
"folio",
"rutContraparte",
"razonSocialContraparte",
"montoNeto",
"montoExento",
"montoIva",
"montoTotal",
"tasaIva",
"fechaEmision",
"fechaEmisionDate",
"fechaRecepcion",
"eventoOrden",
"eventoDescripcion",
"dhdrCodigo"
],
"additionalProperties": false
},
"description": "Las guías de despacho que calzan con el filtro, una por fila. Sale de la caché ya sincronizada: el SII sólo conserva el detalle de los últimos 6 meses, así que un período más viejo no se puede traer aunque la guía exista."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano."
},
"sincronizacion": {
"anyOf": [
{
"type": "object",
"properties": {
"sincronizadoEn": {
"type": "string",
"description": "Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo."
},
"completo": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento."
},
"incompletos": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber."
},
"fueraDeVentana": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce."
},
"perspectivasFallidas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron."
},
"code": {
"type": "string",
"description": "El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto."
}
},
"required": [
"perspectiva",
"code"
],
"additionalProperties": false
},
"description": "Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía."
}
},
"required": [
"sincronizadoEn",
"completo",
"incompletos",
"fueraDeVentana",
"perspectivasFallidas"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a)."
}
},
"required": [
"documentos",
"cursor",
"sincronizacion"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.
---
# Servicio de Impuestos Internos
> Las 8 tools de Servicio de Impuestos Internos en el plan pagado.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ---------------- | ------------------------------------------------------------- |
| **Código** | `sii` |
| **Tipo** | `tax_authority` |
| **Plan** | `paid` |
| **Categoría** | ninguna (requiere conexión) |
| **Credenciales** | `portal_credentials`, `portal_credentials_representante` |
| **Alcances** | `rcv`, `boletas`, `guias`, `boletas_honorarios`, `documentos` |
| **Versión** | `1.0.0` |
## Tools [#tools]
* [`sii.boletas_honorarios.consultar`](./boletas_honorarios-consultar): Lee las boletas de honorarios electrónicas (BHE) ya sincronizadas para esta conexión, filtradas por período y/o perspectiva (emitidas = las que emitió esta empresa; recibidas = las que le emitieron, donde esta empresa es el agente retenedor).
* [`sii.boletas.consultar`](./boletas-consultar): Lee el resumen diario de boletas electrónicas ya sincronizado para esta conexión, filtrado por período.
* [`sii.conexion.sincronizar`](./conexion-sincronizar): Sincroniza los alcances solicitados (rcv, boletas, guias, boletas\_honorarios, documentos) para un período en una sola sesión (un login, un logout).
* [`sii.conexion.verificar`](./conexion-verificar): Prueba las credenciales de la conexión contra el SII haciendo un login real (y su logout, a cargo del pipeline).
* [`sii.documentos.consultar`](./documentos-consultar): Lista los documentos tributarios (DTE) cuyo XML firmado ya se respaldó para esta conexión, filtrables por período, perspectiva y tipo de documento.
* [`sii.documentos.detallar`](./documentos-detallar): Devuelve UN documento tributario respaldado, con su detalle completo: los ítems (nombre, cantidad, unidad, precio unitario y monto), los giros, direcciones y comunas de emisor y receptor, y la forma de pago.
* [`sii.guias.consultar`](./guias-consultar): Lee las guías de despacho electrónicas (DTE 52) ya sincronizadas para esta conexión, filtradas por período y/o perspectiva (emitidas = las que emitió esta empresa; recibidas = las que le emitieron).
* [`sii.rcv.consultar`](./rcv-consultar): Lee el Registro de Compra-Venta ya sincronizado para esta conexión, filtrable por período, perspectiva, tipo de documento (tipoDte) y estado del registro.
---
# Consultar RCV del SII
> Lee el Registro de Compra-Venta ya sincronizado para esta conexión, filtrable por período, perspectiva, tipo de documento (tipoDte) y estado del registro.
{/* AUTO-GENERATED by @emisso/codegen. DO NOT EDIT. */}
| | |
| ------------------- | ------------------------------------------------------------------ |
| **Tool ID** | `sii.rcv.consultar` |
| **Nombre MCP** | `sii__rcv__consultar` |
| **Conector** | `sii` |
| **Plano** | `action` |
| **Lee el alcance** | `rcv` (debe estar habilitado en la conexión) |
| **Scope (permiso)** | `sii:read` |
| **Auth** | `none` |
| **Versión** | `6` |
| **Sensible** | sí |
| **Deprecado** | no |
| **Comportamiento** | readOnly=true, destructive=false, idempotent=true, openWorld=false |
> **Requiere conexión.** Indica cuál en cada llamada: header `X-Connect-Connection` en REST, campo `connectionId` en el `execute` de MCP y en las opciones del SDK. No hay resolución implícita, ni siquiera con una sola conexión activa: la conexión es la empresa. El id sale de [`conexiones.estado.consultar`](../conexiones/estado-consultar).
## Qué hace [#qué-hace]
Lectura pura: NO dispara una sincronización nueva ni contacta al SII. Si el período nunca se sincronizó, devuelve una lista vacía y 'sincronizacion: null'. Para traer datos nuevos, use 'sii.conexion.sincronizar' primero. Pagina con 'cursor': cuando la respuesta trae 'cursor' distinto de null, hay más filas. reenvía ese valor tal cual en 'cursor' para pedir la página siguiente; nunca lo construyas a mano.
## Entrada [#entrada]
| Campo | Tipo | Requerido | Descripción |
| ------------- | ------------------------------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `periodo` | string `^\d{4}-\d{2}$` | no | Período tributario AAAA-MM. Sin él, la respuesta cruza períodos y 'sincronizacion' llega null. |
| `perspectiva` | `"compras"` · `"ventas"` | no | compras = la empresa es el receptor; ventas = la empresa es el emisor. |
| `tipoDte` | entero | no | Tipo de DTE (33 factura electrónica, 34 exenta, 46 factura de compra, 56 nota de débito, 61 nota de crédito, …). |
| `estado` | `"registro"` · `"pendiente"` · `"no_incluir"` · `"reclamado"` | no | Estado del documento en el RCV. |
| `cursor` | string | no | Paginación: el valor que devolvió la respuesta anterior, tal cual. |
| `limit` | entero 1-500 | no · default `100` | Filas por página. |
JSON Schema de entrada
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"periodo": {
"description": "Período tributario AAAA-MM. Sin él, la respuesta cruza períodos y 'sincronizacion' llega null.",
"type": "string",
"pattern": "^\\d{4}-\\d{2}$"
},
"perspectiva": {
"description": "compras = la empresa es el receptor; ventas = la empresa es el emisor.",
"type": "string",
"enum": [
"compras",
"ventas"
]
},
"tipoDte": {
"description": "Tipo de DTE (33 factura electrónica, 34 exenta, 46 factura de compra, 56 nota de débito, 61 nota de crédito, …).",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"estado": {
"description": "Estado del documento en el RCV.",
"type": "string",
"enum": [
"registro",
"pendiente",
"no_incluir",
"reclamado"
]
},
"cursor": {
"description": "Paginación: el valor que devolvió la respuesta anterior, tal cual.",
"type": "string"
},
"limit": {
"default": 100,
"description": "Filas por página.",
"type": "integer",
"minimum": 1,
"maximum": 500
}
}
}
```
## Ejemplo [#ejemplo]
```bash title="curl"
curl -X POST https://connect.emisso.ai/api/v1/tools/sii.rcv.consultar/execute \
-H "Authorization: Bearer connect_sk_…" \
-H "X-Connect-Connection: conn_9tKfR2mQx4Vb" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-07","perspectiva":"ventas"}}'
```
```ts title="SDK TypeScript"
const data = await connect.tools.sii.rcv.consultar({ periodo: "2026-07", perspectiva: "ventas" }, { connectionId: "conn_9tKfR2mQx4Vb" });
```
```json title="MCP · meta-tool execute"
{
"tool": "sii.rcv.consultar",
"params": {
"periodo": "2026-07",
"perspectiva": "ventas"
},
"connectionId": "conn_9tKfR2mQx4Vb"
}
```
**Salida esperada (200):**
```json
{
"data": {
"documentos": [
{
"tipoDte": 33,
"folio": "4712",
"rutEmisor": "77123456-9",
"rutReceptor": "76543210-3",
"razonSocial": "Constructora Los Robles Ltda",
"fechaEmision": "14/07/2026",
"montoNeto": 1250000,
"montoIva": 237500,
"montoTotal": 1487500,
"estado": "registro",
"periodo": "2026-07",
"fechaEmisionDate": "2026-07-14",
"montoExento": 0,
"fechaRecepcion": "2026-07-14",
"eventoReceptor": null,
"eventoReceptorCod": null,
"tipoDocRef": null,
"folioDocRef": null,
"fechaAcuse": null,
"fechaReclamo": null,
"tipoTransaccion": null,
"perspectiva": "ventas"
},
{
"tipoDte": 33,
"folio": "4718",
"rutEmisor": "77123456-9",
"rutReceptor": "78900400-5",
"razonSocial": "Ferretería El Volcán SpA",
"fechaEmision": "27/07/2026",
"montoNeto": 480000,
"montoIva": 91200,
"montoTotal": 571200,
"estado": "registro",
"periodo": "2026-07",
"fechaEmisionDate": "2026-07-27",
"montoExento": 0,
"fechaRecepcion": "2026-07-28",
"eventoReceptor": null,
"eventoReceptorCod": null,
"tipoDocRef": null,
"folioDocRef": null,
"fechaAcuse": null,
"fechaReclamo": null,
"tipoTransaccion": null,
"perspectiva": "ventas"
}
],
"cursor": null,
"sincronizacion": {
"sincronizadoEn": "2026-08-06T03:15:42.000Z",
"completo": true,
"incompletos": 0,
"fueraDeVentana": null,
"perspectivasFallidas": []
}
},
"meta": {
"request_id": "req_…",
"tool_id": "sii.rcv.consultar",
"plane": "action",
"latency_ms": 24,
"audit_status": "recorded"
}
}
```
> Recortado a dos documentos; una respuesta real trae hasta 'limit' filas por página. En 'ventas' el emisor es la empresa de la conexión y 'razonSocial' nombra a la contraparte (el cliente).
## Salida [#salida]
| Campo | Tipo | Requerido | Descripción | |
| --------------------------------------------------- | ---------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `documentos` | lista de objeto | sí | Los documentos del RCV que calzan con el filtro, uno por fila. Sale de la caché ya sincronizada, nunca de una consulta en vivo al SII. | |
| `documentos[].tipoDte` | entero | sí | Código del tipo de DTE: 33 factura electrónica, 34 exenta, 46 factura de compra, 56 nota de débito, 61 nota de crédito. Es un NÚMERO, a diferencia de 'folio': es un código de un vocabulario cerrado, no un identificador. | |
| `documentos[].folio` | string | sí | El folio del documento, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'tipoDte' y 'rutEmisor' lo identifica de forma única. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits. Compáralo como cadena y no lo conviertas a número para ordenar ni para volver a mandarlo. | |
| `documentos[].rutEmisor` | string | sí | Quien EMITIÓ el documento: en 'ventas' es la empresa de esta conexión, en 'compras' es la contraparte. | |
| `documentos[].rutReceptor` | string | sí | Quien RECIBIÓ el documento: en 'compras' es la empresa de esta conexión, en 'ventas' es la contraparte. | |
| `documentos[].razonSocial` | string | sí | La razón social de la CONTRAPARTE, nunca la de la empresa de esta conexión, tal como la informó el SII. | |
| `documentos[].fechaEmision` | string | sí | La fecha de emisión tal cual la manda el SII, en formato DD/MM/AAAA. Para ordenar o comparar usa 'fechaEmisionDate'. | |
| `documentos[].montoNeto` | entero | sí | Monto neto en pesos chilenos, entero. Un 0 no distingue 'el documento no tiene neto' (uno sólo exento) de 'el SII no informó el campo': las dos formas llegan igual. | |
| `documentos[].montoIva` | entero | sí | IVA en pesos chilenos, entero. Un 0 es ambiguo por el mismo motivo que en 'montoNeto'. | |
| `documentos[].montoTotal` | entero | sí | Monto total del documento en pesos chilenos, entero. | |
| `documentos[].estado` | string | sí | La casilla del Registro de Compras donde el SII tiene el documento: 'registro', 'pendiente', 'no\_incluir' o 'reclamado'. Las ventas son siempre 'registro'. | |
| `documentos[].periodo` | string | null | sí | El período tributario con que se sincronizó el documento, en formato AAAA-MM. 'null' en filas viejas que no lo guardaron. |
| `documentos[].fechaEmisionDate` | string | null | sí | La misma fecha de emisión en formato AAAA-MM-DD, o 'null' si no se pudo parsear. Es la que conviene usar para ordenar. |
| `documentos[].montoExento` | entero | sí | Monto exento de IVA en pesos chilenos, entero. | |
| `documentos[].fechaRecepcion` | string | null | sí | Cuándo el SII recibió el documento, en formato AAAA-MM-DD. 'null' si no vino. |
| `documentos[].eventoReceptor` | string | null | sí | La leyenda del evento que registró el receptor (un acuse, un reclamo). 'null' cuando no hubo evento. |
| `documentos[].eventoReceptorCod` | string | null | sí | El código de ese mismo evento. Decide por el código, nunca por la leyenda. 'null' cuando no hubo evento. |
| `documentos[].tipoDocRef` | entero | null | sí | Tipo del documento que este corrige o referencia (una nota de crédito sobre una factura 33). 'null' cuando no referencia a ninguno. Es un NÚMERO, a diferencia de 'folioDocRef': código de vocabulario cerrado contra identificador. |
| `documentos[].folioDocRef` | string | null | sí | Folio del documento referenciado, en TEXTO decimal canónico igual que 'folio', o 'null' cuando no hay referencia. Es el campo más expuesto del conector porque sale de lo que tipeó el emisor en el DTE, así que trátalo como cadena y no lo conviertas a número. |
| `documentos[].fechaAcuse` | string | null | sí | Fecha del acuse de recibo, en formato AAAA-MM-DD. 'null' si no se acusó. |
| `documentos[].fechaReclamo` | string | null | sí | Fecha del reclamo, en formato AAAA-MM-DD. 'null' si no se reclamó. |
| `documentos[].tipoTransaccion` | string | null | sí | Con qué tipo de transacción quedó clasificado el documento en el RCV, tal cual lo manda el SII. 'null' si no vino. |
| `documentos[].perspectiva` | `"compras"` · `"ventas"` | sí | compras = tú eres el receptor; ventas = tú eres el emisor | |
| `cursor` | string | null | sí | El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano. |
| `sincronizacion` | objeto | null | sí | Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a). |
| `sincronizacion.sincronizadoEn` | string | sí | Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo. | |
| `sincronizacion.completo` | booleano | null | sí | 'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento. |
| `sincronizacion.incompletos` | entero | null | sí | Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber. |
| `sincronizacion.fueraDeVentana` | entero | null | sí | Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce. |
| `sincronizacion.perspectivasFallidas` | lista de objeto | sí | Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía. | |
| `sincronizacion.perspectivasFallidas[].perspectiva` | `"emitidas"` · `"recibidas"` | sí | Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron. | |
| `sincronizacion.perspectivasFallidas[].code` | string | sí | El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto. | |
JSON Schema de salida
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"documentos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tipoDte": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Código del tipo de DTE: 33 factura electrónica, 34 exenta, 46 factura de compra, 56 nota de débito, 61 nota de crédito. Es un NÚMERO, a diferencia de 'folio': es un código de un vocabulario cerrado, no un identificador."
},
"folio": {
"type": "string",
"description": "El folio del documento, en TEXTO decimal canónico (sin ceros a la izquierda). Junto con 'tipoDte' y 'rutEmisor' lo identifica de forma única. Es texto y no un número a propósito: un folio es un identificador con el que no se hace aritmética, y hay folios reales que no caben en un entero de 32 bits. Compáralo como cadena y no lo conviertas a número para ordenar ni para volver a mandarlo."
},
"rutEmisor": {
"type": "string",
"description": "Quien EMITIÓ el documento: en 'ventas' es la empresa de esta conexión, en 'compras' es la contraparte."
},
"rutReceptor": {
"type": "string",
"description": "Quien RECIBIÓ el documento: en 'compras' es la empresa de esta conexión, en 'ventas' es la contraparte."
},
"razonSocial": {
"type": "string",
"description": "La razón social de la CONTRAPARTE, nunca la de la empresa de esta conexión, tal como la informó el SII."
},
"fechaEmision": {
"type": "string",
"description": "La fecha de emisión tal cual la manda el SII, en formato DD/MM/AAAA. Para ordenar o comparar usa 'fechaEmisionDate'."
},
"montoNeto": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto neto en pesos chilenos, entero. Un 0 no distingue 'el documento no tiene neto' (uno sólo exento) de 'el SII no informó el campo': las dos formas llegan igual."
},
"montoIva": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "IVA en pesos chilenos, entero. Un 0 es ambiguo por el mismo motivo que en 'montoNeto'."
},
"montoTotal": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto total del documento en pesos chilenos, entero."
},
"estado": {
"type": "string",
"description": "La casilla del Registro de Compras donde el SII tiene el documento: 'registro', 'pendiente', 'no_incluir' o 'reclamado'. Las ventas son siempre 'registro'."
},
"periodo": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El período tributario con que se sincronizó el documento, en formato AAAA-MM. 'null' en filas viejas que no lo guardaron."
},
"fechaEmisionDate": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La misma fecha de emisión en formato AAAA-MM-DD, o 'null' si no se pudo parsear. Es la que conviene usar para ordenar."
},
"montoExento": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "Monto exento de IVA en pesos chilenos, entero."
},
"fechaRecepcion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Cuándo el SII recibió el documento, en formato AAAA-MM-DD. 'null' si no vino."
},
"eventoReceptor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "La leyenda del evento que registró el receptor (un acuse, un reclamo). 'null' cuando no hubo evento."
},
"eventoReceptorCod": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El código de ese mismo evento. Decide por el código, nunca por la leyenda. 'null' cuando no hubo evento."
},
"tipoDocRef": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Tipo del documento que este corrige o referencia (una nota de crédito sobre una factura 33). 'null' cuando no referencia a ninguno. Es un NÚMERO, a diferencia de 'folioDocRef': código de vocabulario cerrado contra identificador."
},
"folioDocRef": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Folio del documento referenciado, en TEXTO decimal canónico igual que 'folio', o 'null' cuando no hay referencia. Es el campo más expuesto del conector porque sale de lo que tipeó el emisor en el DTE, así que trátalo como cadena y no lo conviertas a número."
},
"fechaAcuse": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Fecha del acuse de recibo, en formato AAAA-MM-DD. 'null' si no se acusó."
},
"fechaReclamo": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Fecha del reclamo, en formato AAAA-MM-DD. 'null' si no se reclamó."
},
"tipoTransaccion": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Con qué tipo de transacción quedó clasificado el documento en el RCV, tal cual lo manda el SII. 'null' si no vino."
},
"perspectiva": {
"type": "string",
"enum": [
"compras",
"ventas"
],
"description": "compras = tú eres el receptor; ventas = tú eres el emisor"
}
},
"required": [
"tipoDte",
"folio",
"rutEmisor",
"rutReceptor",
"razonSocial",
"fechaEmision",
"montoNeto",
"montoIva",
"montoTotal",
"estado",
"periodo",
"fechaEmisionDate",
"montoExento",
"fechaRecepcion",
"eventoReceptor",
"eventoReceptorCod",
"tipoDocRef",
"folioDocRef",
"fechaAcuse",
"fechaReclamo",
"tipoTransaccion",
"perspectiva"
],
"additionalProperties": false
},
"description": "Los documentos del RCV que calzan con el filtro, uno por fila. Sale de la caché ya sincronizada, nunca de una consulta en vivo al SII."
},
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "El cursor de la página siguiente, opaco. 'null' significa que no hay más filas; cualquier otro valor se reenvía tal cual en 'cursor' de la próxima llamada y nunca se construye a mano."
},
"sincronizacion": {
"anyOf": [
{
"type": "object",
"properties": {
"sincronizadoEn": {
"type": "string",
"description": "Cuándo terminó la última sincronización de este período, en ISO 8601 UTC. Es la frescura del dato que estás leyendo."
},
"completo": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "'true' = el período se sincronizó entero. 'false' = quedaron casillas sin traer, así que puede faltar información. 'null' = no se puede saber, porque no hay registro de ese intento."
},
"incompletos": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Cuántas casillas quedaron sin traer en esa sincronización. 'null' cuando no se puede saber."
},
"fueraDeVentana": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Sólo aplica a guías: cuántas direcciones cayeron fuera de la ventana de 6 meses que el SII conserva. Un 0 dice que se verificó y no aplicó; 'null', que no aplica o no se conoce."
},
"perspectivasFallidas": {
"type": "array",
"items": {
"type": "object",
"properties": {
"perspectiva": {
"type": "string",
"enum": [
"emitidas",
"recibidas"
],
"description": "Qué lado falló: 'emitidas' son las que emitió esta empresa y 'recibidas' las que le emitieron."
},
"code": {
"type": "string",
"description": "El código del catálogo de errores que explica por qué falló ese lado. Decide por el código, nunca por el texto."
}
},
"required": [
"perspectiva",
"code"
],
"additionalProperties": false
},
"description": "Qué direcciones fallaron enteras en esa sincronización, con su código de error. Hoy sólo la puebla el alcance de boletas de honorarios; para los demás llega vacía."
}
},
"required": [
"sincronizadoEn",
"completo",
"incompletos",
"fueraDeVentana",
"perspectivasFallidas"
],
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Completitud del último sync del período consultado. Es 'null' por DOS motivos distintos, y ninguno significa que las filas devueltas sean inválidas: (a) la consulta no filtró por 'periodo', así que no hay un sync único al que mirar (pide un 'periodo' concreto para obtener el bloque); o (b) ese período nunca se sincronizó. Un 'null' junto a una lista CON documentos es siempre el caso (a)."
}
},
"required": [
"documentos",
"cursor",
"sincronizacion"
],
"additionalProperties": false
}
```
## Errores de esta tool [#errores-de-esta-tool]
| Código | HTTP | Reintentable | Qué hacer |
| --------------------- | ---- | ------------ | ----------------------------------------------------------------------------- |
| `connection_disabled` | 403 | no | Reactívala en /connections o usa otra conexión del mismo sistema. |
| `alcance_not_enabled` | 403 | no | Habilita el alcance en /connections o quítalo del input de la sincronización. |
Toda llamada puede devolver además los códigos transversales (`validation_error`, `unauthorized`, `scope_not_granted`, `rate_limited`, entre otros): el detalle vive en el [catálogo de errores](../errores).
## Próximos pasos [#próximos-pasos]
* [`sii.conexion.sincronizar`](./conexion-sincronizar): la tool que escribe los datos que esta lectura devuelve.
* [Sincronizar y consultar](/docs/conceptos/sincronizar-consultar): por qué leer datos reales son dos pasos.