Ir al contenido

Rangos de numeración

Un rango de numeración es, casi siempre, una resolución de la DIAN guardada en el API: autoriza a tu emisor a numerar un tipo de documento, con un prefijo, entre dos números y entre dos fechas. Las notas crédito y débito son la excepción, porque ahí no hay resolución que copiar — lo explica Las notas no llevan resolución.

Cada documento que emites toma un consecutivo de un rango. Por eso numbering_range_id va en el cuerpo de toda factura, nota o documento soporte: sin rango no hay número, y sin número no hay documento.

La DIAN no deja numerar dos tipos distintos en la misma serie. Cada tipo lleva su serie aparte, y el API refleja eso: un rango tiene un documentType, y solo sirve para ese.

documentType Para qué documento Código único
INVOICE Factura electrónica de venta (01) CUFE
CREDIT_NOTE Nota crédito (91) CUDE
DEBIT_NOTE Nota débito (92) CUDE
SUPPORT_DOCUMENT Documento soporte (05) CUDS
EQUIVALENT_POS Documento equivalente POS (20) CUDE

Si mandas el id de un rango que no corresponde, el documento se rechaza al crearlo, antes de tocar la cola:

{
"status": 422,
"code": "VALIDATION",
"message": "El rango 9a76de2a-71c6-4020-aa54-255c58219283 es de tipo INVOICE, no aplica para este documento",
"errors": []
}

La DIAN autoriza la numeración de la factura, del documento soporte y del equivalente POS: entrega un número de resolución, un prefijo, un tramo y una vigencia. De las notas crédito y débito no autoriza nada. Una nota va con el consecutivo del propio emisor, así que no hay resolución ni fechas de validez que guardar.

documentType resolutionNumber, validFrom y validUntil
INVOICE, SUPPORT_DOCUMENT, EQUIVALENT_POS Obligatorios. Son la copia del acto administrativo que ampara esa serie.
CREDIT_NOTE, DEBIT_NOTE Opcionales. Si no los mandas, se guardan y se devuelven en null.

Se ve en el XML: el bloque sts:InvoiceControl —la resolución, su vigencia y el tramo autorizado— viaja firmado dentro de la factura, del documento soporte y del tiquete POS, y no viaja dentro de una nota. Un número de resolución inventado para llenar el campo no llegaba a ningún documento: solo ocupaba la llave única del rango y le hacía creer al emisor que tenía un papel que nadie le dio.

Un rango así se comporta distinto en dos sitios, y los dos están más abajo: no vence nunca, y su prefijo sirve otra vez para el rango siguiente cuando este se agote.

GET/v1/numbering-ranges

Sin cuerpo. Devuelve un array de rangos, del más reciente al más antiguo por fecha de creación. No hay paginación.

Ventana de terminal
curl https://sandbox.facturacion.misofk.com/v1/numbering-ranges \
-H "Authorization: Bearer $TOKEN"
[
{
"id": "9a76de2a-71c6-4020-aa54-255c58219283",
"companyId": "3f2b1c9d-8a41-4e77-9b03-1d6e5a2c7f80",
"documentType": "INVOICE",
"resolutionNumber": "18764092807123",
"resolutionDate": "2026-01-15",
"prefix": "SETP",
"rangeFrom": "990000000",
"rangeTo": "990005000",
"currentNumber": "990000123",
"validFrom": "2026-01-15",
"validUntil": "2027-01-15",
"isActive": true,
"source": "DIAN_SYNC",
"createdAt": "2026-01-15T14:02:11.884Z",
"updatedAt": "2026-09-03T18:41:07.221Z",
"hasTechnicalKey": true,
"usagePercent": 2.5,
"proximoNumero": "990000124"
}
]
Campo Tipo Descripción
id uuid El que mandas en numbering_range_id.
companyId uuid Emisor dueño de la resolución.
documentType enum Uno de los cinco de arriba.
resolutionNumber string | null Número de la resolución DIAN. Máximo 30 caracteres. null en un rango de notas sin resolución.
resolutionDate string | null yyyy-MM-dd. Puede venir en null.
prefix string Prefijo autorizado. Máximo 10 caracteres, y puede ser cadena vacía.
rangeFrom string Primer número autorizado.
rangeTo string Último número autorizado.
currentNumber string Último consecutivo ya consumido. "0" significa que no se ha usado ninguno.
validFrom string | null yyyy-MM-dd. Antes de esta fecha no se puede emitir. null = el rango no espera ninguna fecha para empezar.
validUntil string | null yyyy-MM-dd. Después de esta fecha tampoco. null = no vence.
isActive boolean Un rango inactivo no entrega números.
source enum MANUAL si lo cargaste tú, DIAN_SYNC si lo trajo la sincronización.
hasTechnicalKey boolean Si el rango tiene clave técnica guardada.
usagePercent number Porcentaje consumido, con un decimal.
proximoNumero string | null Con qué número sale el siguiente documento. null = agotado.
createdAt fecha-hora
updatedAt fecha-hora

