# Referencia

URL base: `https://nexoid.victorllerena.com/id/v1`. El contrato completo: https://nexoid.victorllerena.com/id/v1/openapi.json

## GET /id/v1/calendario/feriados/{anio}

**Feriados del Perú de un año** · `calendario.feriados`

Los 16 feriados nacionales del año —Jueves y Viernes Santo incluidos— con su nombre y su norma (DL 713 y sus leyes). Con `sector=publico`, también los días no laborables decretados para el sector público. Solo nacionales: los regionales no están. Un año fuera de `meta.cubre` es 404 `anio_no_disponible`, nunca una lista vacía. **Gratis y sin scope**, como el ubigeo: un catálogo, no una consulta de documentos. No descuenta cuota ni se audita, y responde también al token público del sandbox, con datos reales.

- Scopes: —
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `anio` | path | sí | string |  |
| `sector` | query | no | string | `privado` (por defecto): fines de semana y feriados. `publico`: además, los días no laborables que decreta el Ejecutivo para el sector público, cada uno con su decreto supremo. |

| Estado | Qué significa |
|---|---|
| 200 | Los días inhábiles del año que no son fin de semana, por fecha. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El año está fuera del rango que cubre el calendario (`motivo: anio_no_disponible`). |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/calendario/plazo

**En qué fecha vence un plazo de N días hábiles** · `calendario.plazo`

Cuenta desde el día siguiente a `desde`, como los plazos administrativos, y se salta los fines de semana y los feriados —y los no laborables, con `sector=publico`—. Devuelve también los días que se saltó, con su motivo. **Gratis y sin scope**, como el ubigeo: un catálogo, no una consulta de documentos. No descuenta cuota ni se audita, y responde también al token público del sandbox, con datos reales.

- Scopes: —
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `desde` | query | sí | string | El día desde el que se cuenta, `AAAA-MM-DD`. No cuenta: el plazo empieza al siguiente. |
| `dias` | query | sí | integer | Cuántos días hábiles. |
| `sector` | query | no | string | `privado` (por defecto): fines de semana y feriados. `publico`: además, los días no laborables que decreta el Ejecutivo para el sector público, cada uno con su decreto supremo. |

| Estado | Qué significa |
|---|---|
| 200 | La fecha de vencimiento y los días inhábiles que hubo en medio. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El plazo sale del rango que cubre el calendario (`motivo: anio_no_disponible`). |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/calendario/{fecha}

**Si una fecha es día hábil** · `calendario.dia`

Si es hábil, y si no, por qué —fin de semana, feriado o no laborable, con su norma—, además del día hábil anterior y el siguiente. **Gratis y sin scope**, como el ubigeo: un catálogo, no una consulta de documentos. No descuenta cuota ni se audita, y responde también al token público del sandbox, con datos reales.

- Scopes: —
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `fecha` | path | sí | string |  |
| `sector` | query | no | string | `privado` (por defecto): fines de semana y feriados. `publico`: además, los días no laborables que decreta el Ejecutivo para el sector público, cada uno con su decreto supremo. |

| Estado | Qué significa |
|---|---|
| 200 | La fecha, si es hábil y los hábiles que la rodean. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | La fecha está fuera del rango que cubre el calendario (`motivo: anio_no_disponible`). |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/demo/ruc/{ruc}

**La demo de la landing: un RUC 20, sin token** · `demo.ruc`

Público, para probar antes de pedir acceso. 🔒 **Solo RUC `20`**: uno de persona natural (`10`, `15`, `17`) responde 403, como a un token sin `dni.read`. Se audita como cualquier consulta, no se cobra y nunca sale a un proveedor externo. Admite 10 consultas por minuto y 50 al día por IP: para integrar, pide un token.

- Scopes: `ruc.read`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `ruc` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | La ficha del contribuyente, con su procedencia: la misma que `/ruc/{ruc}`. |
| 403 | Códigos posibles: `scope_insuficiente`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El RUC no está en el padrón publicado, o la demo está cerrada. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/demo/tipo-cambio

**La demo de la landing: el tipo de cambio del día, sin token** · `demo.tc`

Público, para el conversor de la landing. La misma respuesta que `/tipo-cambio`: se audita y no se cobra. Admite 10 consultas por minuto y 50 al día por IP, aparte de la demo de RUC: para integrar, pide un token.

- Scopes: `tc.read`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `moneda` | query | no | string | `USD` (por defecto) o `EUR`. El dólar es el de la SBS; el euro, el que publica el BCRP en soles, sin respaldo de SUNAT. |

