Skip to content
Log in

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.

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).

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:

Terminal window
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"
}
}'

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:

Terminal window
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.

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.

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:

Terminal window
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%:

Terminal window
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 price and tax_inclusive_amount.
  • Withheld Taxes: Withheld taxes (e.g., IRPF) are reported at the document level using the tax_amount_withheld field.

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).

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.

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.

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_idSujetos > Emisor > NIF
supplier_party_nameSujetos > Emisor > ApellidosNombreRazonSocial
customer_party_tax_id (if customer_party_tax_scheme is nil)Sujetos > Destinatarios > IDDestinatario > NIF
customer_party_countrySujetos > Destinatarios > IDDestinatario > IDOtro > CodigoPais
customer_party_tax_schemeSujetos > Destinatarios > IDDestinatario > IDOtro > IDType
customer_party_tax_id (if customer_party_tax_scheme is present)Sujetos > Destinatarios > IDDestinatario > IDOtro > ID
customer_party_nameSujetos > Destinatarios > IDDestinatario > ApellidosNombreRazonSocial
customer_party_postalcodeSujetos > Destinatarios > IDDestinatario > CodigoPostal
customer_party_addressSujetos > Destinatarios > IDDestinatario > Direccion
invoice_series_codeFactura > CabeceraFactura > SerieFactura
invoice_numberFactura > CabeceraFactura > NumFactura
invoice_dateFactura > CabeceraFactura > FechaExpedicionFactura
ticketFactura > CabeceraFactura > FacturaSimplificada
is_amendFactura > CabeceraFactura > FacturaRectificativa
invoice_type_code (if is_amend)Factura > CabeceraFactura > FacturaRectificativa > Codigo
amended_series_codeFactura > CabeceraFactura > FacturasRectificadasSustituidas > IDFacturaRectificadaSustituida > SerieFactura
amended_numberFactura > CabeceraFactura > FacturasRectificadasSustituidas > IDFacturaRectificadaSustituida > NumFactura
amended_dateFactura > CabeceraFactura > FacturasRectificadasSustituidas > IDFacturaRectificadaSustituida > FechaExpedicionFactura
tax_point_dateFactura > DatosFactura > FechaOperacion
descriptionFactura > DatosFactura > DescripcionFactura
tax_report_lines[].descriptionFactura > DatosFactura > DetallesFactura > IDDetalleFactura > DescripcionDetalle
tax_report_lines[].quantityFactura > DatosFactura > DetallesFactura > IDDetalleFactura > Cantidad
tax_report_lines[].priceFactura > DatosFactura > DetallesFactura > IDDetalleFactura > ImporteUnitario
tax_report_lines[].tax_inclusive_amountFactura > DatosFactura > DetallesFactura > IDDetalleFactura > ImporteTotal
tax_inclusive_amountFactura > DatosFactura > ImporteTotalFactura
tax_amount_withheldFactura > DatosFactura > RetencionSoportada
special_regime_keyFactura > DatosFactura > Claves > IDClave > ClaveRegimenIvaOpTrascendencia
tax_breakdowns[].taxable_base (non-exempt)Factura > TipoDesglose > DesgloseFactura > Sujeta > NoExenta > DetalleNoExenta > DesgloseIVA > DetalleIVA > BaseImponible
tax_breakdowns[].percentFactura > TipoDesglose > DesgloseFactura > Sujeta > NoExenta > DetalleNoExenta > DesgloseIVA > DetalleIVA > TipoImpositivo
tax_breakdowns[].tax_amountFactura > TipoDesglose > DesgloseFactura > Sujeta > NoExenta > DetalleNoExenta > DesgloseIVA > DetalleIVA > CuotaImpuesto

TicketBAI schemas provided by the Basque Tax Authorities.

XSD fileDescription
ticketbaiv1-2-2.xsdEsquema principal de TicketBAI
anula_ticketbaiv1-2-2.xsdEsquema para el suministro de facturas

Param: invoice_type_code

CodeDescription
F1Factura (art. 6, 7.2 y 7.3 del RD 1619/2012)
F2Factura Simplificada y Facturas sin identificación del destinatario (art. 6.1.d) RD 1619/2012
F3Factura emitida en sustitución de facturas simplificadas facturadas y declaradas
R1Factura Rectificativa (Error fundado en derecho y Art. 80 Uno Dos y Seis LIVA)
R2Factura Rectificativa (Art. 80.3)
R3Factura Rectificativa (Art. 80.4)
R4Factura Rectificativa (Resto)
R5Factura Rectificativa en facturas simplificadas

Param: special_regime_key

CodeDescription
01Operación de régimen general.
02Exportación.
03Operaciones a las que se aplique el régimen especial de bienes usados, objetos de arte, antigüedades y objetos de colección.
04Régimen especial del oro de inversión.
05Régimen especial de las agencias de viajes.
06Régimen especial grupo de entidades en IVA (Nivel Avanzado)
07Régimen especial del criterio de caja.
08Operaciones sujetas al IPSI / IGIC.
51Operaciones en recargo de equivalencia.
52Operaciones en régimen simplificado.

Param: non_exemption_code

CodeDescription
S1Operación Sujeta y No exenta - Sin inversión del sujeto pasivo.
S2Operación Sujeta y No exenta - Con Inversión del sujeto pasivo.

Param: exemption_code

CodeDescription
E1Exenta por el artículo 20
E2Exenta por el artículo 21
E3Exenta por el artículo 22
E4Exenta por los artículos 23 y 24
E5Exenta por el artículo 25
E6Exenta por otros

Param: no_subject_code

CodeDescription
OTNo sujeto por el artículo 7 de la Norma Foral de IVA Otros supuestos de no sujeción.
RLNo sujeto por reglas de localización.
VTNo sujeto, ventas realizadas por cuenta de terceros.
IENo sujeto en el TAI por reglas de localización, pero repercute impuesto extranjero.

Param: customer_party_tax_scheme

CodeDescription
02EU VAT ID (NIF-IVA)
03Passport
04Official ID issued by country of residence
05Certificate of residence
06Other supporting document