La clave técnica nunca sale por el API, ni cifrada ni en claro. Lo único que puedes saber es si está: hasTechnicalKey.

GET/v1/numbering-ranges/:id

Mismo objeto que en la lista.

Código code Cuándo
400 VALIDATION El :id no es un UUID. Falla antes de consultar la base.
404 NOT_FOUND Rango de numeración no encontrado — no existe, o no es de tu emisor.

POST/v1/numbering-ranges

Para registrar a mano lo que dice el PDF de la resolución — o, si es un rango de notas, el tramo de consecutivos con el que el emisor va a numerarlas.

Campo Tipo Obligatorio Descripción
documentType enum Sí INVOICE, CREDIT_NOTE, DEBIT_NOTE, SUPPORT_DOCUMENT o EQUIVALENT_POS.
resolutionNumber string Sí, salvo en notas No puede ir vacío. Máximo 30 caracteres, el largo de la columna. Omitido en una nota, se guarda null.
resolutionDate string No yyyy-MM-dd, y el día tiene que existir.
prefix string No Por defecto, cadena vacía. Máximo 10 caracteres.
rangeFrom entero Sí Mínimo 1.
rangeTo entero Sí Mínimo 1, y no menor que rangeFrom.
validFrom string Sí, salvo en notas yyyy-MM-dd, y el día tiene que existir: 2026-02-30 devuelve 400. Omitido, el rango puede emitir desde ya.
validUntil string Sí, salvo en notas yyyy-MM-dd, con la misma comprobación. Omitido, el rango no vence.
technicalKey string No en el contrato, sí para INVOICE La de la resolución, copiada tal cual.

«Salvo en notas» son CREDIT_NOTE y DEBIT_NOTE, y solo esos dos: en INVOICE, SUPPORT_DOCUMENT y EQUIVALENT_POS los tres siguen siendo obligatorios.

Ventana de terminal
curl -X POST https://facturacion.misofk.com/v1/numbering-ranges \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"documentType": "INVOICE",
"resolutionNumber": "18764092807123",
"resolutionDate": "2026-01-15",
"prefix": "SETP",
"rangeFrom": 990000000,
"rangeTo": 990005000,
"validFrom": "2026-01-15",
"validUntil": "2027-01-15",
"technicalKey": "fc8eac422eba16e22ffd8c6f94b3f40a6e38162c"
}'

La respuesta es el objeto rango completo, con currentNumber: "0" y source: "MANUAL". En un rango de notas, los tres campos que omitiste salen en null, no en cadena vacía, y de esa diferencia depende poder registrar el rango siguiente con el mismo prefijo.

Los tres son 422 con code: VALIDATION, y traen el campo en errors[].

errors[].field message Cuándo
rangeTo El número final no puede ser menor que el inicial rangeTo < rangeFrom
validUntil La resolución no puede vencer antes de empezar a regir validUntil < validFrom, cuando llegan las dos fechas
technicalKey Una resolución de facturas necesita su clave técnica: sin ella no se puede calcular el CUFE documentType: INVOICE sin technicalKey
{
"status": 422,
"code": "VALIDATION",
"message": "Una resolución de facturas necesita su clave técnica: sin ella no se puede calcular el CUFE",
"errors": [
{ "field": "technicalKey", "message": "Viene en la resolución de la DIAN" }
]
}

Omitir resolutionNumber, validFrom o validUntil en un INVOICE, un SUPPORT_DOCUMENT o un EQUIVALENT_POS no llega a esa tabla: lo ataja la validación del cuerpo y responde 400, también con code: VALIDATION, pero con un errors[] que trae solo message y no field. Y el de las fechas te habla del formato aunque el campo venga vacío: cuando no mandas validFrom, lo que recibes es validFrom debe ser una fecha real en formato YYYY-MM-DD. Dice «está mal escrita» donde querría decir «falta», así que si lo ves y estás seguro del formato, mira si el campo está llegando.

