---
title: "API: Folios (CAF)"
description: Carga, inventario y eliminación de los archivos CAF que autorizan tus rangos de folios por tipo de DTE, con alerta de folios bajos
url: https://notta.cl/docs/api/caf
related:
  - https://notta.cl/docs/api/dtes
  - https://notta.cl/docs/onboarding-cert
  - https://notta.cl/docs/concepts
---

> Documentación de Notta en markdown. Índice completo: https://notta.cl/llms.txt — corpus entero: https://notta.cl/llms-full.txt

# "API: Folios (CAF)"

El recurso `/caf` administra los CAF (Código de Autorización de Folios) de tu organización: el XML que el SII te entrega con un rango de folios autorizado por tipo de DTE. Notta consume folios secuencialmente de cada CAF al emitir y avisa cuando quedan pocos (`low_folios`).

Base URL: `https://app.notta.cl/api/v1`. El CAF se guarda cifrado con la misma envelope encryption de los certificados.

> **Estado actual: autenticación por sesión:**
Igual que `/certificates`, estos endpoints se autentican con la sesión del dashboard (cookie de Supabase Auth), no con API key Bearer. Sin sesión u organización responden `412 org.required`. El camino práctico hoy es el dashboard (`/app/caf`); los curl muestran el contrato exacto.

## Endpoints

### `POST /caf`

Sube un archivo CAF (multipart/form-data): parsea el XML (`RE`, `TD`, `D`, `H`), valida el tipo esperado y registra el rango de folios.

| Campo (form) | Tipo | Requerido | Descripción |
|---|---|---|---|
| `file` | archivo | sí | El XML CAF descargado del SII. |
| `tipo_esperado` | int | no | Tipo de DTE que esperas (33, 34, 39, 41, 56, 61); si el XML trae otro, responde `422 caf.wrong_tipo` antes de insertar. |
| `certificate_id` | uuid | no | Certificado asociado; default: el primer certificado vigente de la org (sin ninguno → `422 caf.no_certificate`). |

```bash
curl -X POST https://app.notta.cl/api/v1/caf \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@CAF-33-1000-1500.xml" \
  -F "tipo_esperado=33"
```

Respuesta `201 Created`:

```json
{
  "data": {
    "id": "01977f6e-5f90-7000-8000-000000000005",
    "tipo_dte": 33,
    "folio_desde": 1000,
    "folio_hasta": 1500,
    "remaining": 501,
    "low_folios": false
  }
}
```

La deduplicación es por rango: re-subir el mismo `(rut, tipo, folio_desde)` responde `409 caf.duplicate`.

Errores posibles:

| Código | HTTP | `next_action` |
|---|---|---|
| `caf.invalid_request` (falta `file`) | 400 | — |
| `caf.duplicate` | 409 | — |
| `org.required` | 412 | — |
| `caf.parse_error` (XML sin `RE`/`TD`/`D`/`H`) | 422 | — |
| `caf.wrong_tipo` | 422 | — |
| `caf.no_certificate` | 422 | — |
| `caf.insert_failed` | 500 | — |
| `db.unavailable` · `caf.kms_unavailable` | 503 | — |

### `GET /caf`

Lista los CAF de tu organización con el consumo de folios de cada rango.

```bash
curl https://app.notta.cl/api/v1/caf
```

Respuesta `200 OK`:

```json
{
  "data": [
    {
      "id": "01977f6e-5f90-7000-8000-000000000005",
      "rut_emisor": "76123456-0",
      "tipo_dte": 33,
      "folio_desde": 1000,
      "folio_hasta": 1500,
      "next_folio": 1235,
      "remaining": 266,
      "low_folios": false,
      "not_after": "2026-12-09T00:00:00.000Z",
      "exhausted": false,
      "created_at": "2026-06-09T18:20:00.000Z"
    }
  ]
}
```

`remaining = folio_hasta - next_folio + 1`; `low_folios` se prende con 10 o menos. Errores posibles: `412 org.required`, `503 db.unavailable`.

### `DELETE /caf/:id`

Elimina un CAF (hard delete, permite re-subir el mismo rango) junto con los DTE del rango que el SII **no** aceptó; está bloqueado si algún DTE del CAF fue aceptado.

```bash
curl -X DELETE https://app.notta.cl/api/v1/caf/01977f6e-5f90-7000-8000-000000000005
```

Respuesta `200 OK`:

```json
{ "data": { "id": "01977f6e-5f90-7000-8000-000000000005", "deleted": true, "dtes_deleted": 2 } }
```

> **Retención legal de 6 años:**
Si algún DTE del CAF fue aceptado por el SII (`EPR`, `RPR`, `DOK`, aceptado con reparos), el borrado responde `409 caf.has_accepted_dtes`: esos folios quedaron registrados en el SII y los documentos se conservan por obligación legal de 6 años. Lo mismo si un DTE del rango fue cedido vía RPETC (`409 caf.has_ceded_dtes`).

Errores posibles:

| Código | HTTP | `next_action` |
|---|---|---|
| `caf.not_found` | 404 | — |
| `caf.has_accepted_dtes` | 409 | — |
| `caf.has_ceded_dtes` | 409 | — |
| `org.required` | 412 | — |
| `db.unavailable` | 503 | — |

## Próximos pasos

- **[API: DTEs](/docs/api/dtes)** — con folios cargados, emite tu primera factura por API.
- [Onboarding y certificación SII](/docs/onboarding-cert) — cómo obtener CAFs en maullin (cert) y palena (prod).
- [Conceptos](/docs/concepts) — qué es un CAF y cómo se relaciona con folio, timbre y certificado.
