Planes y cupo de documentos
Un emisor factura contra un paquete: una cantidad de documentos y una
fecha de corte. Cada documento que emites gasta una unidad. Cuando se
acaban las unidades, o llega la fecha, el API deja de aceptar documentos
nuevos y responde 402.
Eso es todo el modelo. El resto de esta página es dónde exactamente se descuenta, qué no se descuenta, y cómo enterarte antes de quedarte sin cupo.
Qué gasta cupo
Sección titulada «Qué gasta cupo»Un documento emitido = una unidad. No importa el tipo: los cinco tipos que emite el API salen del mismo cupo.
| Tipo | Gasta cupo |
|---|---|
| Factura electrónica de venta | Sí |
| Nota crédito | Sí |
| Nota débito | Sí |
| Documento soporte | Sí |
| Documento equivalente POS | Sí |
No gasta cupo:
- Reintentar con el mismo
reference_code. La idempotencia se resuelve antes de tocar el paquete: el API encuentra el documento que ya existe y lo devuelve sin volver a cobrar. Un timeout tuyo no te cuesta un documento. - Consultar.
GET /v1/documents/...,/v1/subscriptiony el resto de lecturas no descuentan nada.
Cuándo se descuenta
Sección titulada «Cuándo se descuenta»Al crear el documento, antes de guardarlo y antes de encolar el trabajo en segundo plano. Es a propósito: si el paquete ya se agotó, mejor no haber consumido un consecutivo de la DIAN que después nadie va a poder usar.
El orden real dentro de un POST de documento:
- Se busca el
reference_code. Si ya existe, se devuelve ese documento y aquí se acaba: no se cobra nada. - Se validan los datos y el servidor calcula los montos.
- Se descuenta una unidad. Si el paquete no da, responde
402y no se crea ningún documento. - Se guarda el documento y se encola para armar el XML, firmarlo y transmitirlo.
Si el paso 4 falla al guardar, la unidad se devuelve. Es la única devolución automática que existe, y solo cubre el caso de que el documento no llegara a existir.
Dos ventas al mismo tiempo
Sección titulada «Dos ventas al mismo tiempo»El descuento es un UPDATE condicional atómico —la condición y la
escritura son la misma operación—, no un «leer, sumar, guardar». Dos
documentos simultáneos con una sola unidad libre no se pueden pasar de
la cuota: uno pasa y el otro recibe PLAN_EXHAUSTED.
A quién se le exige paquete
Sección titulada «A quién se le exige paquete»Depende de una variable del despliegue, PLANS_ENFORCE_ALL:
| Valor | A quién se le exige |
|---|---|
| Sin poner (por defecto) | Solo a las empresas a las que alguna vez se les vendió un paquete |
true |
A todas |
Una empresa que nunca tuvo paquete y corre con la configuración por
defecto emite sin bloqueo, y GET /v1/subscription le devuelve null.
Cuál es el paquete vigente
Sección titulada «Cuál es el paquete vigente»El vigente es la suscripción más reciente por fecha de creación cuyo
estado sea ACTIVE o EXHAUSTED. Los otros dos estados no cuentan como
vigentes.
status |
Qué significa |
|---|---|
ACTIVE |
Vigente y con unidades |
EXHAUSTED |
Se acabaron las unidades. Sigue siendo el paquete vigente: una recarga lo reabre |
EXPIRED |
Venció. Deja de ser el vigente |
CLOSED |
Cancelado a mano, o reemplazado por un paquete nuevo estando en ACTIVE |
El campo booleano active de la respuesta es más estricto que el
status: es true solo si el estado es ACTIVE y la fecha de fin
todavía no pasó.
Contrato plano
Sección titulada «Contrato plano»Un paquete puede venderse con unlimited: true. Cuenta lo que emites
pero nunca bloquea, y esa comprobación va antes que la del
vencimiento a propósito: a un cliente con contrato plano no se le corta
la facturación en mitad del día porque se pasó la fecha de corte; eso lo
resuelve comercial.
En un contrato plano, documentsRemaining llega en null y
usedPercent en 0.
Los tres errores 402
Sección titulada «Los tres errores 402»Los tres traen el cuerpo de error estándar del API, con errors vacío.
code |
HTTP | Cuándo aparece |
|---|---|---|
PLAN_REQUIRED |
402 | No hay paquete vigente y a esa empresa sí se le exige uno |
PLAN_EXPIRED |
402 | El paquete vigente ya pasó su fecha de fin, y no es plano |
PLAN_EXHAUSTED |
402 | Se acabaron las unidades |
{ "status": 402, "code": "PLAN_EXHAUSTED", "message": "Se agotaron los 1600 documentos del paquete \"Paquete 1.600 documentos\". Contrata una recarga para seguir emitiendo.", "errors": []}Los mensajes de PLAN_EXPIRED y PLAN_EXHAUSTED traen el nombre del
paquete y —según el caso— la fecha de vencimiento o la cantidad
contratada. Están escritos para mostrárselos al usuario tal cual. Aun
así, programa contra code: el texto puede cambiar.
Detalles que se notan en producción
Sección titulada «Detalles que se notan en producción»El documento que alcanza exactamente la cuota sí se emite. Si tienes
1.600 documentos y emites el número 1.600, ese sale bien. Lo que pasa es
que el paquete queda en EXHAUSTED. El 402 llega en el siguiente.
PLAN_EXPIRED normalmente lo ves una sola vez. Al detectar el
vencimiento, el API marca la suscripción como EXPIRED, y ese estado ya
no cuenta como paquete vigente. La petición siguiente no vuelve a dar
PLAN_EXPIRED: da PLAN_REQUIRED. Y desde ese momento
GET /v1/subscription devuelve null —el paquete solo se ve en el
histórico—. Por eso conviene tratar los tres códigos igual: no hay nada
que tu código pueda arreglar, hay que renovar.
PLAN_EXHAUSTED sí se repite en cada intento, porque un paquete
agotado sigue siendo el vigente.
Ninguno de los tres es reintentable. Reintentar sin cambiar nada devuelve el mismo error; míralos junto al resto en Errores.
Cómo enterarte antes de quedarte sin cupo
Sección titulada «Cómo enterarte antes de quedarte sin cupo»runningLow se pone en true cuando:
- Paquete normal: queda el 10% o menos de los documentos (con un umbral mínimo de 1), o faltan 30 días o menos para la fecha de corte.
- Contrato plano: solo por fecha, 30 días o menos.
Consúltalo una vez al día, no en cada venta: cuenta contra el límite de 300 peticiones por minuto y el número no cambia tan rápido.
Consultar el estado del paquete
Sección titulada «Consultar el estado del paquete»Los tres endpoints exigen Authorization: Bearer con un
token válido. El emisor sale del token; un
usuario ADMIN, que no tiene emisor propio, indica sobre cuál trabaja con
la cabecera X-Company-Id. Si falta, responde 400 con
Falta indicar sobre qué emisor trabajar (cabecera X-Company-Id).
Paquete vigente y consumo
Sección titulada «Paquete vigente y consumo»GET/v1/subscription
Devuelve el paquete vigente. Si la empresa no tiene ninguno vigente
—porque nunca tuvo uno, o porque el suyo quedó en EXPIRED o CLOSED—,
responde 200 sin cuerpo: cadena vacía, Content-Length: 0. No es
un 404, y tampoco llega el texto null. Comprueba que el cuerpo no
venga vacío antes de parsearlo: un await res.json() directo revienta
con «Unexpected end of JSON input».
{ "id": "3f0c9e21-5b8a-4d77-9a13-6c2e84fbb410", "plan": { "id": "b7d41e88-2c05-4f6a-8e39-1d5a7c930bb2", "name": "Paquete 1.600 documentos" }, "documentTypes": ["INVOICE", "CREDIT_NOTE", "DEBIT_NOTE"], "unlimited": false, "documentsIncluded": 1600, "documentsUsed": 1520, "documentsRemaining": 80, "usedPercent": 95, "startsAt": "2025-11-20T00:00:00.000Z", "endsAt": "2026-11-20T00:00:00.000Z", "daysRemaining": 77, "status": "ACTIVE", "active": true, "runningLow": true, "priceCop": 220000}| Campo | Tipo | Descripción |
|---|---|---|
id |
uuid | Identificador de la suscripción |
plan.id |
uuid | null | null en paquetes hechos a la medida |
plan.name |
string | Copia congelada del nombre del paquete al venderlo |
documentTypes |
string[] | Valor fijo. Ver el aviso de arriba |
unlimited |
booleano | Contrato plano: cuenta pero no bloquea |
documentsIncluded |
número | Cuota, ya con las recargas sumadas |
documentsUsed |
número | Documentos emitidos contra este paquete |
documentsRemaining |
número | null | null si unlimited. Nunca baja de 0 |
usedPercent |
número | Entero redondeado. 0 si unlimited |
startsAt |
fecha | Inicio del periodo |
endsAt |
fecha | Fecha de corte |
daysRemaining |
número | Redondeado hacia arriba. Nunca baja de 0 |
status |
enum | ACTIVE, EXHAUSTED, EXPIRED o CLOSED |
active |
booleano | ACTIVE y sin vencer |
runningLow |
booleano | Hay que renovar pronto |
priceCop |
número | Precio pagado, en pesos y sin decimales |
Histórico de paquetes
Sección titulada «Histórico de paquetes»GET/v1/subscription/history
Todos los paquetes de la empresa, del más nuevo al más viejo. Sin paginación ni límite.
[ { "id": "3f0c9e21-5b8a-4d77-9a13-6c2e84fbb410", "companyId": "c1a7f402-98d6-4b21-b0e5-7f3ac6d18e94", "planId": "b7d41e88-2c05-4f6a-8e39-1d5a7c930bb2", "planName": "Paquete 1.600 documentos", "documentsIncluded": 1600, "documentsUsed": 1520, "unlimited": false, "priceCop": "220000", "startsAt": "2025-11-20T00:00:00.000Z", "endsAt": "2026-11-20T00:00:00.000Z", "status": "ACTIVE", "notes": "Recarga de 400 documentos - transferencia 4471", "createdAt": "2025-11-20T14:32:07.118Z", "updatedAt": "2026-06-02T09:14:51.006Z" }]Catálogo de paquetes
Sección titulada «Catálogo de paquetes»GET/v1/plans
Los paquetes que se venden hoy, ordenados de menor a mayor cuota.
[ { "id": "b7d41e88-2c05-4f6a-8e39-1d5a7c930bb2", "name": "Paquete 1.600 documentos", "code": "PAQ-1600", "documentQuota": 1600, "priceCop": "220000", "periodMonths": 12, "description": "Incluye facturas, notas crédito, notas débito y documentos soporte. Rangos de numeración ilimitados para sucursales, sin limitación por ventas.", "unlimitedNumberingRanges": true, "active": true, "sortOrder": 1600, "createdAt": "2025-10-01T00:00:00.000Z", "updatedAt": "2025-10-01T00:00:00.000Z", "deletedAt": null }]| Campo | Tipo | Descripción |
|---|---|---|
id |
uuid | |
name |
string | Nombre comercial |
code |
string | Identificador estable y único. Intégrate contra esto, no contra el nombre |
documentQuota |
número | Documentos incluidos en el periodo |
priceCop |
string | Pesos sin decimales, serializado como cadena |
periodMonths |
número | Duración. 12 es como se vende hoy |
description |
string | null | Texto comercial |
unlimitedNumberingRanges |
booleano | true: los rangos de numeración no se limitan por paquete |
active |
booleano | Los inactivos no salen en esta lista |
sortOrder |
número | Orden de presentación |
Lee las tarifas de aquí, no las fijes en tu código. Los paquetes se editan desde el panel: cambian de precio y entran y salen del catálogo.
Contratar, renovar y recargar
Sección titulada «Contratar, renovar y recargar»No hay endpoint para que un emisor se contrate o se recargue solo. Esas operaciones las hace el equipo de la plataforma. Lo que te conviene saber del comportamiento:
- Una recarga suma documentos sin mover la fecha de corte. Es lo que
se hace cuando alguien se queda corto a mitad de año. Verás subir
documentsIncluded. - Recargar reabre un paquete agotado: pasa de
EXHAUSTEDaACTIVEotra vez, sin cambiarendsAt. - Contratar un paquete nuevo cierra el anterior solo si estaba en
ACTIVE: en la misma operación pasa aCLOSED. Uno que ya estaba enEXHAUSTEDoEXPIREDconserva ese estado en el histórico —el caso más frecuente, comprar porque el anterior se agotó, deja el viejo enEXHAUSTEDpara siempre—. Deja de ser el vigente porque el nuevo es más reciente porcreatedAt, no porque se haya cerrado. - Subir la tarifa no cambia lo ya vendido. La suscripción guarda su propia cuota y su propio precio en vez de leerlos del catálogo, así que una lista de precios nueva no toca los paquetes en curso.
Qué hacer en tu integración
Sección titulada «Qué hacer en tu integración»- Maneja los tres
402desde el primer día, aunque el emisor con el que pruebas todavía no tenga paquete. - Trátalos como no reintentables: son un aviso para una persona, no un
fallo temporal. Corta la cola de envío y muestra el
message. - Consulta
GET /v1/subscriptionuna vez al día y alerta conrunningLow. Es el único aviso que hay. - No cuentes los documentos por tu lado. Las recargas cambian la cuota sin previo aviso.
- Recuerda que un rechazo de la DIAN ya gastó su unidad: si vas a reprocesar algo muchas veces, hazlo en sandbox.