TicketBAI
Alta, subsanación y anulación de facturas TicketBAI ante las haciendas forales.
Los endpoints TicketBAI remiten facturas a la hacienda foral que indique provincia. Base URL: https://sandbox.veribai.com (Sandbox) o https://api.veribai.com (LIVE), con autenticación x-api-key.
| Método | Endpoint | Descripción |
|---|---|---|
POST |
/v1/ticketbai/crear |
Alta de una factura TicketBAI. |
PUT |
/v1/ticketbai/subsanar |
Subsanación de un registro remitido. |
POST |
/v1/ticketbai/anular |
Anulación de un registro remitido. |
A diferencia de VeriFactu, la firma y el encadenado TicketBAI se completan en el momento del alta: la respuesta ya incluye el identificador TBAI y el QR. La remisión a la hacienda foral se completa en segundo plano.
En LIVE, todas las operaciones exigen que el emisor tenga su representación firmada; en caso contrario devuelven
403 REPRESENTATION_PENDING. En Sandbox no se exige.
POST /v1/ticketbai/crear
En TicketBAI los datos de identificación viajan en el nivel superior del JSON (no hay bloque cabecera) y el desglose fiscal es la lista plana desglose.
Cada regla foral que la API puede comprobar se valida en el alta, antes de firmar. Un incumplimiento devuelve 400 VALIDATION_ERROR nombrando el campo, y no se firma ni se remite nada. Como la factura TicketBAI queda firmada en el alta, un rechazo posterior de la hacienda obligaría a subsanarla o a emitir una rectificativa. Donde una provincia difiere, la regla se aplica según provincia.
curl -X POST https://sandbox.veribai.com/v1/ticketbai/crear \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"version": "1.0",
"provincia": "bizkaia",
"serie": "A",
"numero": "12345",
"fechaExpedicion": "03-04-2026",
"tipoFactura": "F1",
"descripcion": "Servicios de consultoría",
"emisor": {
"nif": "B00000000",
"nombre": "Prueba 1 SL"
},
"destinatario": {
"nif": "B11111119",
"nombre": "Nombre cliente, SL",
"direccion": "Calle Mayor 1, Bilbao",
"codigoPostal": "48001"
},
"desglose": [
{
"baseImponible": "100.00",
"tipoImpositivo": "21.00",
"cuota": "21.00",
"descripcion": "Consultoría"
}
],
"importeTotal": "121.00"
}'
from datetime import date
from decimal import Decimal
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.ticketbai.crear({
"version": "1.0",
"provincia": "bizkaia",
"serie": "A",
"numero": "12345",
"fechaExpedicion": date(2026, 4, 3),
"tipoFactura": "F1",
"descripcion": "Servicios de consultoría",
"emisor": {
"nif": "B00000000",
"nombre": "Prueba 1 SL",
},
"destinatario": {
"nif": "B11111119",
"nombre": "Nombre cliente, SL",
"direccion": "Calle Mayor 1, Bilbao",
"codigoPostal": "48001",
},
"desglose": [
{
"baseImponible": Decimal("100.00"),
"tipoImpositivo": Decimal("21.00"),
"cuota": Decimal("21.00"),
"descripcion": "Consultoría",
},
],
"importeTotal": Decimal("121.00"),
})
print(respuesta["idTbai"], respuesta["urlValidacion"])
const respuesta = await fetch("https://sandbox.veribai.com/v1/ticketbai/crear", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"version": "1.0",
"provincia": "bizkaia",
"serie": "A",
"numero": "12345",
"fechaExpedicion": "03-04-2026",
"tipoFactura": "F1",
"descripcion": "Servicios de consultoría",
"emisor": {
"nif": "B00000000",
"nombre": "Prueba 1 SL"
},
"destinatario": {
"nif": "B11111119",
"nombre": "Nombre cliente, SL",
"direccion": "Calle Mayor 1, Bilbao",
"codigoPostal": "48001"
},
"desglose": [
{
"baseImponible": "100.00",
"tipoImpositivo": "21.00",
"cuota": "21.00",
"descripcion": "Consultoría"
}
],
"importeTotal": "121.00"
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"nifEmisor": "B00000000",
"idFactura": "…",
"estado": "en_cola",
"sistemaFiscal": "ticketbai",
"provincia": "bizkaia",
"numSerieFactura": "A12345",
"serie": "A",
"numero": "12345",
"tipoFactura": "F1",
"importeTotal": "121.00",
"cuotaTotal": "21.00",
"creadoEn": "2026-04-03T10:00:00Z",
"idTbai": "TBAI-B00000000-…",
"urlValidacion": "https://…",
"qrBase64": "…"
}
La factura queda firmada y encadenada en el momento:
idTbai: el identificador TBAI, generado a partir de la firma.estadovaleen_cola: firmada y encadenada, pendiente de remisión foral. Estado final en/estado.qrBase64yurlValidacion: el QR TicketBAI, verificable ante la hacienda foral.
Campos
| Campo | Notas |
|---|---|
provincia |
Obligatorio: araba, bizkaia o gipuzkoa. Determina la hacienda foral de destino y sus reglas. Debe corresponder con la hacienda del emisor. La discrepancia se rechaza con 400 PROVINCE_MISMATCH. |
emisor |
Obligatorio: nif (9 caracteres) y nombre. El NIF debe corresponder a un cliente dado de alta con la hacienda foral correspondiente. |
serie |
Opcional. TicketBAI admite factura sin serie. |
numero |
Obligatorio. |
fechaExpedicion |
Obligatorio. Formato dd-mm-aaaa; no puede ser posterior a hoy, en hora peninsular. Ver Fechas de la factura. |
horaExpedicion |
Opcional. HH:MM:SS en formato 24 h, dos dígitos por componente. Si se omite, se usa la hora peninsular del momento del alta. Se valida como hora real: 24:00:00 se rechaza. |
fechaOperacion |
Opcional, dd-mm-aaaa, si difiere de la expedición. Cada hacienda foral aplica sus propias reglas. Ver Fechas de la factura. |
tipoFactura |
Obligatorio: F1–F3, R1–R5, los mismos que en VeriFactu. F4 no existe en TicketBAI y devuelve 400. |
descripcion |
Descripción de la operación. |
destinatario |
Obligatorio salvo en F2 y R5 (simplificadas), que no lo admiten. nombre más nif o idOtro (ver destinatario extranjero), y direccion y codigoPostal. Un nif con el dígito de control incorrecto devuelve 400 en las tres provincias. Gipuzkoa exige direccion y codigoPostal; Araba devuelve un aviso sin ellos. Envíalos en las tres provincias. |
destinatarios |
Opcional, en lugar de destinatario, nunca los dos: de 1 a 100 destinatarios con la misma forma. No se pueden mezclar destinatarios nacionales y extranjeros en una factura. |
clavesRegimen |
Opcional: de 1 a 3 claves de régimen de IVA. Por defecto ["01"]. Ver claves de régimen. |
baseImponibleACoste |
Obligatorio con la clave 06 y rechazado sin ella. Cadena decimal, dos decimales como máximo. La clave 06 no se admite en F2, F3 ni R5. |
desglose |
Obligatorio, mínimo una línea. Importes como cadenas decimales. La cuota total se calcula como la suma de las líneas. Ver desglose. |
importeTotal |
Obligatorio. Importe total de la factura, IVA incluido y sin descontar el IRPF. Ver Retención de IRPF. Debe cuadrar con el desglose (suma de baseImponible, cuota y cuotaRecargoEquivalencia) con una tolerancia de ±10,00 €; no se comprueba con las claves 03, 05, 06 o 09, con la causa IE ni, en Araba, con la clave 17. Negativo solo en rectificativas. |
rectificativa |
Obligatorio en tipos R1–R5: tipo (S o I) y facturasRectificadas, de 1 a 100 referencias (serie, numero, fechaExpedicion). Con tipo: "S" es obligatorio importes; con tipo: "I" se rechaza. Ver rectificativas. En F3, y solo en F3, lleva facturasSustituidas: de 1 a 100 referencias, con la misma forma, a las simplificadas que la F3 sustituye. Una F3 no es una rectificativa y no lleva tipo ni facturasRectificadas. |
retencionSoportada |
Opcional. Retención de IRPF de la factura, como cadena decimal con dos decimales como máximo. No puede ser negativa. Se declara aparte: no se resta de importeTotal. |
lineas |
Líneas de detalle de la factura. Obligatorio en Araba; opcional en Bizkaia y Gipuzkoa. Cada línea lleva descripcion, importeUnitario (sin IVA) e importeTotal (con IVA), obligatorios en las tres provincias; cantidad (por defecto 1) y descuento (en euros) son opcionales. Ver líneas de detalle. Máximo 250 líneas por factura, en las tres provincias y tanto en crear como en subsanar. La suma de lineas[].importeTotal debe coincidir con importeTotal, con una tolerancia de ±10,00 € (no se comprueba con las claves 03, 05, 06 o 09). |
subsanacion / rechazoPrevio |
Opcionales, booleanos. En la práctica no los envías: PUT /subsanar marca la subsanación automáticamente. |
idMaquina |
Opcional. Identificador del dispositivo o TPV emisor (máx. 64, A-Za-z0-9._-). Es metadato salvo en los emisores con Alta capacidad activa, donde encamina la cadena y se declara en el documento firmado con sus últimos 30 caracteres. |
emitidaPorTerceros |
Opcional. Solo admite "T": VeriBai firma cada factura TicketBAI con su propio certificado, como tercero que expide por cuenta del emisor. Cualquier otro valor devuelve 400. |
Tipos y formato del cuerpo
TicketBAI valida el tipo de cada campo antes de construir el XML, para que un dato mal tipado sea un 400 inmediato y no un fallo aguas abajo:
provincia,serie,numero,fechaExpedicion,tipoFactura,emisor.nifyemisor.nombredeben ser cadenas JSON. Un número,null, un array o un objeto devuelve400 VALIDATION_ERRORnombrando el campo (o400 INVALID_FIELD_TYPEenanular).- Las longitudes se comprueban en la entrada, con los máximos del propio XSD de TicketBAI 1.2.2:
serieynumero, 20 caracteres cada uno (TextMax20Type), también en las referencias defacturasRectificadasyfacturasSustituidas;descripcionylineas[].descripcion, 250;emisor.nombreydestinatario.nombre, 120;destinatario.direccion, 250;destinatario.codigoPostal, 20. Pasarse devuelve400 VALIDATION_ERRORnombrando el campo y su máximo, sin llegar a firmar. - Los caracteres
#,&y'se rechazan enserieynumero: Araba los rechaza con el código004, y lo hace con el documento ya firmado./, el espacio,Ñ,-,_,.,<,>y"sí se admiten. - Los campos de texto no pueden contener caracteres de control ilegales en XML. El tabulador y los saltos de línea sí se admiten, y se eliminan al serializar.
- Los campos de texto tampoco admiten entidades XML o HTML ya codificadas (
<,�,&…). El escapado del XML lo hace la API, así que ese texto es una doble codificación y devuelve400nombrando el campo. Un&suelto dentro de un nombre (Tom & Jerry) es correcto. - El cuerpo debe ser un objeto JSON. Un array, una cadena, un número o
nulldevuelven400 BAD_REQUEST. - Los importes negativos solo se admiten en rectificativas
R1–R5. La base y la cuota de una línea llevan siempre el mismo signo, salvo en Bizkaia y Gipuzkoa en una rectificativa por diferencias o enR2yR3.
Desglose
Cada línea de desglose corresponde a una calificación y un tipo impositivo, o a una causa de exención o de no sujeción. Una línea sin calificacionOperacion ni operacionExenta es S1.
| Campo | Notas |
|---|---|
baseImponible |
Obligatorio. |
calificacionOperacion |
S1 sujeta (por defecto), S2 inversión del sujeto pasivo, N1 o N2 no sujeta. Excluyente con operacionExenta. |
operacionExenta |
E1–E6, en lugar de calificacionOperacion, nunca los dos. Sin tipo, cuota ni recargo. |
causaNoSujecion |
Solo en N1 y N2. N1: OT (por defecto) o VT (ventas por cuenta de terceros). N2: RL (por defecto, reglas de localización) o IE (localización con impuesto extranjero). |
tipoOperacion |
servicios o entrega. Obligatorio en cada línea cuando el destinatario es extranjero y, en Bizkaia, con la clave 02. Rechazado en cualquier otro caso. |
tipoImpositivo y cuota |
Obligatorios en S1. Ver tipos impositivos. |
tipoRecargoEquivalencia y cuotaRecargoEquivalencia |
Solo en S1, siempre juntos. Ver recargo de equivalencia. |
recargoORegimenSimplificado |
Booleano. Solo en S1 y solo con las claves 51 o 52. Con esas claves, al menos una línea debe llevarlo. |
descripcion |
Obligatorio en Gipuzkoa. |
Cada bloque del desglose admite como máximo 12 líneas S1, 12 S2, 7 exentas y 4 no sujetas, con una línea por causa: una causa de exención o de no sujeción repetida devuelve 400. Agrupa las bases de la misma causa en una línea. El desglose forma un único bloque, o dos (servicios y entregas) cuando el destinatario es extranjero.
Tipos impositivos
tipoImpositivo admite los tipos nacionales 0, 4, 10 y 21, además de 5 para operaciones del 01-07-2022 al 30-09-2024, y 2 y 7.5 para operaciones del 01-10-2024 al 31-12-2024. Una rectificativa puede llevar el tipo de la factura que corrige. Bizkaia comprueba además que cuota sea base × tipo, con una tolerancia de ±1 € (±10 € por encima de 100.000 €).
Inversión del sujeto pasivo (S2)
Sin tipo ni cuota: omítelos o envía 0. Exige destinatario, no se admite en simplificadas y solo con las claves 01, 04, 05, 06, 07 o 12 (en Araba, no con 07).
Exentas
Las claves limitan las causas: con 01 sola no se admiten E2 ni E3; con 07 sola, ninguna de E2 a E5.
Una entrega intracomunitaria exenta (E5) exige un destinatario con idOtro, idType 02 (NIF-IVA) y un país distinto de ES; tipoOperacion: "entrega"; y la clave 01, 15, 19, 51 o 52.
Recargo de equivalencia
La pareja de recargo debe corresponder al tipo de IVA de la línea:
| IVA | Recargo |
|---|---|
21 |
5.2 o 1.75 |
10 |
1.4 |
5 |
0.5 o 0.62 |
4 |
0.5 |
2 |
0.26 |
7.5 |
1 |
0 |
0 |
Exige un destinatario con NIF español. En Araba no se admite en servicios y solo con las claves 01, 07 o 52.
El tope de las simplificadas F2
En simplificadas F2, la suma de baseImponible y cuota de todas las líneas no puede superar los 3.000 €. El tope es exclusivo: 3.000,00 € se acepta y 3.000,01 € devuelve 400 VALIDATION_ERROR nombrando desglose. El recargo de equivalencia queda fuera de esa suma, así que una F2 con recargo puede pasar de 3.000 € de total y seguir siendo válida. Las R5 no tienen tope. El límite es el de la regla AEAT 1150, que recoge el tope de la factura simplificada del art. 4 del RD 1619/2012: ley nacional, no una exigencia foral. Las haciendas forales no lo comprueban; la API sí lo aplica, para que la factura no salga fuera de norma.
Claves de régimen
clavesRegimen lleva de 1 a 3 claves de la lista TicketBAI: 01–15, 17, 19, 51–54. La primera clave determina cuáles pueden acompañarla:
| Primera clave | Claves que pueden seguirla |
|---|---|
02, 04, 09, 10, 14, 15, 17, 53, 54 |
Ninguna: van solas. |
01 |
02, 19, 51, 52 |
03 |
01 |
05 |
01, 06, 08, 11, 12, 13 |
06 |
11, 12, 13, 14, 15 |
07 |
01, 03, 05, 09, 11, 12, 13, 14, 15 |
08 |
01, 02 |
11, 12, 13 |
08, 15 |
Algunas claves condicionan el desglose:
| Clave | Efecto |
|---|---|
02 |
Todas las líneas exentas. |
04 |
Todas las líneas S2. |
06 |
Exige baseImponibleACoste. |
08 |
Una línea no sujeta con causa RL o IE. |
10 |
Causa de no sujeción OT y destinatario con NIF español. |
11, 12 o 13, solas |
Solo tipo 21. |
14 |
Destinatario con NIF que empieza por P, Q, S o V. |
51, 52 |
Al menos una línea con recargoORegimenSimplificado. En Araba, solo para personas físicas o entidades con NIF que empieza por E o J. |
53 |
Solo líneas no sujetas OT (en Bizkaia y Gipuzkoa, también VT). |
54 |
Solo líneas no sujetas IE o VT. |
En Bizkaia, las personas jurídicas no pueden usar 19, 51, 52 ni 53; las personas físicas no pueden usar 06; y 51 y 52 solo se combinan entre sí.
Destinatario extranjero
Un destinatario sin NIF español se identifica con idOtro en lugar de nif:
| Campo | Regla |
|---|---|
idOtro.idType |
02 NIF-IVA, 03 pasaporte, 04 documento oficial del país de residencia, 05 certificado de residencia, 06 otro documento probatorio. 07 no existe en TicketBAI. |
idOtro.codigoPais |
Código ISO 3166-1 alfa-2. Obligatorio salvo con 02. ES solo con 03. |
idOtro.id |
Solo letras y dígitos, de 1 a 20 caracteres. Con 02, empieza por el código del país. |
Con un destinatario extranjero (idOtro, o un NIF que empieza por N), el desglose se organiza por tipo de operación: cada línea lleva tipoOperacion. Una factura a Irlanda del Norte (NIF-IVA XI…) necesita al menos una línea entrega. Las R2 y R3 solo admiten el desglose por factura, salvo una R2 con destinatario NIF-IVA.
Entrega intracomunitaria exenta con una instalación no sujeta en destino:
{
"version": "1.0",
"provincia": "gipuzkoa",
"serie": "A",
"numero": "12346",
"fechaExpedicion": "03-04-2026",
"tipoFactura": "F1",
"descripcion": "Entrega e instalación de maquinaria",
"emisor": { "nif": "B00000000", "nombre": "Prueba 1 SL" },
"destinatario": {
"nombre": "Klang GmbH",
"direccion": "Hauptstrasse 1, Berlin",
"codigoPostal": "10115",
"idOtro": { "idType": "02", "id": "DE123456789" }
},
"clavesRegimen": ["01"],
"desglose": [
{
"baseImponible": "1000.00",
"operacionExenta": "E5",
"tipoOperacion": "entrega",
"descripcion": "Entrega intracomunitaria de maquinaria"
},
{
"baseImponible": "300.00",
"calificacionOperacion": "N2",
"causaNoSujecion": "RL",
"tipoOperacion": "servicios",
"descripcion": "Instalación en destino"
}
],
"lineas": [
{ "descripcion": "Maquinaria", "importeUnitario": "1000.00", "importeTotal": "1000.00" },
{ "descripcion": "Instalación", "importeUnitario": "300.00", "importeTotal": "300.00" }
],
"importeTotal": "1300.00"
}
Líneas de detalle
Cada elemento de lineas es un concepto de la factura:
| Campo | Notas |
|---|---|
descripcion |
Obligatorio, no vacío, máx. 250 caracteres. |
cantidad |
Opcional, por defecto 1. |
importeUnitario |
Obligatorio. Precio unitario de tarifa, sin IVA y antes del descuento. |
descuento |
Opcional. Descuento en euros para toda la línea, no un porcentaje. No puede ser negativo ni superar cantidad × importeUnitario. Un descuento de 0 se acepta. |
importeTotal |
Obligatorio. Total de la línea con IVA, después del descuento. |
Los cuatro importes son cadenas decimales de hasta 12 dígitos enteros y 8 decimales. La hacienda foral cuadra cada línea como (cantidad × importeUnitario − descuento) × (1 + tipo). Envía el precio de tarifa en importeUnitario, el descuento en descuento y el total rebajado con IVA en importeTotal.
Un 10 % de descuento sobre dos unidades a 50,00 € con IVA al 21 %:
{ "descripcion": "Licencia anual", "cantidad": "2", "importeUnitario": "50.00", "descuento": "10.00", "importeTotal": "108.90" }
Gipuzkoa comprueba además el tipo implícito de cada línea (su regla 5018): importeTotal / (cantidad × importeUnitario − descuento) − 1 debe ser un tipo de IVA admitido, recargo de equivalencia incluido (26.2, por ejemplo), con una tolerancia de 0,02 € o el 0,3 % de la base, lo que sea mayor. No se aplica con la clave 06, en rectificativas tipo: "I", en R2 y R3 ni con la causa IE.
Cualquier incumplimiento devuelve 400 VALIDATION_ERROR nombrando la línea y el campo, antes de firmar.
Rectificativas
| Campo | Notas |
|---|---|
rectificativa.tipo |
S sustitución o I diferencias. |
rectificativa.facturasRectificadas |
De 1 a 100 referencias: serie y numero (máx. 20 caracteres cada uno) y fechaExpedicion. |
rectificativa.importes |
Obligatorio con tipo: "S" y rechazado con tipo: "I". Lleva baseRectificada y cuotaRectificada, y cuotaRecargoRectificada opcional: los importes de la factura rectificada, como cadenas decimales con dos decimales como máximo. |
"rectificativa": {
"tipo": "S",
"facturasRectificadas": [
{ "serie": "A", "numero": "12345", "fechaExpedicion": "03-04-2026" }
],
"importes": { "baseRectificada": "100.00", "cuotaRectificada": "21.00" }
}
Retención de IRPF (autónomos)
Una factura corriente de un autónomo con retención lleva tres importes: base 550,00, IVA 115,50 y retención de IRPF 82,50. El campo importeTotal es el importe con IVA, antes de la retención: aquí "665.50", no "583.00". La retención viaja aparte, en retencionSoportada.
Restar el IRPF de importeTotal es el error habitual, y no pasa silenciosamente: el total deja de cuadrar con el desglose y la petición responde 400, indicando que la retención se declara en su propio campo.
Reglas por provincia
Cada hacienda foral valida cosas distintas. La API las comprueba en el alta, para que el error llegue como un 400 inmediato y no como un rechazo foral diferido:
- Araba exige las líneas de detalle:
lineases obligatorio. Limita el recargo de equivalencia y las claves51y52(ver desglose) y no admiteS2con la clave07. - Gipuzkoa exige
descripcionen cada línea dedesglose, ydireccionycodigoPostalen el destinatario. Comprueba el tipo de IVA implícito en cada línea delineas(ver líneas de detalle). - Bizkaia opera dentro de Batuz/LROE (el envoltorio lo gestiona la API sin cambios en tu petición) y solo admite ejercicios desde 2024. Comprueba la cuota de cada línea y restringe las claves según el emisor sea persona física o jurídica (ver claves de régimen).
desglose y lineas son cosas distintas: el desglose es el resumen fiscal por tipo impositivo; las líneas son los conceptos de la factura. Ninguno se deriva del otro.
Los duplicados son idempotentes: reenviar un alta con la misma identidad devuelve 200 con el registro ya existente, no un error. La repetición lleva "yaExistente": true, estado trae el valor de ciclo de vida (registrada, anulada o rectificada) y no se incluyen los campos del QR. yaExistente: true indica que VeriBai ya tenía esa factura y no ha creado nada; en una factura nueva el campo no aparece.
La repetición funciona también con la remisión en curso: si el original ya está firmado pero aún no registrado (o fue rechazado), la respuesta trae la identidad almacenada (idTbai, urlValidacion, el estado de la remisión, p. ej. en_cola o rechazada), también con "yaExistente": true. La factura nunca se vuelve a firmar, así que el identificador TBAI es estable entre reintentos. En la ventana breve en que una petición idéntica anterior sigue firmando, la respuesta es 409 INVOICE_SIGNING_IN_FLIGHT: reintenta en unos segundos.
PUT /v1/ticketbai/subsanar
Corrige un registro ya remitido. El cuerpo es el mismo que el de crear con los datos corregidos; la API marca la subsanación automáticamente. Acepta rechazoPrevio (booleano) para declarar un rechazo foral previo.
POST /v1/ticketbai/anular
El cuerpo es plano e incluye provincia, como el alta:
curl -X POST https://sandbox.veribai.com/v1/ticketbai/anular \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"provincia": "bizkaia",
"nifEmisor": "B00000000",
"serie": "A",
"numero": "12345",
"fechaExpedicion": "03-04-2026"
}'
from datetime import date
import veribai
client = veribai.Client(api_key="TU_CLAVE", environment="test")
respuesta = client.ticketbai.anular({
"provincia": "bizkaia",
"nifEmisor": "B00000000",
"serie": "A",
"numero": "12345",
"fechaExpedicion": date(2026, 4, 3),
})
const respuesta = await fetch("https://sandbox.veribai.com/v1/ticketbai/anular", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"provincia": "bizkaia",
"nifEmisor": "B00000000",
"serie": "A",
"numero": "12345",
"fechaExpedicion": "03-04-2026"
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"nifEmisor": "B00000000",
"idFactura": "…",
"idAnulacion": "…",
"estado": "en_cola",
"sistemaFiscal": "ticketbai",
"provincia": "bizkaia",
"numSerieFactura": "A12345",
"serie": "A",
"numero": "12345",
"fechaExpedicion": "03-04-2026",
"creadoEn": "2026-04-03T11:00:00Z"
}
| Campo | Notas |
|---|---|
provincia |
Obligatorio: araba, bizkaia o gipuzkoa. |
nifEmisor |
Obligatorio. NIF del emisor de la factura a anular. |
numero / fechaExpedicion |
Obligatorios. serie solo si la factura la tenía. Los tres deben coincidir exactamente con la factura original (la fecha forma parte de su identidad). |
nombreEmisor |
Opcional. Por defecto, el nombre registrado del cliente emisor. |
idMaquina |
Opcional. |
La anulación se firma y encadena en el momento, como el alta. No genera QR: es un registro de baja, no una factura.
Repetición idempotente: si la factura ya tiene una anulación registrada o en curso, la respuesta es un 200 con la misma forma (sin creadoEn) más "yaAnulada": true; estado refleja el envío de la anulación previa (registrada o en_cola). Una anulación previamente rechazada no bloquea el reintento.
Errores
Los códigos comunes están en Errores. Específicos de estos endpoints:
| Código | Cuándo |
|---|---|
400 INVALID_PROVINCE |
provincia ausente o distinta de araba, bizkaia, gipuzkoa. En crear/subsanar, el cuerpo incluye provinciaRecibida con el valor recibido. |
400 PROVINCE_MISMATCH |
provincia no coincide con la hacienda registrada del emisor. |
400 VALIDATION_ERROR |
El cuerpo no supera la validación, incluidas las reglas por provincia y los tipos de campo. |
400 INVALID_FIELD_TYPE |
anular: los campos están, pero con el tipo JSON equivocado. |
400 MISSING_FIELDS |
anular: faltan campos obligatorios. El message los enumera. |
400 BAD_REQUEST |
El cuerpo no es un objeto JSON, o el idMaquina tiene formato inválido. |
400 R3_TIMING_ERROR |
crear: rectificativa R3 con menos de seis meses desde la factura que rectifica. Ver Rectificar y anular. |
409 INVOICE_HAS_RECTIFICATIVAS |
anular: la factura tiene rectificativas. Anula primero las rectificativas; details.rectificativas las lista. |
403 UNAUTHORIZED_NIF |
El emisor.nif no está dado de alta bajo tu cuenta. |
403 REPRESENTATION_PENDING |
Solo LIVE: el emisor no tiene la representación firmada. |
404 INVOICE_NOT_FOUND |
anular: no existe la factura original para ese emisor. |
409 DUPLICATE_INVOICE |
Caso límite: el registro existe pero sus datos no pudieron recuperarse. El duplicado normal no es un error, sino una repetición idempotente con 200. |
409 INVOICE_SIGNING_IN_FLIGHT |
Una petición idéntica anterior sigue completando su firma. Reintenta en unos segundos. |
500 XML_PERSIST_ERROR |
No se pudo guardar un XML tras tres intentos. Si el message dice que no se ha enviado nada (subsanar, anulación corregida), reintenta. En crear la factura quedó firmada y encadenada y el reintento no la desbloquea: escríbenos. |
503 CHAIN_CONTENTION |
La cadena de huellas del emisor se estaba escribiendo simultáneamente. No se firmó ni registró nada: reintenta la misma petición. |