Un emisor no puede tener dos rangos con el mismo prefix y el mismo resolutionNumber: la base lo impide.

PATCH/v1/numbering-ranges/:id

Mandas solo lo que cambia. Todos los campos son opcionales y tienen los mismos topes que en el alta: documentType, resolutionNumber, resolutionDate, prefix, rangeFrom, rangeTo, validFrom, validUntil, technicalKey y ultimoEmitido.

Un campo ausente significa «no lo toques». Un campo en null explícito es otra cosa: lo borra. Esa diferencia importa en cuatro campos —resolutionNumber, resolutionDate, validFrom y validUntil— y es la salida del emisor que ya registró su serie de notas con una resolución inventada, cuando el API todavía se la exigía:

Ventana de terminal
curl -X PATCH https://facturacion.misofk.com/v1/numbering-ranges/$ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"resolutionNumber": null,
"validFrom": null,
"validUntil": null
}'

La respuesta es el objeto rango completo, ya con los tres en null. A partir de ahí la serie se comporta como debe: no vence, y su prefijo queda libre para el tramo siguiente.

Mientras de este rango no haya salido nada que no se pueda desmentir. La condición exacta es que no exista ningún documento suyo que la DIAN ya tenga o esté a punto de tener: firmado, enviado, validado o en contingencia, más los que están esperando número en la cola. Un borrador, un descartado y un FAILED no cuentan — de esos no hay nada declarado ahí fuera.

La pregunta no es si currentNumber está en cero. Haber declarado por /ultimo-emitido hasta dónde llegaste en otro sistema mueve el contador, pero no emite nada desde aquí: ese rango se sigue pudiendo corregir. Es justo el caso que antes dejaba encerrado a quien migraba —declaraba su número y con eso perdía el derecho a arreglar la fecha mal tecleada—.

Si ya emitió, la corrección se rechaza con 422:

{
"status": 422,
"code": "VALIDATION",
"message": "De esta resolución ya salió un documento que la DIAN tiene o está a punto de tener, así que sus datos autorizados no se pueden cambiar: viajan firmados dentro de él. Registra la resolución correcta y retira esta.",
"errors": [
{ "field": "id", "message": "Va por el 1253" }
]
}

Dos cosas se corrigen siempre, hayan emitido o no: resolutionDate y technicalKey. Ninguna de las dos viaja dentro de un documento, así que cambiarlas no desmiente nada de lo que ya salió.

El API revisa cómo quedaría el rango después del cambio, no cómo estaba. Así que no puedes dejar sin resolución uno que la necesita, ni por borrarla ni por cambiarle el tipo:

Qué intentas Respuesta
validUntil: null en un INVOICE, SUPPORT_DOCUMENT o EQUIVALENT_POS 422 — Solo las notas de crédito y débito van sin resolución. Este rango tiene que citar la suya y su vigencia: viajan dentro de cada documento que ampare.
documentType: "INVOICE" en una serie de notas que no tiene resolución El mismo 422: quedaría una resolución de facturación sin resolución.
Corregir mientras otro cambia el mismo rango 422 — Alguien cambió esta resolución mientras la corregías. Vuelve a abrirla y comprueba cómo quedó.

ultimoEmitido también viaja por aquí, y esta es la única ruta que lo deja bajar — /ultimo-emitido solo sube. Para bajarlo la exigencia es aún más cerrada, y con su propio 422: que de este rango no haya salido ningún número, cuente el documento como cuente hoy. Un FAILED o un descartado que alcanzó a llevar número también lo impiden, porque ese número ya se gastó y bajar el contador lo volvería a entregar — De esta resolución ya salieron números, así que el contador no puede bajar: se volverían a entregar. Registra la resolución correcta y retira esta.

En sandbox está cerrado, igual que el POST: la numeración es compartida y no se administra desde el API.

PATCH /v1/numbering-ranges/{id}/actividad
{ "isActive": false }

Un rango retirado deja de entregar consecutivos: POST /v1/documents responde 422 RANGE_INACTIVE y el rango desaparece de las listas de resoluciones utilizables. No se pierde nada — el consecutivo se queda donde está, y reactivarlo ("isActive": true) lo continúa desde ahí, no desde el principio.

