> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cucu.bo/llms.txt
> Use this file to discover all available pages before exploring further.

# Guía para agentes

> Contrato del MCP Server para un LLM: transporte, autenticación, límites, formato de salida, textos de error y flujos recomendados.

Esta página es el contrato operativo del MCP Server, pensado para que un agente lo lea y opere sin leer el resto del sitio.

## Conexión

| | Valor |
| - | - |
| Endpoint | `POST https://sandbox.cucu.bo/mcp` (sandbox) · `POST https://api.cucu.bo/mcp` (producción) |
| Transporte | Streamable HTTP (JSON-RPC 2.0). Legacy SSE en `GET /mcp/sse` |
| Headers | `X-API-Key` (`sk_test_...` o `sk_live_...`) · `Content-Type: application/json` · `Accept: application/json, text/event-stream` |
| `initialize` | Opcional: el server acepta `tools/list` y `tools/call` directos (útil para `curl` y clientes sin estado) |
| Tenant | Lo fija la API Key. Cada tool ve solo los datos de ese tenant (RLS); un id de otro tenant se comporta como inexistente |
| Auditoría | Toda llamada queda en el log de actividad del tenant |

```bash theme={"system"}
# Listar herramientas
curl -X POST https://sandbox.cucu.bo/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "X-API-Key: sk_test_..." \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Invocar una herramienta
curl -X POST https://sandbox.cucu.bo/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "X-API-Key: sk_test_..." \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"verificarFormatoNit","arguments":{"nit":"1023456789"}}}'
```

```json theme={"system"}
{ "jsonrpc": "2.0", "id": 2, "result": { "content": [ { "type": "text", "text": "NIT con formato valido: 1023456789" } ] } }
```

## Autenticación y límites

