TicketBAI
The Anti-Fraud Law (Law 11/2021) and its subsequent technical regulation Royal Decree 1007/2023 mandates that businesses must ensure their invoicing and accounting systems meet strict technical standards to prevent data manipulation. For business located in Euskadi, Spain Anti-Fraud law mandates the use of TicketBAI. B2Brouter’s TicketBAI REST API is a technical solution that allows your business to easily be 100% compliant offloading the complexities to B2Brouter and focusing on the business logic of your system.
B2Brouter provides a convenient API for submitting TicketBAI tax reports so you can easily comply with the legislation of the three Basque Tax Authorities (Araba, Bizkaia, and Gipuzkoa). Our API abstracts away the differences between them and offers you an unified interface that is fully compliant with all three Tax Authorities. You can check the status of B2Brouter as “software garante” in all three Tax Authorities: Araba, Bizkaia, Gipuzkoa.
What is TicketBAI?
Section titled “What is TicketBAI?”TicketBAI is a joint project of the Basque Provincial Councils (Gipuzkoa, Bizkaia, and Araba) and the Basque Government to control invoicing and ensure tax compliance. It requires all self-employed individuals and companies to use certified invoicing software that generates a unique code and a QR code for each invoice. This system aims to prevent tax fraud by ensuring that all economic transactions are recorded and transmitted to the tax authorities in real-time or near real-time.
TicketBAI involves:
- Generating a unique TicketBAI identifier, a QR code for each invoice, and a tax report that includes the relevant information of the invoice.
- Digitally signing each tax report using a qualified electronic certificate.
- Chaining tax reports together cryptographically to ensure immutability and prevent tampering.
- Submitting the tax report to the relevant Basque Tax Authority (Hacienda Foral) in a specified format in a timely manner.
- Processing the responses from the Tax Authority for each submission.
With B2Brouter, you can abstract much of the complexities of this process, and comply with the Anti-Fraud Law by issuing just a few REST calls to the API. You will not need your own qualified electronic certificate because B2Brouter is a Social Collaborator in Tax Management. You just need a valid B2Brouter API Key.
B2Brouter provides two modalities of operation depending on whether you are using B2Brouter for both issuing invoices and reporting them to the Tax Authority, or just for tax reporting.
Testing environments: Use sandbox for initial API testing and payload validation — sandbox simulates TicketBAI submissions so you can exercise the invoice lifecycle without hitting the Basque Tax Authority. For full end-to-end testing with the authority’s test endpoint, use the staging environment (
api-staging.b2brouter.net).
Setting up and working with B2Brouter API
Section titled “Setting up and working with B2Brouter API”The first step is to set up the configuration for TicketBAI for each one of the Companies for which you want to submit tax reports. You can refer to the Tax Report Settings Guide for a full description of the process. The call for configuring an account is as follows:
curl --request GET \ --url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/tax_report_settings \ --header 'X-B2B-API-Key: {YOUR_API_KEY}' \ --header 'X-B2B-API-Version: {YOUR_API_VERSION}' \ --header 'Content-Type: application/json' \ --data '{ "tax_report_setting": { "code": "tbai", "enabled": true, "start_date": "2025-04-06", "auto_generate": true, "auto_send": true, "reason_vat_exempt": "E1", "special_regime_key": "01", "reason_no_subject": "OT", "credit_note_code": "R1", "delegation": "gipuzkoa", "type_operation": "services" } }'Invoice API
Section titled “Invoice API”If you are using B2Brouter for both issuing invoices and reporting them to the Tax Authority, once you have correctly configured your account, you can operate as you’d normally do for issuing invoices.
The issued invoices will contain the mandatory QR code, and the TicketBAI tax report will be sent automatically to the relevant Tax Authority. This is an example of a call to create and issue an Invoice:
curl --request POST \ --url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/invoices \ --header 'X-B2B-API-Key: {YOUR_API_KEY}' \ --header 'X-B2B-API-Version: {YOUR_API_VERSION}' \ --header 'content-type: application/json' \ --data '{ "send_after_import": true, "invoice": { "date": "2025-04-15", "due_date": "2025-05-15", "number": 48, "payment_method": 1, "contact": { "name": "Ejemplo S.L.", "address": "Calle Falsa 123, 3ºA", "city": "Madrid", "province": "Madrid", "postalcode": "28001", "country": "es", "email": "john.doe@example.com", "language": "es", "currency": "EUR", "terms": "15", "payment_method": 1, "tin_scheme": 9920, "tin_value": "ESB12345678" }, "invoice_lines_attributes": [ { "quantity": 10.0, "description": "Servicio de Ejemplo", "price": 11.0, "unit": 9, "taxes_attributes": [ { "name": "IVA", "percent": 21.0, "category": "S" } ] } ] } }'In the response of the POST REST call for creating an invoice, if send_after_import is set to true, you will find a section tax_report_ids in which you’ll find the ID of the tax report associated with the invoice. Note that tax_report_ids is an array because each invoice can have more than one tax report for cancellations and corrections.
You can access the complete tax report, including the QR code, its identifier, its state, and all other fields, by calling the get tax report endpoint with the tax report ID provided in the response of the create invoice call.
The state of the tax report will always be processing in the response of the POST call because the background process that chains and sends the tax report is performed independently. This is the reason why you must check the life cycle of the tax report. You have two options:
-
The recommended way is using the tax report web hook. The web hook will issue a POST call to your system each time the state of the tax report reaches a final state: registered, error, and registered_with_errors
-
Polling the state of the tax report with get tax report endpoint until the state field is registered, error or registered_with_errors.
Tax Report API for TicketBAI
Section titled “Tax Report API for TicketBAI”If you are not using B2Brouter for issuing invoices, need more control over the process of generating and processing tax reports, or expect a high volume of tax reports, you can use the Tax Report API.
The structure of the JSON format for B2Brouter tax reports is based on PEPPOL Continuous Transaction Control (CTC). B2Brouter Tax Report API is an universal API not only geared towards TicketBAI but designed to handle tax reporting around the World.
It’s important to notice that for TicketBAI, tax reports have tax report lines which represent the individual lines of the original invoice. The TaxReport model does not perform any calculations. You must provide all relevant figures in the tax report.
Create a Tax Report
Section titled “Create a Tax Report”To create a tax report you have to call the create tax report endpoint. A minimal and valid tax report can be created by the following POST call:
curl --request POST \ --url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/tax_reports \ --header 'X-B2B-API-Key: {YOUR_API_KEY}' \ --header 'X-B2B-API-Version: {YOUR_API_VERSION}' \ --header 'Content-Type: application/json' \ --data '{ "tax_report": { "type": "TicketBai", "invoice_date": "2025-04-03", "invoice_number": "11", "description": "Consulting service", "customer_party_tax_id": "B12345678", "customer_party_country": "es", "customer_party_postalcode": "28001", "customer_party_address": "Calle Falsa 123, 3ºA", "customer_party_name": "Ejemplo S.L.", "tax_inclusive_amount": 121.0, "tax_amount": 21.0, "invoice_type_code": "F1", "currency": "EUR", "tax_report_lines": [ { "quantity": 1.0, "description": "Service", "price": 100.0, "tax_inclusive_amount": 121.0, "tax_exclusive_amount": 100.0, "tax_amount": 21.0 } ], "tax_breakdowns": [ { "category": "S", "non_exempt": true, "non_exemption_code": "S1", "percent": 21, "taxable_base": 100.0, "tax_amount": 21.0 } ] } }'The response to this POST call will return immediately with the tax report in a processing state. The qr and identifier fields will be null initially. To retrieve them, make subsequent GET requests until the state changes to signed or registered.
The following is a more complex example of a TicketBAI with two lines, a global discount, IVA of 21%, and IRPF of 15%:
curl --request POST \ --url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/tax_reports \ --header 'X-B2B-API-Key: {YOUR_API_KEY}' \ --header 'X-B2B-API-Version: {YOUR_API_VERSION}' \ --header 'Content-Type: application/json' \ --data '{ "tax_report": { "type": "TicketBai", "invoice_date": "2025-08-13", "invoice_number": "2025-128", "description": "Invoice with multiple lines, global discount, IVA, and IRPF (tax withheld)", "customer_party_tax_id": "B12345678", "customer_party_country": "es", "customer_party_postalcode": "28001", "customer_party_address": "Calle Falsa 123, 3ºA", "customer_party_name": "Ejemplo S.L.", "tax_inclusive_amount": 217.8, "tax_amount": 37.8, "tax_amount_withheld": 27, "invoice_type_code": "F1", "currency": "EUR", "tax_report_lines": [ { "quantity": 1, "description": "Product A", "price": 100, "tax_inclusive_amount": 121, "tax_exclusive_amount": 100, "tax_amount": 21 }, { "quantity": 2, "description": "Product B", "price": 50, "tax_inclusive_amount": 121, "tax_exclusive_amount": 100, "tax_amount": 21 }, { "quantity": 1, "description": "Global Discount 10%", "price": -20, "tax_inclusive_amount": -24.2, "tax_exclusive_amount": -20, "tax_amount": -4.2 } ], "tax_breakdowns": [ { "category": "S", "non_exempt": true, "non_exemption_code": "S1", "percent": 21, "taxable_base": 180, "tax_amount": 37.8 } ] } }'Key points to consider:
- Line-level Tax Calculation: TicketBAI requires that all taxes are calculated on a per-line basis.
- Global Discounts and Charges as Lines: Global discounts must be treated as separate lines with a negative
priceandtax_inclusive_amount. - Withheld Taxes: Withheld taxes (e.g., IRPF) are reported at the document level using the
tax_amount_withheldfield.
Check the state of a Tax Report
Section titled “Check the state of a Tax Report”You must check the life cycle of the tax report. You have two options:
-
The recommended way is using the tax report web hook. Final states are: registered, error, annulled, and registered_with_errors
-
Polling the state of the tax report by calling the get tax report endpoint until the state field is any of: registered, error, annulled, and registered_with_errors.
Get the XML representation of the tax report
Section titled “Get the XML representation of the tax report”If you need the XML representation of the tax report, use the download tax report endpoint. B2Brouter also includes a field named xml_base64 in the GET response. Note that the XML can only be generated after the tax report has been chained (an asynchronous process).
Error checking
Section titled “Error checking”B2Brouter has implemented an exhaustive set of validations of TicketBAI tax reports based on the specifications published by the Basque Tax Authorities. If there is a validation error the tax report will not be created. You’ll receive a 422: Unprocessable Entity response with all errors in JSON format.
Cancel a Tax Report
Section titled “Cancel a Tax Report”To cancel a tax report use the DELETE verb calling the annullation endpoint. Check the evolution of the cancellation by either processing the tax report web hook or polling the get tax report endpoint until the state field of the tax report is error or annulled.
List Tax Reports
Section titled “List Tax Reports”To list your tax reports use the GET verb in list tax reports endpoint.
Equivalence between B2Brouter internal tax report fields and TicketBAI XML nodes
Section titled “Equivalence between B2Brouter internal tax report fields and TicketBAI XML nodes”| B2Brouter Field (based on PEPPOL CTC) | TicketBAI XML Node |
|---|---|
supplier_party_tax_id | Sujetos > Emisor > NIF |
supplier_party_name | Sujetos > Emisor > ApellidosNombreRazonSocial |
customer_party_tax_id (if customer_party_tax_scheme is nil) | Sujetos > Destinatarios > IDDestinatario > NIF |
customer_party_country | Sujetos > Destinatarios > IDDestinatario > IDOtro > CodigoPais |
customer_party_tax_scheme | Sujetos > Destinatarios > IDDestinatario > IDOtro > IDType |
customer_party_tax_id (if customer_party_tax_scheme is present) | Sujetos > Destinatarios > IDDestinatario > IDOtro > ID |
customer_party_name | Sujetos > Destinatarios > IDDestinatario > ApellidosNombreRazonSocial |
customer_party_postalcode | Sujetos > Destinatarios > IDDestinatario > CodigoPostal |
customer_party_address | Sujetos > Destinatarios > IDDestinatario > Direccion |
invoice_series_code | Factura > CabeceraFactura > SerieFactura |
invoice_number | Factura > CabeceraFactura > NumFactura |
invoice_date | Factura > CabeceraFactura > FechaExpedicionFactura |
ticket | Factura > CabeceraFactura > FacturaSimplificada |
is_amend | Factura > CabeceraFactura > FacturaRectificativa |
invoice_type_code (if is_amend) | Factura > CabeceraFactura > FacturaRectificativa > Codigo |
amended_series_code | Factura > CabeceraFactura > FacturasRectificadasSustituidas > IDFacturaRectificadaSustituida > SerieFactura |
amended_number | Factura > CabeceraFactura > FacturasRectificadasSustituidas > IDFacturaRectificadaSustituida > NumFactura |
amended_date | Factura > CabeceraFactura > FacturasRectificadasSustituidas > IDFacturaRectificadaSustituida > FechaExpedicionFactura |
tax_point_date | Factura > DatosFactura > FechaOperacion |
description | Factura > DatosFactura > DescripcionFactura |
tax_report_lines[].description | Factura > DatosFactura > DetallesFactura > IDDetalleFactura > DescripcionDetalle |
tax_report_lines[].quantity | Factura > DatosFactura > DetallesFactura > IDDetalleFactura > Cantidad |
tax_report_lines[].price | Factura > DatosFactura > DetallesFactura > IDDetalleFactura > ImporteUnitario |
tax_report_lines[].tax_inclusive_amount | Factura > DatosFactura > DetallesFactura > IDDetalleFactura > ImporteTotal |
tax_inclusive_amount | Factura > DatosFactura > ImporteTotalFactura |
tax_amount_withheld | Factura > DatosFactura > RetencionSoportada |
special_regime_key | Factura > DatosFactura > Claves > IDClave > ClaveRegimenIvaOpTrascendencia |
tax_breakdowns[].taxable_base (non-exempt) | Factura > TipoDesglose > DesgloseFactura > Sujeta > NoExenta > DetalleNoExenta > DesgloseIVA > DetalleIVA > BaseImponible |
tax_breakdowns[].percent | Factura > TipoDesglose > DesgloseFactura > Sujeta > NoExenta > DetalleNoExenta > DesgloseIVA > DetalleIVA > TipoImpositivo |
tax_breakdowns[].tax_amount | Factura > TipoDesglose > DesgloseFactura > Sujeta > NoExenta > DetalleNoExenta > DesgloseIVA > DetalleIVA > CuotaImpuesto |
XSD files
Section titled “XSD files”TicketBAI schemas provided by the Basque Tax Authorities.
| XSD file | Description |
|---|---|
| ticketbaiv1-2-2.xsd | Esquema principal de TicketBAI |
| anula_ticketbaiv1-2-2.xsd | Esquema para el suministro de facturas |
Code descriptions for specific fields
Section titled “Code descriptions for specific fields”Invoice type codes
Section titled “Invoice type codes”Param: invoice_type_code
| Code | Description |
|---|---|
| F1 | Factura (art. 6, 7.2 y 7.3 del RD 1619/2012) |
| F2 | Factura Simplificada y Facturas sin identificación del destinatario (art. 6.1.d) RD 1619/2012 |
| F3 | Factura emitida en sustitución de facturas simplificadas facturadas y declaradas |
| R1 | Factura Rectificativa (Error fundado en derecho y Art. 80 Uno Dos y Seis LIVA) |
| R2 | Factura Rectificativa (Art. 80.3) |
| R3 | Factura Rectificativa (Art. 80.4) |
| R4 | Factura Rectificativa (Resto) |
| R5 | Factura Rectificativa en facturas simplificadas |
Special regime key codes
Section titled “Special regime key codes”Param: special_regime_key
| Code | Description |
|---|---|
| 01 | Operación de régimen general. |
| 02 | Exportación. |
| 03 | Operaciones a las que se aplique el régimen especial de bienes usados, objetos de arte, antigüedades y objetos de colección. |
| 04 | Régimen especial del oro de inversión. |
| 05 | Régimen especial de las agencias de viajes. |
| 06 | Régimen especial grupo de entidades en IVA (Nivel Avanzado) |
| 07 | Régimen especial del criterio de caja. |
| 08 | Operaciones sujetas al IPSI / IGIC. |
| 51 | Operaciones en recargo de equivalencia. |
| 52 | Operaciones en régimen simplificado. |
Non exemption codes
Section titled “Non exemption codes”Param: non_exemption_code
| Code | Description |
|---|---|
| S1 | Operación Sujeta y No exenta - Sin inversión del sujeto pasivo. |
| S2 | Operación Sujeta y No exenta - Con Inversión del sujeto pasivo. |
Exemption codes
Section titled “Exemption codes”Param: exemption_code
| Code | Description |
|---|---|
| E1 | Exenta por el artículo 20 |
| E2 | Exenta por el artículo 21 |
| E3 | Exenta por el artículo 22 |
| E4 | Exenta por los artículos 23 y 24 |
| E5 | Exenta por el artículo 25 |
| E6 | Exenta por otros |
Non subject codes
Section titled “Non subject codes”Param: no_subject_code
| Code | Description |
|---|---|
| OT | No sujeto por el artículo 7 de la Norma Foral de IVA Otros supuestos de no sujeción. |
| RL | No sujeto por reglas de localización. |
| VT | No sujeto, ventas realizadas por cuenta de terceros. |
| IE | No sujeto en el TAI por reglas de localización, pero repercute impuesto extranjero. |
Customer ID Type
Section titled “Customer ID Type”Param: customer_party_tax_scheme
| Code | Description |
|---|---|
| 02 | EU VAT ID (NIF-IVA) |
| 03 | Passport |
| 04 | Official ID issued by country of residence |
| 05 | Certificate of residence |
| 06 | Other supporting document |