Es la salida de una resolución mal cargada que ya emitió, y lo que hace un emisor con la resolución del proveedor anterior una vez ha migrado.

PATCH /v1/numbering-ranges/{id}/ultimo-emitido
{ "ultimoEmitido": 1253 }

Un rango recién registrado arranca en rangeFrom. Si el emisor ya emitió con esa misma resolución en otro sistema, hay que decirlo o los primeros documentos saldrían con números que ya usó.

ultimoEmitido es el último gastado, no el próximo: es el mismo significado que currentNumber, para que no haya dos formas de decir lo mismo. Con 1253 en un rango que empieza en 1000, el siguiente documento sale con el 1254.

En sandbox POST /v1/numbering-ranges está cerrado:

{
"status": 422,
"code": "VALIDATION",
"message": "En sandbox la numeración es compartida y no se administra desde aquí. Usa el rango que ya viene listado.",
"errors": []
}

El rango de sandbox es uno solo para todos los integradores. GET sigue funcionando y te lo devuelve: ese es el id que mandas en tus pruebas.

POST/v1/numbering-ranges/sync-dian

Sin cuerpo. Le pregunta a la DIAN por las resoluciones autorizadas de tu emisor y las guarda con su clave técnica.

Es la forma recomendada de cargar numeración de facturación. Registrarla a mano es copiar ocho datos de un PDF, y el que más duele equivocarse es la clave técnica: un carácter cambiado no se nota al guardar —el rango se ve perfecto— sino cuando la DIAN rechaza la primera factura.

Antes de llamar a la DIAN, el API exige tres cosas del emisor: credenciales de software (softwareId y PIN), certificado digital activo y proveedor configurado.

Ventana de terminal
curl -X POST https://facturacion.misofk.com/v1/numbering-ranges/sync-dian \
-H "Authorization: Bearer $TOKEN"
{
"encontradas": 2,
"creadas": 1,
"actualizadas": 1,
"sinCambios": 0,
"conflictos": 0,
"sinClave": 0,
"rangos": [
{
"prefix": "SETP",
"resolutionNumber": "18764092807123",
"rangeFrom": "990000000",
"rangeTo": "990005000",
"validUntil": "2027-01-15",
"resultado": "clave-actualizada",
"tieneClaveTecnica": true
},
{
"prefix": "FE",
"resolutionNumber": "18764092811987",
"rangeFrom": "1",
"rangeTo": "5000",
"validUntil": "2027-06-30",
"resultado": "creado",
"tieneClaveTecnica": true
}
],
"motivo": null
}
Campo Tipo Descripción
encontradas number Cuántas resoluciones devolvió la DIAN.
creadas number Rangos nuevos dados de alta.
actualizadas number Rangos que ya existían y a los que se les corrigió la clave técnica.
sinCambios number Rangos que ya existían con la misma clave.
conflictos number Las que no se pudieron guardar. Cada una dice por qué en su fila.
sinClave number Las que la DIAN devolvió sin clave técnica en esta respuesta. Las nuevas no sirven para facturar; las que ya existían conservan la suya.
rangos array Una entrada por resolución.
motivo string | null Lo que dijo la DIAN cuando no mandó ninguna. null si vinieron rangos.

Cada entrada de rangos trae prefix, resolutionNumber, rangeFrom, rangeTo, validUntil, tieneClaveTecnica y un resultado que vale creado, clave-actualizada, sin-cambios o conflicto.

conflicto es la fila que hay que mirar. El sync recorre las resoluciones una por una y no se detiene cuando una falla, así que la respuesta puede venir con rangos creados y una resolución que se quedó fuera. Esa trae además un motivo propio, con la razón concreta:

{
"prefix": "FE",
"resolutionNumber": "18764092811987",
"rangeFrom": "1",
"rangeTo": "5000",
"validUntil": "2027-06-30",
"resultado": "conflicto",
"motivo": "Ese rango se pisa con la resolución 18764092800001, que va del 1 al 3000 con el mismo prefijo y ya tiene números comprometidos. Dos documentos no pueden llevar el mismo número.",
"tieneClaveTecnica": true
}

No lo confundas con el motivo de arriba, que es del nivel de la respuesta y solo habla de cuando la DIAN no devolvió ninguna.

