> ## 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.

# Autenticación

> API Key en el header X-API-Key. Tipos de key, errores de autenticación, freno por IP y rotación.

Cada request lleva el header `X-API-Key`. La key identifica el **tenant**, el **ambiente** y los permisos. No se acepta por query string, body ni Basic Auth.

| Prefijo | Ambiente | URL base | Validez fiscal |
| - | - | - | - |
| `sk_test_` | Sandbox (SIAT Piloto, ambiente 2) | `https://sandbox.cucu.bo` | No |
| `sk_live_` | Producción (SIAT ambiente 1) | `https://api.cucu.bo` | Sí |

Una key solo funciona en su ambiente: `sk_live_` en sandbox (o al revés) responde `401`. La key llega al contratar el plan ([agente de CUCU](https://wa.me/59160522508) o [app.cucu.bo](https://app.cucu.bo/signup)) y es la misma para REST y [MCP](https://docs.cucu.bo/mcp/para-agentes.md).

```bash cURL theme={"system"}
curl https://sandbox.cucu.bo/api/v1/invoices \
  -H "X-API-Key: YOUR_API_KEY"
```

```javascript JavaScript theme={"system"}
const response = await fetch('https://sandbox.cucu.bo/api/v1/invoices', {
  headers: { 'X-API-Key': process.env.CUCU_API_KEY }
});
```

```python Python theme={"system"}
import os, requests
response = requests.get(
    'https://sandbox.cucu.bo/api/v1/invoices',
    headers={'X-API-Key': os.environ['CUCU_API_KEY']}
)
```

```go Go theme={"system"}
req, _ := http.NewRequest("GET", "https://sandbox.cucu.bo/api/v1/invoices", nil)
req.Header.Set("X-API-Key", os.Getenv("CUCU_API_KEY"))
resp, err := http.DefaultClient.Do(req)
```

```php PHP theme={"system"}
$ch = curl_init('https://sandbox.cucu.bo/api/v1/invoices');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: " . getenv('CUCU_API_KEY')]);
$data = json_decode(curl_exec($ch), true);
```

> [!WARNING]
>
> `sk_live_` vive solo en tu backend (variable de entorno o secret manager). Nunca en frontend, apps móviles ni repositorios.

## Errores de autenticación

Los filtros responden antes de llegar al endpoint, con un JSON mínimo (`error: true` + `message`, sin `error.code`):

| HTTP | Causa | Qué hacer |
| - | - | - |
| `401` | Header `X-API-Key` ausente en una ruta protegida | Enviar el header (no `Authorization`) |
| `401` | Key inválida, revocada, con espacios o del otro ambiente | Verificar la key y el ambiente |
| `401` | Tenant inactivo | Contactar soporte |
| `403` | Credencial de servicio sin alcance para la operación | La credencial no tiene acceso a ese recurso |
| `429` | Más de 20 credenciales inválidas por minuto desde tu IP | Esperar `Retry-After` segundos |

```json theme={"system"}
{ "error": true, "message": "API Key inválida o expirada" }
```

### Freno a credenciales inválidas (429)

Si desde una misma IP se presentan **más de 20 credenciales inválidas en un minuto** (keys de tenant o de servicio en rutas protegidas, credenciales de administración en `/api/v1/admin`, o keys en `/mcp`), esa IP recibe `429` con `Retry-After` **presente la llave que presente**, incluida una válida, hasta que se cierre la ventana.

```http theme={"system"}
HTTP/1.1 429 Too Many Requests
Retry-After: 37
Content-Type: application/json

{
  "error": true,
  "message": "Demasiadas credenciales inválidas desde esta dirección. Intente nuevamente en 37 segundos."
}
```

* No cuentan: un request **sin** credencial (sigue siendo `401`) ni una credencial válida sin permiso (`403`).
* Es independiente del límite de requests por API Key: el tráfico con una llave válida no lo activa.
* En MCP el `429` no es una respuesta HTTP: el texto llega como resultado de la tool ([detalle](https://docs.cucu.bo/mcp/para-agentes.md#errores)).
* Si tu integración rota llaves, asegurate de que la vieja no quede en un reintento en bucle.

### Cabeceras de seguridad

Todas las respuestas traen `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer` y `Strict-Transport-Security: max-age=31536000; includeSubDomains`. Las respuestas JSON (incluido `/mcp`) traen además `X-Frame-Options: DENY` y `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'`. Los documentos públicos (`/api/v1/public/...`) y la página `/f/{cuf}` quedan fuera de esas dos últimas para poder embeberse; la página pública lleva su propia CSP.

## Endpoints públicos (sin API Key)

| Endpoint | Método | Descripción |
| - | - | - |
| `/api/v1/ping` | `GET` | Health check |
| `/api/v1/info` | `GET` | Versión y uptime del servidor |
| `/api/v1/public/invoice/{cuf}/pdf` | `GET` | PDF de la factura |
| `/api/v1/public/invoice/{cuf}/xml` | `GET` | XML firmado |
| `/api/v1/public/invoice/{cuf}/status` | `GET` | Estado público de la factura |
| `/f/{cuf}` | `GET` | Página pública de verificación |
| `/api/v1/ai/status` | `GET` | Estado de los servicios de IA (Qdrant, embeddings) |

El CUF actúa como token de acceso: las URLs se pueden compartir con el cliente final. Todo lo demás (`/api/v1/invoices`, `/api/v1/siat/*`, `/api/v1/ai/chat`, etc.) exige `X-API-Key`, y cada endpoint verifica que el recurso pertenezca al tenant autenticado.

## Rotación

1. En [app.cucu.bo](https://app.cucu.bo) → **Configuración > API Keys** → **Regenerar key** del ambiente que corresponda. La key anterior **se revoca en el acto**.
2. Actualizá la variable de entorno o secret manager y desplegá.
3. Verificá con un request a `/api/v1/invoices`.

Rotación con período de gracia (la anterior sigue valiendo hasta 30 días): la hace CUCU con [`generarApiKey`](https://docs.cucu.bo/mcp/herramientas-admin#generarapikey).

No hay SDK oficial: la API es REST plano y cualquier cliente HTTP alcanza.


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