* **Ninguna herramienta es pública.** Las 51 (incluidas `verificarFormatoNit`, `calcularMontoLiteral`, `buscarNormativa`, `erroresComunesSiat`, `consultarDatosNit`) se autentican antes de ejecutarse.
* Las herramientas de [Administración](https://docs.cucu.bo/mcp/herramientas-admin.md) exigen la **credencial de administración de CUCU** (prefijo `ck_admin_`), no una key de tenant.
* **Rate limit por API Key**: cada `tools/call` cuenta para el mismo límite de requests por minuto que REST. Superado, la tool devuelve el texto de rate limit (abajo) y hay que esperar 60 segundos.
* **Freno por IP**: más de 20 credenciales inválidas en un minuto desde una IP bloquean esa IP por el tiempo que diga el mensaje, presente la llave que presente. Una llave de tenant válida sin permiso admin no cuenta.
* Los errores de autenticación y de límite **no son respuestas HTTP 401/429**: llegan como texto en `result.content[0].text`.

## Errores

Todos los errores llegan como **texto** en el resultado de la tool (no como error JSON-RPC). El agente debe inspeccionar el texto. Formas exactas:

| Situación | Texto |
| - | - |
| Sin header | `Error: API Key requerido. Envie el header X-API-Key con su clave (sk_live_xxx o sk_test_xxx)` |
| Key inválida o tenant inactivo | `Error: API Key invalido o tenant inactivo` |
| IP bloqueada por credenciales inválidas | `Error: demasiadas credenciales invalidas desde esta direccion. Intente nuevamente en <N> segundos.` |
| Rate limit por key | `Error: rate limit excedido. Maximo <N> requests/minuto. Intente nuevamente en 60 segundos.` |
| Tool admin sin credencial admin | `Acceso denegado. Las herramientas de administracion requieren la credencial de administracion de CUCU.` |
| UUID mal formado | `Error: UUID invalido: <valor>` |
| JSON de entrada inválido (`crearFactura`, `crearNotaCreditoDebito`, `crearTenantCompleto`) | `Error parseando JSON: <detalle>` |
| Error de dominio (regla de negocio, SIAT) | `[<CODIGO>] <mensaje>`, p. ej. `[SIAT_ERROR] CUFD NO VIGENTE O NO EXISTE`. Códigos en [Errores](https://docs.cucu.bo/errors#codigos-de-error-internos) |
| Argumento o estado inválido | el mensaje crudo, p. ej. `Error: repeticiones debe estar entre 1 y 10 (recibido: 12)` |
| Fallo no manejado | `Error interno: <mensaje>` |

Las tools de eventos, masiva y webhooks anteponen el contexto: `Error cerrando evento: [CODIGO] mensaje`, `Error registrando webhook: ...`. `crearFactura` y `descargarDocumento` devuelven JSON: `{"success": false, "error": "<texto con las mismas formas>"}`.

## Formato de salida

| Tools | Salida |
| - | - |
| `crearFactura`, `descargarDocumento`, `consultarPadron` | **JSON** serializado en el texto (`success` + campos; ver cada tool) |
| El resto | **Texto plano** con formato fijo de líneas `- Campo: valor` (p. ej. `Factura #42` + `- ID`, `- CUF`, `- Estado SIAT`, `- Total`…). Listados: una línea por elemento |

Para extraer datos de forma confiable, preferí las tools con salida JSON o leé las líneas `- Campo: valor` por nombre. Los identificadores que se reutilizan son `ID` (UUID interno) y `CUF`.

## Flujos recomendados

**Emitir y entregar**

1. `consultarPadron(tipo=5, numero=<NIT>)` para obtener la razón social oficial; `verificarFormatoNit` si el NIT lo escribió un humano.
2. `crearFactura(requestJson)` con `pointOfSaleId`, cliente, `paymentMethodCode` y `details`.
3. Leer `siatState` de la respuesta: `VALIDATED` → listo; `REJECTED` → corregir con `siatDescription` y `erroresComunesSiat`; otro estado → `verificarEstadoFactura(invoiceId)`.
4. Entregar: `documents` de la respuesta (requieren API Key) o `descargarDocumento(cuf, formato)`, que devuelve un `download_url` temporal para bajar y adjuntar el archivo.
5. No reintentar `crearFactura` a ciegas tras un timeout: primero `listarFacturas(0, 5)` u `obtenerFacturaPorCuf` para ver si ya se emitió.

**Anular y revertir**

1. `anularFactura(id, codigoMotivo)` con motivo `1` error de datos, `2` devolución, `3` período posterior, `4` otros. Solo sobre `VALIDATED`.
2. Si fue un error, `revertirAnulacion(id)`: vuelve a `VALIDATED` (también para emitidas offline).
3. Confirmar con `verificarEstadoFactura(id)`.

**Devolución o ajuste**

1. La factura original debe estar `VALIDATED`.
2. `crearNotaCreditoDebito(requestJson)` con `originalInvoiceId`, `notaType` (`CREDITO`/`DEBITO`) y solo `returnedDetails`; las líneas originales se clonan de la base.

**Contingencia (SIAT caído)**

1. `verificarConexionSiat()` para confirmar la caída.
2. `abrirEvento(posId, codigoMotivo, descripcion)`; desde ahí el POS emite offline (`crearFactura` sigue igual).
3. Al volver el SIAT: `cerrarEvento(posId, eventoId)`. Leer el detalle de paquetes de la respuesta: si alguno no entró, `reenviarPaqueteEvento(posId, eventoId)`.
4. Facturas emitidas después del fin del evento (`verificarEstadoFactura` responde `[924]`): `regularizarFacturasFueraDeEvento(posId, eventoId)`. Facturas con CUFD distinto al del evento (quedan en `901`): `resellarFacturasEvento`.
5. `obtenerEventoActivo(posId)` antes de abrir otro: un POS tiene a lo sumo un evento activo.

**Emisión masiva**

1. `abrirLoteMasivo(posId)` (idempotente). 2. `crearFactura` las veces que haga falta: quedan en `MASSIVE_PENDING`. 3. `cerrarLoteMasivo(posId)`; puede tardar minutos y es reentrante. 4. `verLoteMasivo(posId)`; si hay rechazadas, `rectificarLoteMasivo(posId)` y volver a `cerrarLoteMasivo`.

**Webhooks**

1. `registrarWebhook(url, descripcion, tiposEvento)`: el secreto de firma se muestra **una sola vez**; guardarlo.
2. Diagnóstico: `listarEntregasWebhook(endpointId)` y `reenviarEntregaWebhook(deliveryId)` para una entrega `DEAD`.

## Efectos y seguridad de reintento

| Tool | Efecto | Reintentable |
| - | - | - |
| `crearFactura`, `crearNotaCreditoDebito` | Emite un documento fiscal ante el SIAT | **No** a ciegas: verificar antes si ya se emitió |
| `anularFactura` | Anula ante el SIAT | Sí, pero el estado ya es `CANCELLED`; se revierte con `revertirAnulacion` |
| `cerrarEvento`, `cerrarLoteMasivo`, `abrirLoteMasivo`, `indexarNormativa`, `reindexarContingencias` | Idempotentes / reentrantes | Sí |
| `rotarSecretoWebhook`, `generarApiKey` (sin `diasGracia`), `revocarApiKey` | Invalidan la credencial anterior en el acto | Cada llamada genera una nueva |
| `eliminarWebhook`, `cambiarEstadoTenant(activo=false)` | Deshabilitan | Sí |
| Lecturas (`listar*`, `obtener*`, `ver*`, `verificar*`, `consultar*`, `buscar*`, `calcularMontoLiteral`) | Sin efectos | Sí |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.