| Estado | Qué significa |
|---|---|
| 200 | La cotización vigente, que puede ser de un día anterior. |
| 404 | No hay ninguna cotización cargada todavía, o la demo está cerrada. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## POST /id/v1/dni/batch

**Hasta 100 DNIs de una vez** · `dni.batch`

🔒 **Nunca dispara fallback**, aunque el token tenga `dni.fallback`: la respuesta del batch se guarda 24 h para la idempotencia, y un dato de Clase B no puede acabar en almacenamiento persistente (§15.3, R1).

- Scopes: `batch`, `dni.read`
- Consume cuota: sí

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `Idempotency-Key` | header | no | string | Opcional. La misma clave con los mismos documentos devuelve la misma respuesta, con `Idempotency-Replayed: true` y sin volver a consumir. Con otros documentos, 422. |

| Estado | Qué significa |
|---|---|
| 200 | Un elemento por documento, con menos campos que la consulta individual. Cruza por `dni`, no por la posición. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `finalidad_no_declarada`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`, `cuota_agotada`, `tope_excedente_alcanzado`, `presupuesto_agotado`, `tope_de_pruebas_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/dni/{dni}

**Identidad de una persona natural por DNI** · `dni.consultar`

🔒 Exige `dni.read` y finalidad declarada (R6). Responde siempre `private, no-store`. Si el padrón no lo tiene y el token lleva `dni.fallback`, sale al proveedor externo; sin ese scope devuelve 404 aunque el fallback esté contratado. El 404 lleva `meta.motivo` y `meta.degradado`: «no está» y «no pude preguntar» son cosas distintas.

- Scopes: `dni.read`
- Consume cuota: sí

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `dni` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | La identidad de la persona natural, con `Cache-Control: private, no-store`. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `finalidad_no_declarada`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El DNI no está en el padrón, o no se pudo preguntar fuera. 🔒 `meta.degradado` distingue las dos cosas: un 404 degradado NO significa que la persona no exista. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`, `cuota_agotada`, `tope_excedente_alcanzado`, `presupuesto_agotado`, `tope_de_pruebas_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/health

**Estado del servicio y frescura del padrón** · `health`

Público: sin token. `dependencias.padron` dice si las vistas SIRVEN filas, que no es lo mismo que si el ETL corrió: el padrón puede quedarse vacío con `sync_run` en `ok`. `estado` pasa a `degraded` si el padrón supera 36 h o no sirve filas, si la auditoría tiene pocos meses por delante, si Redis no responde o si un proxy no es de confianza. `degraded` responde 200, salvo con `?estricto=1`; `down` —sin PostgreSQL o sin esquema— responde 503.

- Scopes: —
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `estricto` | query | no | boolean | Con `1`, responde **503** también cuando `estado` es `degraded`. Es para un monitor de uptime que solo mira el código HTTP; sin él, `degraded` responde 200 y solo `down` da 503. |

| Estado | Qué significa |
|---|---|
| 200 | El servicio, sus dependencias y la edad del padrón. |
| 503 | `down`, o `degraded` con `?estricto=1`. El cuerpo es el mismo: dice qué falla. |

## GET /id/v1/me

**Qué es este token y qué puede hacer** · `me`

Devuelve tres listas de scopes: los del token, los del plan y los **efectivos** (su intersección, §13.3). Un 403 de scope tiene dos causas posibles y esto dice cuál es la tuya. `bolsas` dice lo que te queda de tus bolsas prepago y cuándo vence la primera. No consume cuota.

- Scopes: —
- Consume cuota: no

| Estado | Qué significa |
|---|---|
| 200 | El token, su plan y los scopes efectivos. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/openapi.json

**Esta misma especificación** · `openapi`

Público y generado desde la tabla de rutas en cada petición, así que describe **este** despliegue y no la última vez que alguien corrió un comando.

- Scopes: —
- Consume cuota: no

| Estado | Qué significa |
|---|---|
| 200 | Esta misma especificación, generada de la tabla de rutas. |

## GET /id/v1/proveedor/{ruc}

**El alta de proveedor: ficha, padrones de SUNAT, sanciones y vigilancia** · `proveedor.consultar`

Lo que un área de compras revisa antes de dar de alta a una empresa, en una respuesta: la ficha de `/ruc/{ruc}`; si es agente de retención y buen contribuyente, con la fecha de cada lista de SUNAT; sus inhabilitaciones vigentes para contratar con el Estado, del OSCE; si ya la vigilas en alguna lista de Watch (solo lectura); y avisos legibles. Cuesta una consulta, como la ficha. 🔒 **Solo RUC `20`**: un `10`, `15` o `17` es 422 sin cobrarse. `sanciones` es `null` si no hay lista cargada: no lo sé, que no es «sin sanciones».

