Dispositivos (Alta capacidad)
El registro de dispositivos de los emisores de gran volumen, donde cada dispositivo lleva su propia cadena de encadenado.
Un emisor factura con una sola cadena de encadenado: cada registro encadena con el anterior, en orden. Eso pone un techo al ritmo de emisión, porque la cadena se escribe en serie.
Alta capacidad reparte esa cadena. Cada dispositivo registrado lleva la suya y emite en paralelo con los demás. Es un modo contratado, se decide por emisor y lo habilita el equipo de VeriBai. Si emites desde decenas de TPV con el mismo NIF, habla con el equipo.
Un dispositivo (idMaquina) es un emisor lógico independiente: una caja, el hub de una tienda, un proceso de tu backend. No tiene que ser hardware. Es la clave de su propia cadena, así que un identificador ni se renombra ni se reutiliza.
Estos endpoints pertenecen a la API de gestión: base URL
https://manage-api.veribai.com, misma clavex-api-key. El entorno (TEST o LIVE) se detecta automáticamente a partir de la clave.
Modo y fase
Dos campos describen el estado de un emisor. Viajan en la respuesta de estos endpoints y también en el listado y el detalle de clientes:
| Campo | Valores | Qué indica |
|---|---|---|
modoCadena |
unica, por_dispositivo |
Cómo encadena la emisión hoy. Es lo que lee el motor al procesar una factura. |
faseAltaCapacidad |
no_contratada, preparacion, activa |
La fase del contrato. La fija el equipo de VeriBai; no hay endpoint de cliente para cambiarla. |
Los dos valores son compartidos entre TEST y LIVE: un emisor no puede estar en por_dispositivo en un entorno y en unica en el otro. El registro de dispositivos, en cambio, es por entorno: lo que des de alta en TEST no existe en LIVE hasta que lo copies.
| Fase | Emisión | Qué puedes hacer |
|---|---|---|
no_contratada |
Cadena única. idMaquina es solo metadato. |
Leer. Las escrituras responden 409 SHARDING_NOT_ENABLED. |
preparacion |
Cadena única, sin cambios. El idMaquina de una factura se ignora para el encaminamiento, pero se anota. |
Registrar y dar de baja dispositivos, reservar y liberar series del envío centralizado, y avisar de que estás listo. Nada de esto afecta todavía a tus facturas. |
activa |
Una cadena por dispositivo. idMaquina encamina. |
Registrar y dar de baja dispositivos, reservar series. Liberar una reserva ya no. |
Las escrituras dependen de la fase, no del modo. Para eso existe la ventana de preparación: colocar dispositivos y series antes de que nada cambie de sitio.
Endpoints
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/v1/clientes/{nif}/dispositivos |
Dispositivos, series reclamadas y estado de Alta capacidad del emisor. |
POST |
/v1/clientes/{nif}/dispositivos |
Registra un dispositivo. |
DELETE |
/v1/clientes/{nif}/dispositivos/{idMaquina} |
Da de baja un dispositivo. |
POST |
/v1/clientes/{nif}/dispositivos/promover |
Copia a LIVE los dispositivos de TEST. |
POST |
/v1/clientes/{nif}/dispositivos/solicitar-activacion |
Avisa de que la preparación está hecha. Solo en preparacion. |
POST |
/v1/clientes/{nif}/dispositivos/series-centralizadas |
Reserva series para el envío centralizado. |
DELETE |
/v1/clientes/{nif}/dispositivos/series-centralizadas/{serie} |
Libera una reserva. Solo en preparacion. |
Listar los dispositivos
curl https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos \
-H "x-api-key: TU_CLAVE"
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.dispositivos.listar("B76116342")
const respuesta = await fetch("https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos", {
method: "GET",
headers: { "x-api-key": "TU_CLAVE" },
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"entorno": "test",
"nif": "B76116342",
"modoCadena": "por_dispositivo",
"faseAltaCapacidad": "activa",
"dispositivos": [
{
"idMaquina": "CAJA-BILBAO-01",
"etiqueta": "Caja 1 · Bilbao",
"creadoEn": "2026-09-01T10:00:00Z",
"activo": true,
"numeroInstalacion": "20260901T101500Z_B76116342_CAJA-BILBAO-01"
},
{
"idMaquina": "CAJA-VIEJA",
"etiqueta": "",
"creadoEn": "2026-08-01T09:00:00Z",
"activo": false,
"dadoDeBajaEn": "2026-09-10T08:00:00Z"
}
],
"series": [
{ "serie": "A2026", "idMaquinaPropietaria": "CAJA-BILBAO-01", "reclamadaEn": "2026-09-01T11:00:00Z" },
{ "serie": "FC2026", "idMaquinaPropietaria": "__SHARD0__", "reclamadaEn": "2026-08-01T09:00:00Z" }
],
"idsMaquinaVistos": []
}
| Campo | Notas |
|---|---|
entorno |
test o live, derivado de la clave. |
dispositivos[].etiqueta |
Nombre legible que le diste al registrarlo. Cadena vacía si no le diste ninguno. |
dispositivos[].activo |
false es un dispositivo dado de baja. Los de baja se listan siempre: sus series y su cadena siguen existiendo. |
dispositivos[].numeroInstalacion |
Solo VeriFactu: la instalación que la AEAT asocia al dispositivo. Se asigna en su primera factura, así que viene ausente hasta entonces. |
dispositivos[].dadoDeBajaEn / reactivadoEn |
Presentes solo cuando aplican. |
series[] |
Las series ya reclamadas, con su dueño y la fecha de la reclamación. |
series[].idMaquinaPropietaria |
El dispositivo dueño de la serie, o el literal __SHARD0__: el envío centralizado. |
idsMaquinaVistos |
Los idMaquina que tu integración envió durante la ventana de preparación. |
observandoDesde |
Desde cuándo se anotan esos idMaquina. Solo en fase preparacion. |
activacionSolicitadaEn |
Cuándo avisaste de que estabas listo. Ausente hasta que lo hagas. |
Este endpoint siempre responde 200. Un emisor sin Alta capacidad contratada devuelve faseAltaCapacidad: "no_contratada" con las listas vacías: es un estado, no un error.
Un campo ausente no es null ni cadena vacía: se omite. Un dispositivo sin numeroInstalacion es uno que todavía no ha facturado.
Registrar un dispositivo
curl -X POST https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"idMaquina": "CAJA-BILBAO-02",
"etiqueta": "Caja 2"
}'
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.dispositivos.registrar(
"B76116342",
"CAJA-BILBAO-02",
etiqueta="Caja 2",
)
const respuesta = await fetch("https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"idMaquina": "CAJA-BILBAO-02",
"etiqueta": "Caja 2"
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
| Campo | Notas |
|---|---|
idMaquina |
Obligatorio. Máximo 64 caracteres, A-Za-z0-9._-. Es la misma regla que en los endpoints de emisión. |
etiqueta |
Opcional, máximo 120 caracteres. Solo para que lo reconozcas en el listado. |
201 Created con el dispositivo en el sobre { "message", "entorno", "dispositivo" }.
Dos respuestas que conviene distinguir:
- Un identificador activo que ya existe responde
409 MACHINE_ALREADY_REGISTERED. - Un identificador dado de baja se reactiva:
200conreactivado: true. Es la misma instalación y la misma cadena, que se reanuda donde estaba.
Un identificador no se reutiliza para otra cosa. Si sustituyes el dispositivo físico, registra un identificador nuevo con una serie nueva: la cadena del antiguo se queda donde está y sus series siguen siendo suyas.
Dar de baja un dispositivo
curl -X DELETE https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/CAJA-VIEJA \
-H "x-api-key: TU_CLAVE"
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.dispositivos.dar_de_baja("B76116342", "CAJA-VIEJA")
const respuesta = await fetch("https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/CAJA-VIEJA", {
method: "DELETE",
headers: { "x-api-key": "TU_CLAVE" },
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
La baja es una marca, nunca un borrado: el historial firmado, las series reclamadas y la instalación ante la AEAT siguen ahí. Lo único que cambia es que las facturas nuevas dejan de encaminarse a ese dispositivo. Con el emisor en fase activa, una factura con ese idMaquina responde a partir de entonces 409 MACHINE_NOT_REGISTERED.
La llamada es idempotente: repetirla responde 200 conservando el dadoDeBajaEn original. Un identificador que no existe en ese entorno responde 404 MACHINE_NOT_FOUND.
Las series se reclaman una sola vez
Esta es la regla que condiciona toda la preparación:
En fase activa, la primera factura que se emite sobre una serie ata esa serie a su escritor para siempre. No se transfiere, no se libera y no se reasigna. Las anulaciones no reclaman nada.
Hay dos clases de escritor, y ambas cuentan:
- Un dispositivo, identificado por su
idMaquina. - El envío centralizado: las facturas que se envían sin
idMaquina. Siguen siendo válidas en cualquier modo y viajan por la cadena original del emisor. En el listado de series aparece como__SHARD0__.
De ahí dos consecuencias prácticas:
- Planifica series nuevas para los dispositivos. Un dispositivo no reserva: reclama con su primera factura. Si acierta, la serie es suya; si la serie era de otro, recibe
409 SERIE_OWNED_BY_OTHER_MACHINEy basta con darle otra. - Protege las series del envío centralizado, porque ahí el error no se deshace. Las reclamaciones empiezan con la activación, así que las series que tu envío central lleva usando desde siempre están sin reclamar el día de la activación. Si un dispositivo se adelanta y toma una, la siguiente factura del envío central sobre esa serie se rechaza de forma permanente.
Reservar las series del envío centralizado
Es la única reclamación previa que existe. Ata cada serie a __SHARD0__ antes de que ningún dispositivo pueda tomarla.
curl -X POST https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/series-centralizadas \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"series": [
"A2026",
"FC"
]
}'
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.dispositivos.reservar_series_centralizadas(
"B76116342",
["A2026", "FC"],
)
const respuesta = await fetch("https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/series-centralizadas", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"series": [
"A2026",
"FC"
]
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"message": "Series reservadas",
"entorno": "test",
"reservadas": ["A2026"],
"omitidas": [ { "serie": "FC", "motivo": "propiedad_de_dispositivo", "idMaquina": "CAJA-BILBAO-01" } ]
}
De 1 a 100 series por llamada, cada una de hasta 60 caracteres ASCII imprimibles y sin espacio inicial. La serie vacía no se puede reservar: la reclama su primer uso.
La llamada es idempotente. reservadas incluye tanto las que se reservan ahora como las que ya eran del envío centralizado. omitidas lista las que no se tocaron, con un único motivo posible, propiedad_de_dispositivo: un dispositivo ya la tiene, y una reserva nunca sobrescribe a un dueño. Se admite en preparacion y en activa.
Reserva todas las series que tu envío central usa y va a seguir usando. Es más barato reservar de más que descubrir una que falta después de la activación.
Liberar una reserva
curl -X DELETE https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/series-centralizadas/A%2F2026 \
-H "x-api-key: TU_CLAVE"
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.dispositivos.liberar_serie_centralizada(
"B76116342",
"A/2026",
)
const respuesta = await fetch("https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/series-centralizadas/A%2F2026", {
method: "DELETE",
headers: { "x-api-key": "TU_CLAVE" },
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
Solo en fase preparacion, donde una reserva sigue siendo una declaración y nada ha encadenado bajo ella. En fase activa responde 409 RESERVATION_LOCKED, y es permanente: el envío central puede haber encadenado ya sobre esa serie, y liberarla dejaría que un dispositivo tomara una serie que el flujo principal usa.
La serie viaja en la ruta, así que hay que codificarla (A/2026 se escribe A%2F2026). Una serie sin reserva responde 404 SERIE_NOT_RESERVED; una que pertenece a un dispositivo, 409 SERIE_OWNED_BY_OTHER_MACHINE.
Promover de TEST a LIVE
Los registros de dispositivos son por entorno. Cuando el ensayo en TEST está listo, este endpoint copia el montaje a LIVE sin repetirlo a mano:
curl -X POST https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/promover \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"idsMaquina": [
"CAJA-BILBAO-01",
"CAJA-BILBAO-02"
]
}'
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.dispositivos.promover(
"B76116342",
["CAJA-BILBAO-01", "CAJA-BILBAO-02"],
)
const respuesta = await fetch("https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/promover", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"idsMaquina": [
"CAJA-BILBAO-01",
"CAJA-BILBAO-02"
]
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"message": "Dispositivos copiados a LIVE",
"origen": "test",
"entorno": "live",
"creados": ["CAJA-BILBAO-01"],
"omitidos": [
{ "idMaquina": "CAJA-VIEJA", "motivo": "dado_de_baja" },
{ "idMaquina": "CAJA-BILBAO-02", "motivo": "ya_registrado" }
]
}
El cuerpo es opcional: sin idsMaquina se copian todos los dispositivos activos de TEST. Funciona con la clave de cualquiera de los dos entornos, y es idempotente.
Se copian el identificador y la etiqueta, y nada más. El numeroInstalacion y las series son identidades propias de cada entorno: LIVE acuña la instalación en la primera factura del dispositivo y las series se reclaman con facturas reales de LIVE. El emisor tiene que existir ya en LIVE, o la respuesta es 409 CLIENT_NOT_IN_LIVE.
omitidos[].motivo toma tres valores: no_existe (no es un dispositivo de TEST), dado_de_baja (está de baja en TEST) y ya_registrado (ya está en LIVE). Un dispositivo dado de baja en LIVE también se informa como ya_registrado, y la promoción no lo reactiva.
La ventana de preparación
Mientras el emisor está en preparacion, el idMaquina de una factura se sigue ignorando para el encaminamiento, pero queda anotado. GET …/dispositivos los devuelve en idsMaquinaVistos:
{
"faseAltaCapacidad": "preparacion",
"observandoDesde": "2026-09-14T08:00:00Z",
"idsMaquinaVistos": [
{ "idMaquina": "MAIN", "primeraVez": "2026-09-14T09:12:00Z", "ultimaVez": "2026-09-15T07:40:00Z", "registrado": false }
]
}
Sirve para una cosa concreta. Si tu integración ya etiqueta facturas con un idMaquina que nadie ha registrado, esa etiqueta pasa de ser inocua a ser un 409 MACHINE_NOT_REGISTERED en todas las facturas el día de la activación. La lista te dice cuáles son: regístralos con ese mismo identificador, o quita el campo del envío.
Una lista vacía no significa «no envío etiquetas». Las anotaciones empiezan en observandoDesde, el instante en que se abrió la ventana, y un proceso nocturno puede no haber pasado todavía. Lee siempre las dos cosas juntas. observandoDesde viene ausente fuera de la fase preparacion.
Avisar de que estás listo
La activación la hace el equipo de VeriBai. Cuando hayas registrado los dispositivos y reservado las series, avísanos con este endpoint: el aviso queda registrado como uno de los requisitos que el equipo comprueba antes de activar.
curl -X POST https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/solicitar-activacion \
-H "x-api-key: TU_CLAVE"
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.dispositivos.solicitar_activacion("B76116342")
const respuesta = await fetch("https://manage-api.veribai.com/v1/clientes/B76116342/dispositivos/solicitar-activacion", {
method: "POST",
headers: { "x-api-key": "TU_CLAVE" },
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"message": "Aviso enviado. Revisaremos los requisitos y activaremos Alta capacidad.",
"entorno": "test",
"solicitadoEn": "2026-09-15T09:30:00Z"
}
Sin cuerpo. Solo en fase preparacion: fuera de ella responde 409 NOT_IN_PREPARATION. Es idempotente y la primera llamada es la que cuenta: repetirla no reinicia nada. A partir de entonces GET …/dispositivos incluye activacionSolicitadaEn.
El aviso no activa nada por sí mismo. La fase sigue en preparacion hasta que el equipo la pasa a activa.
Antes de la activación
- Elige identificadores definitivos. Un
idMaquinaes la clave de una cadena: no se renombra. - Reserva las series del envío centralizado y planifica series nuevas para los dispositivos.
- Registra o retira los
idMaquinaque ya envías, según lo que muestreidsMaquinaVistos. - Avisa cuando esté todo, con
solicitar-activacion. Es uno de los requisitos que el equipo comprueba antes de activar. - TicketBAI: el documento firmado declara el dispositivo con los últimos 30 caracteres del
idMaquina, que es el largo que fija la especificación. Dos dispositivos que compartan esos 30 caracteres finales se declaran igual ante la hacienda foral. Mantén los identificadores de TicketBAI en 30 caracteres o distínguelos por el final. - Araba marca la primera factura de cada dispositivo nuevo con el aviso
009 Posible error de encadenamiento. La factura se acepta: es un aviso, no un rechazo, y es correcto que aparezca. Bizkaia y Gipuzkoa no lo emiten.
Errores al emitir en fase activa
Estos tres códigos aparecen en los endpoints de emisión, no aquí, y solo en cuentas con Alta capacidad activa:
| Código | Cuándo |
|---|---|
409 MACHINE_NOT_REGISTERED |
El idMaquina enviado no está registrado (o está de baja) para ese emisor en ese entorno. |
409 SERIE_OWNED_BY_OTHER_MACHINE |
La serie del alta pertenece de forma permanente a otro escritor. Usa otra serie para ese dispositivo. |
503 SHARDING_UNAVAILABLE |
La configuración no pudo comprobarse en ese momento. Reintentable: la petición nunca se encamina sin verificar. |
Errores de estos endpoints
| Código | Cuándo |
|---|---|
400 VALIDATION_ERROR |
idMaquina, etiqueta o series fuera de formato. La respuesta nombra el campo. |
403 FORBIDDEN |
El emisor no es de tu cuenta. |
404 NOT_FOUND |
El emisor no existe bajo tu cuenta en el entorno de la clave. |
404 MACHINE_NOT_FOUND |
La baja apunta a un identificador sin registro en ese entorno. |
404 SERIE_NOT_RESERVED |
La serie no tiene reserva del envío centralizado. |
409 MACHINE_ALREADY_REGISTERED |
Ya hay un dispositivo activo con ese identificador. |
409 SHARDING_NOT_ENABLED |
El emisor está en no_contratada: las escrituras no se admiten. Habla con el equipo. |
409 NOT_IN_PREPARATION |
solicitar-activacion fuera de la fase preparacion. |
409 CLIENT_NOT_IN_LIVE |
promover: el emisor todavía no existe en LIVE. |
409 RESERVATION_LOCKED |
La liberación de una reserva con el emisor ya en fase activa. Es definitivo. |
409 SERIE_OWNED_BY_OTHER_MACHINE |
La serie que intentas liberar pertenece a un dispositivo. |
500 DATABASE_ERROR |
Falló una lectura o escritura de datos. Reintentable. |
503 ENVIRONMENT_NOT_AVAILABLE |
El entorno LIVE no está disponible para esa operación. |