Crea siempre rangos INVOICE. La operación que se consulta responde por las resoluciones de facturación; las notas, el documento soporte y el equivalente POS se registran aparte, a mano, con POST /v1/numbering-ranges.

De un rango que ya existe solo corrige la clave técnica, y solo si cambió. El consecutivo actual, los números y las fechas se quedan como están. Es a propósito: una ampliación de rango llega de la DIAN como una resolución nueva, con su propia clave técnica, no como un cambio de la anterior.

encontradas: 0 con rangos: [] no siempre significa «no tienes resoluciones». El API te pasa la explicación de la DIAN, literal, en motivo:

{
"encontradas": 0,
"creadas": 0,
"actualizadas": 0,
"sinCambios": 0,
"conflictos": 0,
"sinClave": 0,
"rangos": [],
"motivo": "No registra prefijos asociados al código de software: beec7513-..."
}

Ese caso concreto no se arregla aquí: hay que asociarle los prefijos al software en el portal de la DIAN.

Código code Cuándo
422 VALIDATION Falta registrar el software (softwareId y PIN) antes de consultar la numeración
422 CERT_MISSING El emisor no tiene certificado activo
422 CERT_EXPIRED El certificado está vencido
422 VALIDATION En sandbox: la numeración es compartida y no se sincroniza

Los dos de certificado aparecen porque la consulta a la DIAN va firmada.

Es el dato de la resolución que entra en el cálculo del CUFE:

CUFE = SHA-384(NumFac + FecFac + HorFac + ValFac + '01' + ValIVA + '04' +
ValINC + '03' + ValICA + ValTot + NitOFE + NumAdq +
ClTec + TipoAmbiente)

ClTec es la clave técnica del rango. No hay forma de calcular un CUFE sin ella, y sin CUFE no hay factura: es el identificador con el que la DIAN recibe el documento y con el que se arma el QR.

Solo la factura la usa. El CUDE de las notas y del equivalente POS, y el CUDS del documento soporte, se calculan con el PIN del software en su lugar. Por eso un rango CREDIT_NOTE sin clave técnica es perfectamente normal, y uno INVOICE sin clave técnica es una bomba de tiempo.

El número se toma con una operación atómica: dos facturas simultáneas nunca reciben el mismo. currentNumber es el último consumido, y "0" significa que no se ha usado ninguno — el primero que sale es rangeFrom.

Son cuatro cosas, y no salen todas por el mismo sitio. Tres se comprueban al crear el documento —con el cajero todavía delante de la pantalla— y vuelven en el 422 de la respuesta del POST:

code message Cuándo
RANGE_INACTIVE Esa resolución está retirada y no se puede facturar con ella. isActive: false
RANGE_EXPIRED La resolución de numeración está vencida validUntil anterior a hoy
VALIDATION La resolución es válida desde {validFrom} Todavía no empieza a regir

Los dos primeros traen el detalle en errors[0] con field: "numbering_range_id": el rango citado —por su prefijo cuando no tiene resolución— y Venció el {validUntil}. El tercero lo dice todo en el message.

Los dos de la vigencia no le pueden pasar a un rango de notas con las fechas en null: sin fechas no hay nada que comparar y la comprobación se salta. Un rango así solo deja de entregar números si lo retiras o si se agota.

La cuarta, el agotamiento, no se sabe hasta pedir el número, y eso pasa ya dentro de la cola. El documento se acepta con 201, queda en QUEUED, y el fallo aparece en segundo plano — el mismo camino que la clave técnica que falta:

code message Cuándo
RANGE_EXHAUSTED El rango de numeración está agotado No queda número dentro de rangeTo

Ahí el documento pasa a FAILED, el mensaje queda en dian_errors[0].message, y sale el webhook document.failed:

{
"event": "document.failed",
"data": {
"id": "1c4f8e0b-6d52-4a19-8f77-2b90cae41d33",
"reference_code": "FV-1042",
"error": "El rango de numeración está agotado"
},
"sent_at": "2026-09-04T14:22:31.006Z"
}

Por ahí solo viaja la frase. No hay code, no hay errors[] y no hay field: "numbering_range_id": el detalle que acompaña al error —«Último número autorizado: 990005000»— se pierde al marcar el documento.

A cambio, estos cuatro no se comen el consecutivo: el número no llega a tomarse. El reintento del documento vuelve a pedirlo, y volverá a fallar mientras el rango siga como está.

