Documentos
Emitir la factura y obtener el PDF. VeriBai deriva el registro de las líneas comerciales, lo remite y devuelve el PDF con el QR.
La API admite dos formas de integrar:
- Tu software genera la factura. Envía el registro con
POST /v1/verifactu/crearoPOST /v1/ticketbai/creare imprime en tu plantilla el QR que devuelve la API. - VeriBai genera la factura. Envía las líneas comerciales con
POST /v1/documentos/emitir. VeriBai deriva el registro, lo remite y devuelve el PDF terminado, con el QR.
Esta página documenta la segunda. Funciona con cualquier emisor dado de alta como cliente de tu cuenta, no solo con el NIF de la cuenta. El nombre y el domicilio fiscal del emisor que figuran en el PDF salen de su ficha de cliente en VeriBai: el cuerpo no lleva datos del emisor, solo nifEmisor. Base URL: https://sandbox.veribai.com (Sandbox) o https://api.veribai.com (LIVE). El sistema fiscal, VeriFactu o TicketBAI, y la provincia salen del emisor registrado: la ruta es la misma para los dos.
| Método | Endpoint | Descripción |
|---|---|---|
POST |
/v1/documentos/previsualizar |
Borrador del PDF. No registra nada ni consume número. |
POST |
/v1/documentos/emitir |
Registra la factura y devuelve el PDF con el QR. |
POST |
/v1/documentos/{idFactura}/reanudar |
Completa una emisión cuyo PDF no llegó a generarse. |
GET |
/v1/documentos |
Listado de documentos emitidos de un emisor. |
GET |
/v1/documentos/{idFactura} |
Detalle de un documento, con el PDF. |
El cliente de Python todavía no incluye estas rutas. Los ejemplos muestran cURL y JavaScript.
El cuerpo: el documento comercial
previsualizar y emitir reciben el mismo cuerpo: la factura tal como la ve el destinatario, con precios unitarios y porcentajes. No reciben el registro. VeriBai calcula bases, cuotas, desglose y totales a partir de las líneas.
| Campo | Notas |
|---|---|
nifEmisor |
Obligatorio. NIF del emisor. |
serie |
Hasta 20 caracteres. No admite #, & ni '. |
numero |
Opcional. Sin él, VeriBai asigna el siguiente número de la serie. Con él, se usa ese número. Hasta 20 caracteres. |
fecha |
Obligatorio. Fecha de expedición, DD-MM-YYYY. |
fechaVencimiento |
Opcional, DD-MM-YYYY. No puede ser anterior a fecha. |
tipoFactura |
F1 (completa, por defecto) o F2 (simplificada). Para rectificar, anula y emite de nuevo. |
destinatario |
Obligatorio en F1; no se admite en F2. nombre y uno de nif o idOtro (idType, id, codigoPais). Opcionales: email y direccion (calle, codigoPostal, municipio, provincia, pais, por defecto ES). |
lineas |
Obligatorio, de 1 a 250. Ver la tabla siguiente. |
retencionIrpf |
Opcional. Porcentaje de retención, hasta 2 decimales. |
notas, condicionesPago, formaPago, iban |
Opcionales. Se imprimen en el PDF. |
Cada línea:
| Campo | Notas |
|---|---|
concepto |
Obligatorio. Hasta 250 caracteres. |
referencia |
Opcional. Hasta 40 caracteres. |
cantidad |
Por defecto "1". Distinta de cero, hasta 8 decimales. |
precioUnitario |
Obligatorio. Precio sin IVA, hasta 8 decimales. |
descuentoPct |
Opcional. Porcentaje de descuento de la línea, entre 0 y 100. |
fiscal.operacion |
Obligatorio. VeriFactu: S1, S2, N1, N2, E1 a E8 (E7 y E8 solo con IGIC). TicketBAI: S1, S2, E1 a E6, OT, VT, RL, IE. |
fiscal.tipoImpositivo |
Obligatorio con S1; no se admite con otra operación. |
fiscal.clave |
Clave de régimen, dos dígitos. En TicketBAI, una factura admite hasta 3 claves distintas. |
fiscal.impuesto |
Solo VeriFactu: 01 IVA, 02 IPSI, 03 IGIC, 05 Otros. |
fiscal.tipoOperacion |
Solo TicketBAI: servicios o entrega. |
fiscal.recargoTipo |
Opcional. Tipo del recargo de equivalencia, solo con S1. |
Los importes y porcentajes viajan como cadenas. Un campo del otro sistema fiscal (por ejemplo, fiscal.impuesto en un emisor TicketBAI) o un campo del registro (desglose, totales) responde 400 VALIDATION_ERROR con el campo en campo.
POST /v1/documentos/previsualizar
Devuelve el PDF tal como quedará, con la marca de agua «BORRADOR · SIN VALIDEZ FISCAL». El QR aparece como un recuadro en su posición final, sin código. No registra nada ni consume número de la serie. Valida el cuerpo igual que emitir.
curl -X POST https://sandbox.veribai.com/v1/documentos/previsualizar \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"nifEmisor": "B00000000",
"serie": "A2026",
"numero": "00014",
"fecha": "27-10-2026",
"tipoFactura": "F1",
"destinatario": {
"nif": "B11111119",
"nombre": "Cliente Ejemplo SL"
},
"lineas": [
{
"concepto": "Sesiones de entrenamiento personal",
"cantidad": "5",
"precioUnitario": "55.00",
"descuentoPct": "10",
"fiscal": {
"impuesto": "01",
"clave": "01",
"operacion": "S1",
"tipoImpositivo": "21"
}
}
],
"retencionIrpf": "15"
}'
const respuesta = await fetch("https://sandbox.veribai.com/v1/documentos/previsualizar", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"nifEmisor": "B00000000",
"serie": "A2026",
"numero": "00014",
"fecha": "27-10-2026",
"tipoFactura": "F1",
"destinatario": {
"nif": "B11111119",
"nombre": "Cliente Ejemplo SL"
},
"lineas": [
{
"concepto": "Sesiones de entrenamiento personal",
"cantidad": "5",
"precioUnitario": "55.00",
"descuentoPct": "10",
"fiscal": {
"impuesto": "01",
"clave": "01",
"operacion": "S1",
"tipoImpositivo": "21"
}
}
],
"retencionIrpf": "15"
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"pdfBase64": "JVBERi0xLjQK…",
"borrador": true,
"resumen": {
"subtotal": "247.50",
"iva": "51.98",
"recargo": "0.00",
"total": "299.48",
"cuotaTotal": "51.98",
"baseIrpf": "247.50",
"retencionIrpf": "37.13",
"aPagar": "262.35",
"numSerieFactura": "A202600014",
"serie": "A2026",
"numero": "00014",
"sistemaFiscal": "verifactu",
"variosTipos": false
}
}
resumen trae los importes calculados. total incluye el IVA y el recargo y no descuenta la retención. aPagar es total menos retencionIrpf: solo figura en el PDF, no en el registro. variosTipos es true si las líneas usan más de un tipo impositivo.
POST /v1/documentos/emitir
Registra la factura y devuelve el PDF con el QR oficial. El registro se remite a la administración igual que una factura enviada con /crear: el estado final se consulta con GET /v1/facturas/{idFactura}/estado o por webhook.
curl -X POST https://sandbox.veribai.com/v1/documentos/emitir \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"nifEmisor": "B00000000",
"serie": "A2026",
"fecha": "27-10-2026",
"fechaVencimiento": "26-11-2026",
"tipoFactura": "F1",
"destinatario": {
"nif": "B11111119",
"nombre": "Cliente Ejemplo SL",
"email": "facturas@example.com",
"direccion": {
"calle": "Calle Mayor 1",
"codigoPostal": "28013",
"municipio": "Madrid",
"provincia": "Madrid",
"pais": "ES"
}
},
"lineas": [
{
"referencia": "SRV-01",
"concepto": "Sesiones de entrenamiento personal",
"cantidad": "5",
"precioUnitario": "55.00",
"descuentoPct": "10",
"fiscal": {
"impuesto": "01",
"clave": "01",
"operacion": "S1",
"tipoImpositivo": "21"
}
}
],
"retencionIrpf": "15",
"formaPago": "Transferencia",
"iban": "ES9121000418450200051332"
}'
const respuesta = await fetch("https://sandbox.veribai.com/v1/documentos/emitir", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"nifEmisor": "B00000000",
"serie": "A2026",
"fecha": "27-10-2026",
"fechaVencimiento": "26-11-2026",
"tipoFactura": "F1",
"destinatario": {
"nif": "B11111119",
"nombre": "Cliente Ejemplo SL",
"email": "facturas@example.com",
"direccion": {
"calle": "Calle Mayor 1",
"codigoPostal": "28013",
"municipio": "Madrid",
"provincia": "Madrid",
"pais": "ES"
}
},
"lineas": [
{
"referencia": "SRV-01",
"concepto": "Sesiones de entrenamiento personal",
"cantidad": "5",
"precioUnitario": "55.00",
"descuentoPct": "10",
"fiscal": {
"impuesto": "01",
"clave": "01",
"operacion": "S1",
"tipoImpositivo": "21"
}
}
],
"retencionIrpf": "15",
"formaPago": "Transferencia",
"iban": "ES9121000418450200051332"
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"idFactura": "…",
"nifEmisor": "B00000000",
"numSerieFactura": "A202600014",
"serie": "A2026",
"numero": "00014",
"sistemaFiscal": "verifactu",
"estado": "emitida",
"registro": {
"estado": "pendiente_proceso",
"urlValidacion": "https://…"
},
"pdfBase64": "JVBERi0xLjQK…"
}
| Campo | Notas |
|---|---|
idFactura |
El identificador de la factura. Sirve para /v1/documentos/{idFactura} y para las rutas de /v1/facturas. |
numero |
El número asignado. Sin numero en el cuerpo, el siguiente de la serie. |
estado |
Estado del documento. Ver la tabla siguiente. |
registro |
El registro remitido: estado de la remisión, urlValidacion (la URL que codifica el QR) y, en TicketBAI, idTbai. avisos si la validación detectó avisos no bloqueantes. |
pdfBase64 |
El PDF en base64. Presente cuando estado es emitida. |
yaExistente |
true cuando la petición repite una emisión anterior. |
aviso |
Presente con estado: "pdf_pendiente". |
estado |
Significado |
|---|---|
emitida |
El registro está remitido y el PDF guardado. El PDF no cambia: repetir la emisión devuelve el mismo fichero con yaExistente: true. |
pdf_pendiente |
Respuesta 200. La factura está registrada, pero el PDF no se generó. Llama a reanudar para obtenerlo. |
pendiente |
Solo en el listado y el detalle. No consta si el registro llegó a crearse. reanudar lo resuelve. |
Repeticiones y numeración
Repetir el mismo cuerpo (un doble clic, un reintento tras un tiempo de espera agotado) devuelve la misma factura, con el mismo idFactura y sin consumir otro número. Un cuerpo distinto con un número ya usado responde 409.
La numeración automática es propia de cada serie del emisor. Tiene en cuenta los números enteramente numéricos registrados en esa serie con /v1/verifactu/crear o /v1/ticketbai/crear. Un número alfanumérico no la mueve. Un numero explícito por delante del contador lo adelanta hasta ese número.
Una emisión rechazada no consume número: la serie no queda con huecos.
POST /v1/documentos/{idFactura}/reanudar
Completa una emisión en estado pendiente o pdf_pendiente. Si el registro ya existe, no lo remite de nuevo: genera y guarda el PDF. Se puede repetir sin efectos adicionales.
curl -X POST https://sandbox.veribai.com/v1/documentos/ID_FACTURA/reanudar \
-H "x-api-key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"nifEmisor": "B00000000"
}'
const respuesta = await fetch("https://sandbox.veribai.com/v1/documentos/ID_FACTURA/reanudar", {
method: "POST",
headers: {
"x-api-key": "TU_CLAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
"nifEmisor": "B00000000"
}),
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
La respuesta tiene la forma de la de emitir: estado: "emitida" con pdfBase64, o pdf_pendiente con aviso si el PDF sigue sin generarse.
GET /v1/documentos
Los documentos emitidos de un emisor, del más reciente al más antiguo.
curl "https://sandbox.veribai.com/v1/documentos?nifEmisor=B00000000&limite=50" \
-H "x-api-key: TU_CLAVE"
const respuesta = await fetch("https://sandbox.veribai.com/v1/documentos?nifEmisor=B00000000&limite=50", {
method: "GET",
headers: { "x-api-key": "TU_CLAVE" },
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
{
"nifEmisor": "B00000000",
"documentos": [
{
"idFactura": "…",
"nifEmisor": "B00000000",
"estado": "emitida",
"serie": "A2026",
"numero": "00014",
"numSerieFactura": "A202600014",
"fechaExpedicion": "27-10-2026",
"tipoFactura": "F1",
"sistemaFiscal": "verifactu",
"destinatarioNif": "B11111119",
"destinatarioNombre": "Cliente Ejemplo SL",
"importeTotal": "299.48",
"aPagar": "262.35",
"creadoEn": "2026-10-27T10:00:00Z",
"actualizadoEn": "2026-10-27T10:00:02Z",
"pdfDisponible": true,
"registro": {
"estado": "pendiente_proceso",
"urlValidacion": "https://…"
}
}
],
"total": 1,
"proximaPagina": "…"
}
| Parámetro | Notas |
|---|---|
nifEmisor |
Obligatorio. |
limite |
Opcional, por defecto 50, máximo 200. |
cursor |
Opcional. Cursor de paginación: el proximaPagina de la página anterior. |
proximaPagina no aparece en la última página. El listado no incluye el PDF. registro.estado es el estado de la remisión en el momento de la emisión; el estado actual está en /v1/facturas.
GET /v1/documentos/{idFactura}
Un documento, con los campos del listado. Con estado: "emitida", la respuesta incluye pdfBase64. Si el PDF no puede leerse, la respuesta trae pdfDisponible: false y no incluye pdfBase64. Requiere ?nifEmisor=. Un idFactura que no corresponde a un documento del emisor responde 404 NOT_FOUND.
curl "https://sandbox.veribai.com/v1/documentos/ID_FACTURA?nifEmisor=B00000000" \
-H "x-api-key: TU_CLAVE"
const respuesta = await fetch("https://sandbox.veribai.com/v1/documentos/ID_FACTURA?nifEmisor=B00000000", {
method: "GET",
headers: { "x-api-key": "TU_CLAVE" },
});
if (!respuesta.ok) throw new Error(`VeriBai ${respuesta.status}`);
const datos = await respuesta.json();
En GET /v1/facturas y en el detalle de una factura, documentoDisponible: true indica que la factura tiene un PDF guardado.
El PDF
- Viaja en base64 dentro del JSON. Para obtener el fichero, decodifica
pdfBase64. - Formato A4. El emisor figura en la cabecera. VeriBai no aparece en el documento.
- VeriFactu: el QR, de 35 mm, va al principio de la factura, con «QR tributario:» encima y «VERI*FACTU» debajo.
- TicketBAI: el identificador TBAI y el QR debajo, al final de la factura.
- Las líneas muestran el precio unitario sin IVA y la base de cada línea. La tabla por tipo impositivo solo aparece con más de un tipo, y el bloque de retención, solo con
retencionIrpf. - En Sandbox, el PDF lleva la marca de agua «TEST» y la nota «El código QR no tiene validez fiscal».
- El PDF queda guardado y se descarga de nuevo con
GET /v1/documentos/{idFactura}. En Sandbox se conserva 45 días, como el resto de datos de Sandbox.
curl -s -H "x-api-key: $KEY" \
"https://sandbox.veribai.com/v1/documentos/$ID?nifEmisor=$NIF" \
| jq -r .pdfBase64 | base64 -d > factura.pdf
Errores
Los errores siguen el formato común.
| HTTP | code |
Cuándo |
|---|---|---|
| 400 | VALIDATION_ERROR |
El cuerpo no supera la validación. errors enumera los fallos y campo nombra el primero. Incluye los campos del otro sistema fiscal y los del registro. |
| 400 | MISSING_PARAMETER |
Falta nifEmisor: en el cuerpo de un POST, en la consulta de un GET. |
| 403 | FORBIDDEN |
El emisor no pertenece a tu cuenta. |
| 404 | NOT_FOUND |
idFactura desconocido para ese emisor. |
| 409 | EMISOR_ADDRESS_MISSING |
previsualizar, emitir y reanudar: la ficha del emisor no tiene el domicilio fiscal completo (calle, numero, codigoPostal, ciudad, provincia). El cuerpo trae nifEmisor. No se registra nada ni se consume número. Completa la dirección con PATCH /v1/clientes/{nif} y repite la petición. |
| 409 | SERIE_EN_USO |
Otra emisión de la misma serie está en curso. Reintenta en unos segundos. |
| 409 | NUMERO_YA_UTILIZADO |
Ya consta una factura con esa serie, número y fecha, registrada con /crear. siguienteNumero trae el siguiente número libre. |
| 409 | DOCUMENTO_CONFLICTO |
Ese número ya se usó en /v1/documentos/emitir para un documento con otro contenido. siguienteNumero trae el siguiente número libre. |
| 4xx | Varios, con origen: "crear" |
La validación del registro rechazó la factura. El code, message y errors son los de /crear. No se registró nada: corrige y emite de nuevo. |
| 502 | EMISION_INCIERTA |
No consta si el registro llegó a crearse. El cuerpo trae idFactura. No emitas de nuevo: llama a reanudar con ese idFactura pasados unos segundos. |
Un software, varias cuentas VeriBai
Un software puede emitir para varios emisores de dos formas:
- Cada usuario final con su propia cuenta. Cada emisor abre su cuenta de VeriBai y introduce su clave API en tu software. Cada factura es autoemisión de esa cuenta.
- Una cuenta con los emisores como clientes. Tu cuenta da de alta a cada emisor como cliente y emite para él. En LIVE, cada emisor necesita la representación firmada.
Con una cuenta por usuario final:
- Guarda una clave por cliente, asociada a ese cliente. Una clave no se comparte entre clientes.
- Cada clave pertenece a un entorno: Sandbox (
sandbox.veribai.com) o LIVE (api.veribai.com). Comprueba cada clave al guardarla y al arrancar conGET /v1/cuenta:entornoindica el entorno ynifClientePrincipal, el NIF de la cuenta. Úsalos para identificar la clave. - La cuota mensual y el límite de peticiones son por clave: cada cliente consume los suyos.
- Cada cliente rota y revoca su clave desde su panel. Tu software debe permitir que el cliente sustituya la clave sin un despliegue nuevo.
Series y destinatarios
La configuración de series (número siguiente, ancho del número) y la agenda de destinatarios se gestionan desde el panel. No tienen rutas con clave API.