- Scopes: `ruc.read`
- Consume cuota: sí

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `ruc` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | El alta de proveedor, con la procedencia de la ficha en `meta`. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El RUC no está en el padrón publicado. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`, `cuota_agotada`, `tope_excedente_alcanzado`, `presupuesto_agotado`, `tope_de_pruebas_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## POST /id/v1/ruc/batch

**Hasta 100 RUCs de una vez** · `ruc.batch`

Con `Idempotency-Key`: repetir la misma clave devuelve la respuesta guardada sin volver a cobrar. Cada documento se audita por separado. 🔒 Si el lote trae algún RUC de persona natural (10, 15 o 17), la petición entera exige además `dni.read` y finalidad declarada, y esos documentos se auditan como DNI (§3.4).

- Scopes: `batch`, `ruc.read`
- Consume cuota: sí

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `Idempotency-Key` | header | no | string | Opcional. La misma clave con los mismos documentos devuelve la misma respuesta, con `Idempotency-Replayed: true` y sin volver a consumir. Con otros documentos, 422. |

| Estado | Qué significa |
|---|---|
| 200 | Un elemento por documento, con menos campos que la consulta individual. Cruza por `ruc`, no por la posición. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`, `cuota_agotada`, `tope_excedente_alcanzado`, `presupuesto_agotado`, `tope_de_pruebas_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/ruc/buscar

**Búsqueda por razón social** · `ruc.buscar`

🔒 **Solo tipo `20`.** No existe búsqueda por nombre de persona: sería una vía de resolución de identidad sin documento (R4).

- Scopes: `ruc.search`
- Consume cuota: sí

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `q` | query | sí | string | Texto de la razón social. 🔒 Solo busca entre contribuyentes tipo `20`. |

| Estado | Qué significa |
|---|---|
| 200 | Los contribuyentes tipo 20 que casan con la búsqueda. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`, `cuota_agotada`, `tope_excedente_alcanzado`, `presupuesto_agotado`, `tope_de_pruebas_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/ruc/{ruc}

**Ficha de contribuyente por RUC** · `ruc.consultar`

🔒 Un RUC que empieza en `10`, `15` o `17` es una PERSONA NATURAL: su `nombre` es un dato de identidad, así que exige `dni.read` —no basta `ruc.read`—, finalidad declarada, y responde `private, no-store` (§3.4). `direccion` llega en el 13.2% de los casos y es un techo; `ubigeo` y sus nombres, en el 82.5%. Los campos del padrón extendido traen su propia fecha en `meta.fecha_dato_extendido`, que puede ser de hace un mes.

- Scopes: `ruc.read`
- Consume cuota: sí

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `ruc` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | La ficha del contribuyente, con su procedencia. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `finalidad_no_declarada`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El RUC no está en el padrón publicado. `meta.motivo` distingue «no está» de «no pude preguntar». |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`, `cuota_agotada`, `tope_excedente_alcanzado`, `presupuesto_agotado`, `tope_de_pruebas_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/tipo-cambio

**Tipo de cambio de hoy** · `tc.hoy`

Compra y venta de la SBS, del BCRP o, si no responde, de SUNAT. Si hoy no hay publicación —fin de semana o feriado— devuelve el último día hábil y lo dice en `meta`: `fecha_dato`, `antiguedad_dias` y, pasados cuatro días, `aviso`. **Gratis:** no descuenta de tu cuota del mes ni de tu bolsa, y se sigue respondiendo con la cuota agotada. Lleva el límite de peticiones por minuto de tu plan.

- Scopes: `tc.read`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `moneda` | query | no | string | `USD` (por defecto) o `EUR`. El dólar es el de la SBS; el euro, el que publica el BCRP en soles, sin respaldo de SUNAT. |

| Estado | Qué significa |
|---|---|
| 200 | La cotización vigente, que puede ser de un día anterior. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | No hay ninguna cotización cargada todavía. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/tipo-cambio/mes/{periodo}

**Tipo de cambio de un mes** · `tc.mes`

Los días con publicación del mes `AAAA-MM`, en orden cronológico. Es el rango del primer al último día del mes, con la misma respuesta. **Gratis:** no descuenta de tu cuota del mes ni de tu bolsa, y se sigue respondiendo con la cuota agotada. Lleva el límite de peticiones por minuto de tu plan.

