# Folios y CAF (/docs/folios-y-caf)



Esta guía cubre tres operaciones de CAF que se usan juntas, en este orden:

1. [`caf_v1_listar`](/docs/referencia/caf/listar/): qué CAF autorizó el SII para la empresa y el tipo de documento.
2. [`caf_v1_estado_folios`](/docs/referencia/caf/estado-folios/), opcional: cuántos folios de uno de esos CAF el SII todavía no recibió, cuántos ya no permite anular y cuántos se anularon.
3. [`caf_v1_reobtener`](/docs/referencia/caf/reobtener/) con `folioInicial`: volver a bajar uno de esos CAF, ya timbrado.

Ninguna de las tres pide folios nuevos: para eso está [`caf_v1_descargar`](/docs/referencia/caf/descargar/). Las tres están vigentes desde el despliegue de la API del 2026-10-17 (versión 1.5.0 del contrato).

## Antes de empezar [#antes-de-empezar]

* **El certificado va en el cuerpo del POST**, con `Data` y `Contrasenna`, como en las demás operaciones de CAF. Mande siempre el cuerpo: sin él, `caf_v1_listar` y `caf_v1_estado_folios` responden `CERT_REQUERIDO`. `caf_v1_reobtener` también, desde el despliegue de la versión 1.5.3; hasta entonces responde un error del framework en JSON.
* **Pase `ambiente` explícito, 0 o 1, y el mismo en las tres.** Si lo omite, las tres corren en producción. Con un número distinto de 0 y 1, `caf_v1_listar` y `caf_v1_estado_folios` corren en homologación y `caf_v1_reobtener` en producción: no corren en el mismo ambiente. Más en [Ambientes](/docs/ambientes).
* **El certificado no se valida en el mismo ambiente en las tres.** `caf_v1_listar` y `caf_v1_estado_folios` lo validan en el ambiente pedido. `caf_v1_reobtener`, también con `folioInicial`, lo valida siempre en **producción**, aunque pida `ambiente=0`. Un certificado habilitado solo en el ambiente de certificación del SII sirve para listar con `ambiente=0`, pero no para reobtener: el error habla del certificado, no del ambiente.
* **El titular del certificado tiene que estar autorizado por la empresa para operar con folios (CAF)** en el SII. No hace falta que administre usuarios. Además, `caf_v1_listar` y `caf_v1_estado_folios` consultan la opción de anulación masiva del SII: una empresa que no está habilitada ahí recibe `CONTRIBUYENTE_SIN_PERMISO`, y eso lo tiene que resolver la empresa con el SII.
* **Las tres hablan con el SII en serie y pueden tardar decenas de segundos.** Configure un timeout de cliente de al menos 70 segundos. Pasados 60 segundos, la capa de entrada de la API responde 504 con una página HTML, sin código: está en [Tiempos de respuesta y 504](/docs/errores-y-reintentos#tiempos-de-respuesta-y-504).

## Paso 1: listar los CAF [#paso-1-listar-los-caf]

`caf_v1_listar` es `POST /v1/caf/{rut}/listar/{tipoDocumento}`:

```bash
curl -u '<usuario>:<apikey>' -X POST \
  -H 'Accept: application/json' -H 'Content-Type: application/json' \
  -d '{"Data": "<pfx en base64>", "Contrasenna": "********"}' \
  'https://api.enrolamiento.cl/v1/caf/11111111-1/listar/33?ambiente=0'
```

La respuesta trae `Cafs`, un elemento por CAF, ordenados por `FolioInicial` de mayor a menor:

```json
{
  "Cafs": [
    { "FolioInicial": 1101, "FolioFinal": 1200, "Cantidad": 100, "Fecha": "12-03-2026", "Dueno": "Usuario Ejemplo", "RUTDueno": "12345678-5" },
    { "FolioInicial": 1001, "FolioFinal": 1100, "Cantidad": 100, "Fecha": "05-01-2026", "Dueno": "Usuario Ejemplo", "RUTDueno": "12345678-5" }
  ]
}
```

* Vienen **todos** los CAF que el SII lista, también los ya usados o anulados enteros. Que un CAF esté en la lista no dice que le queden folios: para eso está el paso 2.
* `Cafs` vacía quiere decir que el SII no lista CAF de ese tipo para la empresa. Nunca es nula.
* Es una lectura: no timbra ni anula nada, y repetirla no cambia nada en el SII.
* Reemplaza a [`caf_v1_anulacion_masiva_listar`](/docs/referencia/caf/anulacion-masiva-listar/) para listar CAF: el mismo modelo de respuesta, pero en el ambiente pedido y verificando el permiso del certificado.

**Errores de este paso.** Los del certificado (`CERT_REQUERIDO`, `CERT_EXPIRADO`, `CERT_NO_VALIDO` y los demás de la [tabla de la operación](/docs/referencia/caf/listar/)), `CERT_SIN_PERMISO` si el titular no está autorizado por la empresa, `SEC_EMP_NOT_AUTHORIZED` si la empresa opera en la modalidad MiPyme, `CONTRIBUYENTE_SIN_PERMISO`, `SII_ERROR_CXN` (reintentable; desde el despliegue de la versión 1.5.3, también cuando el SII pide volver a iniciar sesión) y `SII_RESPUESTA_INESPERADA` (no reintentable). Esta operación nunca responde 429.

## Paso 2: contar los folios de un CAF [#paso-2-contar-los-folios-de-un-caf]

`caf_v1_estado_folios` es `POST /v1/caf/{rut}/estado-folios/{tipoDocumento}/{folioInicial}/{folioFinal}`. Tome `FolioInicial` y `FolioFinal` de un CAF del paso 1, o un subrango: el rango tiene que caber entero en un solo CAF.

```bash
curl -u '<usuario>:<apikey>' -X POST \
  -H 'Accept: application/json' -H 'Content-Type: application/json' \
  -d '{"Data": "<pfx en base64>", "Contrasenna": "********"}' \
  'https://api.enrolamiento.cl/v1/caf/11111111-1/estado-folios/33/1001/1100?ambiente=0'
```

```json
{ "Rut": "11111111-1", "TipoDocumento": 33, "FolioInicial": 1001, "FolioFinal": 1100,
  "Cantidad": 100, "Disponibles": 60, "Utilizados": 38, "Anulados": 2 }
```

Qué dice cada número:

| Campo | Qué es |
|---|---|
| `Cantidad` | Los folios del rango: `FolioFinal` - `FolioInicial` + 1. Siempre es `Disponibles` + `Utilizados` + `Anulados`. |
| `Disponibles` | Los que el SII todavía no recibió ni anuló. Lo informa el SII. |
| `Utilizados` | Los que el SII ya no permite anular: en la práctica, los de documentos que el SII recibió. No lo informa el SII: se calcula como `Cantidad` - `Disponibles` - `Anulados`. |
| `Anulados` | Los anulados en el SII. Lo informa el SII. |

**`Disponibles` no es la cantidad de folios libres para emitir.** Puede incluir folios que usted ya usó en documentos que aún no envió al SII. Para decidir qué folio emitir, use su propio foliado. Para saber el último folio emitido, use [`caf_v1_consulta`](/docs/referencia/caf/consulta/): esta operación cuenta un rango.

**Consulte bajo demanda, un CAF a la vez.** Cada llamada es una consulta completa al SII, y no hay una consulta de varios CAF juntos. Recorrer todos los CAF del paso 1 cuesta una llamada por CAF, y cada una cuenta para el [límite de requests](/docs/rate-limits). Si necesita varios CAF, haga una llamada por CAF y en secuencia. La suma de los subrangos de un CAF no tiene por qué coincidir con una sola consulta del CAF entero si entre una y otra se emitieron o anularon folios.

**Errores de este paso.** Antes de validar el certificado o consultar al SII, también con el RUT de prueba: `PARAMETRO_FUERA_DE_RANGO` si un folio es menor que 1 o `folioInicial` es mayor que `folioFinal`, y `NO_SOPORTADO_BOLETAS` si pide boletas (39 o 41) en producción; las boletas solo se consultan con `ambiente=0`. Después, los mismos del paso 1, más `CAF_NO_ENCONTRADO` si el rango no cabe entero en un solo CAF del listado del SII, y `SII_RESPUESTA_INESPERADA` si los conteos del SII no cuadran con el rango: nunca un 200 con ceros. Esta operación nunca responde 429.

## Paso 3: volver a bajar un CAF con `folioInicial` [#paso-3-volver-a-bajar-un-caf-con-folioinicial]

`caf_v1_reobtener` es `POST /v1/caf/{rut}/reobtener/{tipoDocumento}`. Sin `folioInicial` devuelve el último CAF, como siempre. Con `folioInicial`, el `FolioInicial` de un CAF del paso 1, devuelve el CAF que empieza en ese folio:

```bash
curl -u '<usuario>:<apikey>' -X POST \
  -H 'Accept: application/json' -H 'Content-Type: application/json' \
  -d '{"Data": "<pfx en base64>", "Contrasenna": "********"}' \
  'https://api.enrolamiento.cl/v1/caf/11111111-1/reobtener/33?folioInicial=1101&ambiente=0'
```

* **No pide folios nuevos.** El CAF ya está timbrado: repetir la llamada vuelve a bajar el mismo CAF. No se aplica el filtro de 3 meses de `descargarCafAntiguos`, que con `folioInicial` no se usa.
* **El certificado se valida en producción**, aunque pida `ambiente=0`. Vea [Antes de empezar](#antes-de-empezar).
* **El CAF se busca solo en la primera página del listado de reobtención del SII**, que no es la fuente de `caf_v1_listar`. Un CAF que el paso 1 muestra, de los más antiguos, puede no estar en esa primera página: la respuesta es `CAF_NO_ENCONTRADO` aunque el CAF exista. Hoy la API no tiene cómo reobtener ese CAF, y `caf_v1_descargar` con `ultimoFolio` tampoco: mira la misma primera página y, si no encuentra el CAF, pide folios nuevos. En ese caso, contacte a soporte con el RUT, el tipo de documento y el folio inicial.
* **Pídalos en secuencia.** Si su cuenta tiene el control de solicitudes concurrentes de CAF, las solicitudes del mismo RUT, tipo de documento y ambiente se atienden de a una, entre `caf_v1_descargar`, `caf_v1_reobtener` y [`caf_v1_anular`](/docs/referencia/caf/anular/). Si va a bajar varios CAF del mismo RUT y tipo, no los pida en paralelo. El detalle está en [409 y 429](/docs/errores-y-reintentos#409-y-429-el-request-no-se-ejecutó).

### Verifique el CAF que recibe [#verifique-el-caf-que-recibe]

La respuesta es un string JSON con el XML del CAF en base64 (ver [Formatos](/docs/formatos#base64)). Antes de usarlo, verifique que el CAF empiece en el folio que pidió: el `<D>` de `<RNG>` del XML tiene que ser `folioInicial`. Una API sin la versión 1.5.0 ignora el parámetro en silencio y devuelve el último CAF con un 200. Si no empieza ahí, no use ese CAF: vuelva a pedirlo o contacte a soporte.

```mjs
// Node.js: decodifica la respuesta de caf_v1_reobtener y verifica que el CAF empiece en folioInicial.
async function cafQueEmpiezaEn(respuesta, folioInicial) {
  const base64 = await respuesta.json(); // un string JSON
  const xml = Buffer.from(base64, 'base64').toString('latin1');
  const desde = /<RNG>\s*<D>\s*(\d+)\s*<\/D>/.exec(xml)?.[1];
  if (Number(desde) !== Number(folioInicial)) {
    throw new Error(`El CAF devuelto empieza en ${desde ?? '(sin RNG)'}, no en ${folioInicial}: no lo use.`);
  }
  return xml;
}
```

**Errores de este paso.** `PARAMETRO_FUERA_DE_RANGO` antes que todo, también con el RUT de prueba, si `folioInicial` no es un entero mayor o igual a 1 o viene repetido. Después, `CERT_REQUERIDO` si falta el cuerpo o le falta `Data` o `Contrasenna` (desde el despliegue de la versión 1.5.3, antes de esperar por otra solicitud en curso), los del certificado validado en producción; `CAF_NO_ENCONTRADO` si ningún CAF de la primera página empieza en `folioInicial`; y, si el listado o la descarga trajo una página del SII, `NO_CAF`, `CERT_SIN_PERMISO`, `SEC_EMP_NOT_AUTHORIZED` o `SII_ERROR_CXN` según la página, o `UNKNOWN_ERROR_CAF` con el motivo en el mensaje. Con `folioInicial`, `UNKNOWN_ERROR_CAF` no pide folios nuevos: el CAF ya está timbrado; lea el mensaje y reintente más tarde. Con el control de solicitudes concurrentes, también el 429 de `CAF_REQUEST_IN_PROGRESS`.

## Qué hacer con cada código [#qué-hacer-con-cada-código]

| Código | Paso | Qué hacer |
|---|---|---|
| `SII_ERROR_CXN` | 1, 2 y 3 | Reintente respetando `Retry-After` (30 segundos), con espera exponencial (30, 60 y 120 segundos) y un tope de 3 intentos. En el SII de producción es frecuente e intermitente. Desde el despliegue de la versión 1.5.3, en los pasos 1 y 2 también llega cuando el SII pide volver a iniciar sesión: si persiste, espere 15 minutos y repita (ver [Ambientes](/docs/ambientes#la-sesión-del-sii-entre-ambientes-sin-verificar)). |
| 504 con una página HTML | 1, 2 y 3 | Es la capa de entrada de la API, que cortó a los 60 segundos. En los pasos 1 y 2, repita: son lecturas. En el paso 3, repita pasados 30 segundos: el pedido puede seguir en curso. |
| `CAF_REQUEST_IN_PROGRESS` | 3 | Espere los segundos de `Retry-After` y repita el mismo request. |
| `CAF_NO_ENCONTRADO` | 2 | Pida un rango que quepa entero en un CAF del paso 1. |
| `CAF_NO_ENCONTRADO` | 3 | Revise que `folioInicial` sea el `FolioInicial` de un CAF del paso 1. Si lo es, el CAF no está en la primera página: contacte a soporte. No lo reintente. |
| `UNKNOWN_ERROR_CAF` | 3 | Lea el mensaje y reintente más tarde. No pida folios nuevos por esto. |
| `NO_CAF` | 3 | El SII no registra CAF de ese tipo para la empresa: no hay nada que reobtener. |
| `PARAMETRO_FUERA_DE_RANGO` | 2 y 3 | Corrija el folio. Repetir da el mismo error. |
| `NO_SOPORTADO_BOLETAS` | 2 | Las boletas (39 y 41) solo se consultan con `ambiente=0`. |
| `CERT_SIN_PERMISO` | 1, 2 y 3 | Use el certificado de un usuario autorizado por la empresa para operar con folios. |
| `CONTRIBUYENTE_SIN_PERMISO` | 1 y 2 | La empresa tiene que habilitarse en la anulación masiva del SII. |
| `SII_RESPUESTA_INESPERADA` | 1 y 2 | No reintente; contacte a soporte con el RUT. Hasta el despliegue de la versión 1.5.3 también llega si el SII rechazó la sesión, por ejemplo al alternar de ambiente con el mismo certificado: vea [Ambientes](/docs/ambientes#la-sesión-del-sii-entre-ambientes-sin-verificar). |

Un código que no está en esta tabla se resuelve con la tabla «Reintentos e idempotencia» de la operación y, si tampoco está ahí, con la [regla de respaldo](/docs/errores-y-reintentos#la-regla-de-respaldo).

## Probar sin el SII [#probar-sin-el-sii]

Con el RUT `1-9` o un RUT de prueba de su perfil de cliente, las tres responden sin consultar al SII ni validar el certificado:

* `caf_v1_listar` devuelve siempre un único CAF de ejemplo, del folio 1 al 10.
* `caf_v1_estado_folios` responde cualquier rango válido con todos sus folios disponibles, sin contrastarlo con ese CAF de ejemplo.
* `caf_v1_reobtener` responde una cadena vacía (`""` en JSON), con o sin `folioInicial`. La verificación del `<D>` de `<RNG>` no aplica a esa respuesta.

`PARAMETRO_FUERA_DE_RANGO` y `NO_SOPORTADO_BOLETAS` sí llegan con el RUT de prueba, porque se revisan antes. Los errores del SII no se simulan: `CAF_NO_ENCONTRADO`, `SII_ERROR_CXN` y los rechazos solo se ven contra el SII, en certificación con una empresa real.