Además del rango, el POST rechaza en el momento el del tipo equivocado que viste al principio de esta página. Ese se comprueba antes que los tres de arriba, así que un rango retirado y del tipo que no toca se queja primero del tipo.

De los cuatro, los dos que no se arreglan tocando el rango son vencido y agotado: hay que pedirle numeración nueva a la DIAN, y eso no es instantáneo. Son los únicos fallos del API cuya solución vive fuera del software.

Vencido y agotado son independientes. Una resolución puede vencer con 4.000 números sin usar, y puede agotarse un año antes de su fecha de vencimiento.

Un rango de notas no vence, y cuando se agota la salida no pasa por la DIAN: registras el siguiente tramo con el mismo prefijo y sigues. Es el único agotamiento que se resuelve el mismo día.

Tienes tres formas de saberlo, y las tres vienen en el mismo objeto.

usagePercent — el porcentaje consumido, con un decimal. Lo más rápido de graficar.

proximoNumero — con qué número sale el siguiente documento, o null si ya no sale ninguno. Es la respuesta a «¿en qué voy?», que no es lo mismo que currentNumber: un rango recién cargado muestra currentNumber: "0", que se lee como si el consecutivo fuera cero cuando en realidad la próxima factura va a salir con 990000000.

Los números que faltan, si los quieres en unidades:

quedan = rangeTo − proximoNumero + 1

Con el rango del ejemplo: 990005000 − 990000124 + 1 = 4877. Cuando proximoNumero es null, quedan cero.

El API te avisa por su cuenta en dos momentos, pero los dos son avisos para el dueño del emisor, no para tu integración: se deja una advertencia en el registro del servidor al pasar del 90 % consumido, y sale un correo cuando al emisor le quedan 500 consecutivos o menos sumando todos sus rangos activos y vigentes.

Un rango se agota en mitad de una venta, no en mitad de un despliegue. Y una resolución nueva de la DIAN no se consigue el mismo día. Lo que hay que mirar, en orden de qué tan tarde te enteras si no lo miras:

hasTechnicalKey de todo rango INVOICE. Revísalo el día que cargas el rango, no el día que facturas con él. Es un booleano; si viene en false, esa numeración no puede emitir facturas aunque todo lo demás se vea bien.

usagePercent de los rangos vigentes, a diario. Pon tu alerta en el 80 %, no en el 90 %: el 90 % ya es tarde para tramitar una resolución. Con un rango de 5.000, el 80 % te deja 1.000 facturas de margen.

validUntil contra hoy más 30 días. Una resolución vencida no entrega números aunque le sobren miles. Es el caso que más sorprende, porque usagePercent se ve tranquilo hasta el último día. En un rango de notas viene en null y no hay nada que vigilar: ahí lo único que se acaba es el tramo, y eso lo ves en usagePercent.

isActive. Un rango inactivo tampoco entrega números, y no da ninguna señal previa: la primera noticia es un documento en FAILED.

El webhook document.failed. Es donde aparecen los fallos que no alcanzas a ver en la respuesta del POST: el rango agotado y la clave técnica que falta. Los otros tres del rango —retirado, vencido y «válida desde»— te los dice el POST en su 422, y solo caen aquí si el rango cambia entre aceptar el documento y numerarlo. La diferencia entre el agotamiento y la clave está en el consecutivo: el agotado se levanta al pedir el número, así que no llega a gastarlo; la clave técnica falla cuando el número ya está guardado, y ese sí se consumió.

sinClave después de cada sync-dian. Si es mayor que cero, la DIAN devolvió resoluciones sin clave técnica en esa respuesta. Las que además salen con resultado: "creado" son las que no sirven para facturar: se dieron de alta sin clave. Las que ya existían no pierden nada —una respuesta sin TechnicalKey nunca borra la clave guardada—, así que pueden seguir emitiendo aunque cuenten aquí. Para saber si un rango puede emitir, mira hasTechnicalKey en GET /v1/numbering-ranges, que es el que refleja la clave realmente guardada.

Una regla que ahorra el susto: cuando pidas una resolución nueva, cárgala antes de que se agote la anterior. Nada impide que un emisor tenga dos rangos INVOICE vigentes a la vez; el que se usa es el del numbering_range_id que mandes, así que el cambio de uno a otro lo decides tú y puedes hacerlo un martes.