- Scopes: `tc.read`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `periodo` | path | sí | string |  |
| `moneda` | query | no | string | `USD` (por defecto) o `EUR`. El dólar es el de la SBS; el euro, el que publica el BCRP en soles, sin respaldo de SUNAT. |

| Estado | Qué significa |
|---|---|
| 200 | Las cotizaciones publicadas del mes. Un mes futuro o sin datos devuelve una lista vacía. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/tipo-cambio/rango

**Serie histórica de tipo de cambio** · `tc.rango`

Entre dos fechas. El rango está acotado para que no sea una exportación del histórico completo. **Gratis:** no descuenta de tu cuota del mes ni de tu bolsa, y se sigue respondiendo con la cuota agotada. Lleva el límite de peticiones por minuto de tu plan.

- Scopes: `tc.read`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `desde` | query | sí | string | Primer día de la serie, `AAAA-MM-DD`. |
| `hasta` | query | sí | string | Último día. El rango está acotado para que esto no sea una exportación del histórico completo. |
| `moneda` | query | no | string | `USD` (por defecto) o `EUR`. El dólar es el de la SBS; el euro, el que publica el BCRP en soles, sin respaldo de SUNAT. |

| Estado | Qué significa |
|---|---|
| 200 | La serie del intervalo pedido, en orden cronológico. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/tipo-cambio/{fecha}

**Tipo de cambio de una fecha** · `tc.fecha`

Formato `YYYY-MM-DD`. Una fecha sin publicación devuelve la última anterior, con `meta.fecha_dato`, `meta.antiguedad_dias` y, pasados cuatro días, `meta.aviso`. Solo una fecha anterior a toda la serie devuelve 404. **Gratis:** no descuenta de tu cuota del mes ni de tu bolsa, y se sigue respondiendo con la cuota agotada. Lleva el límite de peticiones por minuto de tu plan.

- Scopes: `tc.read`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `fecha` | path | sí | string |  |
| `moneda` | query | no | string | `USD` (por defecto) o `EUR`. El dólar es el de la SBS; el euro, el que publica el BCRP en soles, sin respaldo de SUNAT. |

| Estado | Qué significa |
|---|---|
| 200 | La cotización de esa fecha o la última anterior, con `meta.antiguedad_dias`. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | La fecha es anterior a toda la serie: no hay nada que devolver. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/ubigeo/{codigo}

**Departamento, provincia y distrito de un código INEI** · `ubigeo.consultar`

Catálogo de 1,900 divisiones territoriales. Sin scope: no resuelve ningún documento.

- Scopes: —
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `codigo` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | La división territorial del código INEI. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El código no está en el catálogo. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/uso

**Consumo del periodo, desglosado** · `uso`

Solo del tenant del token. `cuota.consumidas` sale del contador en vivo —es el que decide en cada petición— y `consultas.*` del consolidado, que se vuelca cada 60 s: pueden diferir en un minuto. No incluye el hit rate: es un KPI interno y necesita las consultas que no encontraron nada. Las bolsas prepago se gastan cuando se acaba la cuota del mes: `bolsas.saldo` es lo que queda, ya descontado lo que aún no se ha volcado.

- Scopes: —
- Consume cuota: no

| Estado | Qué significa |
|---|---|
| 200 | El consumo del periodo en curso. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/vencimientos/proximos

**Los próximos vencimientos SUNAT de un dígito** · `vencimientos.proximos`

«¿Qué vence ahora?»: los próximos vencimientos de las obligaciones mensuales (IGV, renta, retenciones, percepciones e ITF) para el **último dígito** del RUC, desde hoy o desde `desde`. 🔒 Recibe el dígito, nunca el RUC: un `?ruc=` es 422. **Gratis y sin scope**, como el ubigeo: un catálogo, no una consulta de documentos. No descuenta cuota ni se audita, y responde también al token público del sandbox, con datos reales.

- Scopes: —
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `digito` | query | sí | string | El último dígito del RUC, del 0 al 9. Nunca el RUC entero. |
| `buen_contribuyente` | query | no | boolean | Con `1`, la columna de los buenos contribuyentes y las UESP, que vencen después. |
| `desde` | query | no | string | Desde qué día, incluido. Por defecto, hoy en Lima. |
| `cantidad` | query | no | integer | Cuántos vencimientos. |

| Estado | Qué significa |
|---|---|
| 200 | Los próximos vencimientos, con los días que faltan. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | `desde` cae después del último año del cronograma (`motivo: anio_no_disponible`): «no lo sé», no «no vence nada». |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/vencimientos/{anio}

**El cronograma de vencimientos SUNAT de un año** · `vencimientos.anio`

Los 12 periodos del año con su vencimiento para cada último dígito del RUC y para los buenos contribuyentes, según la RS 281-2022/SUNAT: el N-ésimo día hábil del mes siguiente. `estado: publicado` es la tabla que SUNAT publicó; `calculado`, la regla aplicada a un año que SUNAT aún no publica, con `aviso`. Los no laborables no mueven el cronograma: son hábiles para fines tributarios. **Gratis y sin scope**, como el ubigeo: un catálogo, no una consulta de documentos. No descuenta cuota ni se audita, y responde también al token público del sandbox, con datos reales.

- Scopes: —
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `anio` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | El cronograma del año, periodo a periodo. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 404 | El año está fuera del rango del cronograma (`motivo: anio_no_disponible`). |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/watch/eventos

**Historial de cambios detectados** · `watch.eventos`

Los eventos que el diff del padrón generó para los RUC vigilados.

- Scopes: `watch.manage`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `limite` | query | no | integer | Cuántos eventos devolver, del más reciente al más antiguo. |

| Estado | Qué significa |
|---|---|
| 200 | Los cambios detectados para los RUC vigilados. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/watch/listas

**Listar watchlists** · `watch.listas`

Con el número de RUCs vigilados en cada una.

- Scopes: `watch.manage`
- Consume cuota: no

| Estado | Qué significa |
|---|---|
| 200 | Las watchlists del tenant, con cuántos RUC vigila cada una. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## POST /id/v1/watch/listas

**Crear una watchlist** · `watch.listas.crear`

Sujeta al tope del plan. Un plan sin Watch lo dice explícitamente.

- Scopes: `watch.manage`
- Consume cuota: no

| Estado | Qué significa |
|---|---|
| 201 | La lista creada. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`, `tope_watch_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## POST /id/v1/watch/listas/{lista}/rucs

**Agregar RUCs a una watchlist** · `watch.rucs.agregar`

🔒 **Solo tipo `20`.** El payload del webhook lleva el nombre, y para un RUC `10` eso sería identidad saliendo a una URL ajena. El tope de RUCs es del TENANT sumando todas sus listas, y un lote que lo rebase se rechaza entero.

- Scopes: `watch.manage`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `lista` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | Cuántos RUC se agregaron y cuáles se rechazaron, con el motivo. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`, `tope_watch_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## DELETE /id/v1/watch/listas/{lista}/rucs

**Quitar RUCs de una watchlist** · `watch.rucs.quitar`

Deja de vigilarlos desde el diff siguiente.

- Scopes: `watch.manage`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `lista` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | Cuántos RUC se quitaron. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## GET /id/v1/watch/webhooks

**Listar endpoints de webhook** · `watch.webhooks`

El secreto de firma no se devuelve nunca tras crearlo.

- Scopes: `watch.manage`
- Consume cuota: no

| Estado | Qué significa |
|---|---|
| 200 | Los endpoints del tenant, con su estado. El secreto no se repite nunca. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## POST /id/v1/watch/webhooks

**Registrar un endpoint de webhook** · `watch.webhooks.crear`

Devuelve el secreto de firma **una sola vez**. Las entregas van firmadas con HMAC-SHA256 sobre timestamp y cuerpo (§12.4).

- Scopes: `watch.manage`
- Consume cuota: no

| Estado | Qué significa |
|---|---|
| 201 | El endpoint creado, con su secreto de firma por única vez. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`, `tope_watch_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |

## POST /id/v1/watch/webhooks/{endpoint}/probar

**Enviar una entrega de prueba** · `watch.webhooks.probar`

Firmada y marcada como prueba. No cuenta para el deshabilitado automático.

- Scopes: `watch.manage`
- Consume cuota: no

| Parámetro | En | Obligatorio | Tipo | Qué es |
|---|---|---|---|---|
| `endpoint` | path | sí | string |  |

| Estado | Qué significa |
|---|---|
| 200 | El resultado de la entrega de prueba. |
| 401 | Códigos posibles: `token_invalido`, `ip_no_permitida`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 403 | Códigos posibles: `no_disponible_en_sandbox`, `scope_insuficiente`, `sin_plan_vigente`, `tenant_suspendido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 422 | Códigos posibles: `documento_invalido`, `tope_watch_alcanzado`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 429 | Códigos posibles: `rate_limit_excedido`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
| 503 | Códigos posibles: `servicio_no_disponible`. El catálogo con qué hacer ante cada uno está en `/docs`, y el enlace viene en `error.documentacion`. |
