Skip to main content

Spain Fiscalization Guide

What is Spanish fiscalization?

Spain requires businesses to electronically report every invoice to the government. Think of it as a digital receipt that gets sent to the tax authority in real-time (or near real-time), so they can verify that businesses are accurately reporting their sales and collecting the right amount of VAT (Value Added Tax -- Europe's sales tax).

The twist: Spain doesn't have just one system. Depending on where your merchant operates and how big they are, one of three different systems applies. FiscalAPI handles all three -- you just set the right system in your location config, and the adapter takes care of the rest.

The three systems: TicketBAI, Verifactu, and SII

Here's a simple decision tree:

  1. Is the merchant in the Basque Country? (three provinces in northern Spain with their own tax rules) → Use TicketBAI
  2. Does the merchant have annual revenue over EUR 6 million? → Use SII (Suministro Inmediato de Información -- "Immediate Supply of Information")
  3. Everyone else in Spain → Use Verifactu

TicketBAI -- Basque Country invoice reporting

What it is: A regional fiscalization system used in three provinces (Gipuzkoa, Araba, and Bizkaia) in Spain's Basque Country. These provinces have their own tax authorities, separate from the national one.

What problem it solves: The Basque tax authorities want to see every invoice as it's created, so they can detect unreported sales in real-time.

How it works: When your POS submits a sale, FiscalAPI generates an XML document, digitally signs it (using a cryptographic standard called XAdES-EPES), and sends it to the regional tax authority. Each invoice references the previous one's hash, creating a tamper-evident chain -- if someone tries to delete or modify an invoice, the chain breaks and the authorities will notice.

Example: When your POS submits a EUR 50 sale at a store in San Sebastián (Gipuzkoa), FiscalAPI builds the TicketBAI XML, signs it with the merchant's certificate, and POSTs it to Gipuzkoa's tax authority. Within milliseconds, the authority confirms receipt and FiscalAPI returns a TBAI ID and QR code URL in the transaction response.

You don't need to worry about this

FiscalAPI handles XML generation, digital signing, invoice chaining, and submission automatically. You just create a transaction -- the adapter does the rest.

Verifactu -- nationwide invoice verification

What it is: Spain's national invoice verification system for most businesses. Run by the AEAT (Agencia Estatal de Administración Tributaria -- Spain's equivalent of the IRS).

What problem it solves: Same goal as TicketBAI but for the rest of Spain. Every invoice gets reported to the national tax authority with a hash chain guaranteeing integrity.

How it works: When your POS submits a sale, FiscalAPI creates a record with a SHA-256 hash that chains to the previous invoice. This record is submitted to AEAT via a SOAP web service. No digital certificate signing on the document itself -- the integrity comes from the hash chain instead.

Example: When your POS submits a EUR 200 sale at a store in Barcelona, FiscalAPI generates the Verifactu record, computes the chain hash, and submits it to AEAT. The transaction completes with a fiscal ID confirming the record was accepted.

You don't need to worry about this

FiscalAPI manages the hash chain automatically. You never need to track which invoice came before which -- the adapter maintains chain state per merchant.

SII -- real-time reporting for large businesses

What it is: A separate AEAT system for large companies (annual revenue >= EUR 6,336,000). Instead of reporting in real-time, businesses get a four-day window to submit each invoice.

What problem it solves: Large companies often have complex invoicing workflows. SII gives them a short grace period while still requiring near-real-time reporting to AEAT.

How it works: FiscalAPI builds a SOAP envelope with the invoice details and submits it to AEAT using mutual TLS (the merchant's digital certificate authenticates the connection). AEAT responds with a verification code (CSV -- Código Seguro de Verificación).

Example: When your merchant's ERP system records a EUR 10,000 consulting invoice to a client, FiscalAPI builds the SII SOAP message, submits it to AEAT over mutual TLS, and returns the CSV verification code. The merchant has up to 4 calendar days from the invoice date to submit -- but since FiscalAPI submits in real-time when you create the transaction, you're covered as long as the transaction is created within the window.

You don't need to worry about this

FiscalAPI handles SOAP envelope construction, mutual TLS certificate management, and AEAT communication automatically. You just create transactions -- the adapter does the rest.

Who must use SII?

SII is mandatory for:

  • Companies with annual turnover exceeding EUR 6,336,000
  • Businesses enrolled in the monthly VAT refund scheme (REDEME -- Registro de Devolución Mensual)
  • Groups filing consolidated VAT returns (grupos de IVA -- when multiple related companies file as one)
  • Businesses that voluntarily opt in (some companies choose SII for the faster VAT refunds it enables)

Quick comparison

FeatureTicketBAIVerifactuSII
Who uses itMerchants in the Basque CountryMost businesses in SpainLarge businesses (>= EUR 6M revenue)
WhereGipuzkoa, Araba, Bizkaia onlyNationwideNationwide
AuthorityRegional Basque tax officesAEAT (national tax agency)AEAT (national tax agency)
Submission timingReal-timeReal-timeWithin 4 calendar days
How invoices are securedDigital signature (XAdES-EPES) + hash chainSHA-256 hash chainMutual TLS (certificate on the connection)
Invoice chainingYesYesNo (each invoice is independent)
StatusActive (mandatory in Basque Country)Rolling outActive

Country config fields

The country_config object for Spanish locations tells FiscalAPI which system to use and provides the required credentials.

Common fields (all systems)

FieldTypeRequiredDescription
systemstringYesWhich system to use: ticketbai, verifactu, or sii
legal_namestringYesThe merchant's registered company name

Note: The merchant's Spanish tax ID (NIF or CIF -- a unique business identifier, similar to an EIN in the US) goes in the location's tax_id field, not in country_config.

| certificate_id | string | Yes | Reference to an uploaded certificate used for signing or TLS | | tsa_url | string | No | Timestamp authority URL for XAdES-T timestamps (optional, for TicketBAI) |

TicketBAI-specific fields

FieldTypeRequiredDescription
territorystringYesWhich Basque province: gipuzkoa, araba, or bizkaia
tbai_licensestringYesThe TicketBAI software license code (issued by the regional authority)

Verifactu-specific fields

FieldTypeRequiredDescription
software_nifstringYesTax ID of the software developer (your company's NIF if you built the POS)
installation_numberstringNoIdentifies this specific software installation

SII-specific fields

SII only needs the common fields (legal_name and certificate_id). The merchant's NIF goes in the location's tax_id.

FieldTypeRequiredDescription
legal_namestringYesThe merchant's full legal name (razón social)
certificate_idstringYesReference to an uploaded certificate for mutual TLS with AEAT

Configuration examples

TicketBAI location (Gipuzkoa)

curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Donostia Store",
"country": "ES",
"address": "Calle Mayor 1, 20003 San Sebastián",
"tax_id": "B12345674",
"country_config": {
"system": "ticketbai",
"territory": "gipuzkoa",
"legal_name": "Acme Corp SL",
"certificate_id": "spain-prod-2026",
"tbai_license": "TBAILIC123456"
}
}'

TicketBAI location (Bizkaia)

Bizkaia works differently from the other Basque provinces. Instead of submitting each invoice in real-time, invoices are queued and submitted in a daily batch (called LROE) at 2 AM UTC. Your transaction will show a pending_batch status until the batch runs.

curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Bilbao Store",
"country": "ES",
"address": "Gran Vía de Don Diego López de Haro 1, 48001 Bilbao",
"tax_id": "B12345674",
"country_config": {
"system": "ticketbai",
"territory": "bizkaia",
"legal_name": "Acme Corp SL",
"certificate_id": "spain-prod-2026",
"tbai_license": "TBAILIC123456"
}
}'

Verifactu location

curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Barcelona Store",
"country": "ES",
"address": "Passeig de Gracia 1, 08007 Barcelona",
"tax_id": "B87654321",
"country_config": {
"system": "verifactu",
"legal_name": "Acme Corp SL",
"certificate_id": "spain-prod-2026",
"software_nif": "B99999999",
"installation_number": "001"
}
}'

SII location

curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Madrid HQ",
"country": "ES",
"address": "Calle de Alcalá 50, 28014 Madrid",
"tax_id": "A12345674",
"country_config": {
"system": "sii",
"legal_name": "Gran Empresa SL",
"certificate_id": "spain-sii-prod-2026"
}
}'

Submission flows

What happens when your POS creates a sale

The flow varies by system, but from your perspective it's the same API call -- POST /v1/transactions. FiscalAPI routes to the right system based on the location's country_config.system.

TicketBAI (Gipuzkoa / Araba) -- real-time submission

  1. Your app creates a transaction via POST /v1/transactions
  2. FiscalAPI generates TicketBAI XML and chains it to the previous invoice (per merchant tax ID)
  3. The XML is digitally signed using the merchant's certificate
  4. The signed XML is sent to the regional tax authority in real-time
  5. On success, you get back a fiscal_id (TBAI ID) and a QR code URL
  6. Transaction status: success

TicketBAI (Bizkaia) -- batch submission (LROE)

  1. Your app creates a transaction via POST /v1/transactions
  2. FiscalAPI generates and signs the XML (same as above)
  3. Transaction status: pending_batch (queued for batch submission)
  4. At 2 AM UTC, the LROE batch processor collects all pending transactions and submits them
  5. On acceptance, transaction status transitions to success

You can monitor batch processing via the batch stats endpoint.

Verifactu -- real-time submission

  1. Your app creates a transaction via POST /v1/transactions
  2. FiscalAPI generates the record and computes the SHA-256 chain hash
  3. The record is submitted to AEAT via SOAP
  4. On success, you get back a fiscal_id and transaction status: success

SII -- real-time submission with 4-day window

  1. Your app creates a transaction via POST /v1/transactions
  2. FiscalAPI builds a SOAP envelope with the invoice record
  3. The envelope is submitted to AEAT via mutual TLS
  4. AEAT responds with a status (Correcto, Incorrecto, or ParcialmenteCorrecto)
  5. On success, fiscal_id is set to the CSV (verification code) and transaction status: success

SII cancellations

Voiding a transaction submits a cancellation record to AEAT. This tells the tax authority that a previously reported invoice has been annulled.

SII received invoices

To submit a received invoice (recording a purchase), set "book_type": "received" in the transaction metadata along with the supplier's details.

SII book types (Libros Registro)

SII organizes invoice data into four "books" (think of them as categories or ledgers):

BookWhat it containsWhen to use
Issued invoices (Facturas Expedidas)Invoices your merchant sends to their customersEvery sale your merchant makes
Received invoices (Facturas Recibidas)Invoices your merchant receives from suppliersRecording purchase invoices
Investment goods (Bienes de Inversión)Capital asset purchasesBuying equipment, vehicles, etc.
Intra-community operations (Operaciones Intracomunitarias)Cross-border transactions within the EUSelling to or buying from another EU country

FiscalAPI currently supports issued invoices and received invoices, which cover the vast majority of SII submissions.

SII transaction metadata

SII transactions accept optional metadata fields. All fields have sensible defaults -- you only need to provide them when you want to override the defaults.

Core metadata fields

FieldTypeDefaultDescription
invoice_numberstringTransaction IDThe invoice number as it appears on the document
seriesstring--Optional series prefix (prepended to invoice number)
issue_datestringCurrent dateInvoice date in DD-MM-YYYY format
descriptionstringAuto-generatedA brief description of what the invoice is for
regime_keystring"01" (general)Tax regime code (see regime keys table below)
exercisestringCurrent yearFiscal year (e.g., "2026")
periodstringCurrent monthTax period: "01" through "12", or "0A" for annual
simplifiedbooleanfalseSet to true for simplified invoices (small transactions)
book_typestring"issued""issued" for sales, "received" for purchases

Counterparty fields (the other party on the invoice)

Required for standard invoices. Not needed for simplified invoices.

FieldTypeDescription
counterparty_nifstringThe other party's Spanish tax ID (for domestic transactions)
counterparty_namestringThe other party's legal name
counterparty_id_typestringFor foreign parties: "02" = EU VAT number, "04" = passport
counterparty_countrystringCountry code (ISO 3166-1 alpha-2)
counterparty_idstringThe foreign party's identifier value

Credit note fields (when correcting a previous invoice)

FieldTypeDescription
original_invoice_numberstringNumber of the invoice being corrected
original_issue_datestringDate of the original invoice (DD-MM-YYYY)

Example: standard issued invoice

Scenario: Your merchant issues a EUR 1,210 consulting invoice to another Spanish company.

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"location_id": "loc_madrid_hq",
"type": "sale",
"amount": 121000,
"currency": "EUR",
"items": [
{
"description": "Consulting services",
"quantity": 1,
"unit_price": 100000,
"total_amount": 121000,
"tax_rate": 2100,
"tax_amount": 21000
}
],
"metadata": {
"invoice_number": "FA-2026/001",
"series": "A-",
"issue_date": "13-03-2026",
"description": "Consulting services Q1 2026",
"regime_key": "01",
"counterparty_nif": "B99999999",
"counterparty_name": "Acme Consulting SL"
}
}'

Example: credit note (correcting a previous invoice)

Scenario: Your merchant needs to issue a full credit note to reverse a previous invoice.

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"location_id": "loc_madrid_hq",
"type": "refund",
"amount": -121000,
"currency": "EUR",
"items": [
{
"description": "Consulting services - correction",
"quantity": 1,
"unit_price": -100000,
"total_amount": -121000,
"tax_rate": 2100,
"tax_amount": -21000
}
],
"metadata": {
"invoice_number": "RA-2026/001",
"issue_date": "13-03-2026",
"description": "Rectificación FA-2026/001",
"counterparty_nif": "B99999999",
"counterparty_name": "Acme Consulting SL",
"original_invoice_number": "A-FA-2026/001",
"original_issue_date": "10-03-2026"
}
}'

SII invoice types

CodeNameWhen to use
F1Factura (Standard invoice)Normal invoices -- this is the default for sale transactions
F2Factura simplificada (Simplified invoice)Small transactions -- set "simplified": true in metadata
R1Factura rectificativa (Credit note)Correcting a previous invoice -- automatic for refund transactions

SII regime keys

The regime_key metadata field tells AEAT what tax regime applies to this invoice. Common values:

KeyWhat it meansWhen to use
01General regimeDefault -- use this for standard domestic transactions
02ExportSales to non-EU countries
03Special regime for used goodsSecond-hand goods (margin scheme)
05Travel agenciesTravel agency services
07Special cash accounting regimeBusinesses on cash-basis accounting
09Intra-community acquisitionsPurchases from other EU countries

SII AEAT endpoints

SII communicates with AEAT via SOAP 1.1 over mutual TLS.

Production

BookEndpoint
Issued invoiceshttps://www1.agenciatributaria.gob.es/wlpl/SSII-FACT/ws/fe/SiiFactFEV2SOAP
Received invoiceshttps://www1.agenciatributaria.gob.es/wlpl/SSII-FACT/ws/fr/SiiFactFRV2SOAP

Sandbox

BookEndpoint
Issued invoiceshttps://prewww1.aeat.es/wlpl/SSII-FACT/ws/fe/SiiFactFEV2SOAP
Received invoiceshttps://prewww1.aeat.es/wlpl/SSII-FACT/ws/fr/SiiFactFRV2SOAP

Sandbox routing is automatic when using a test API key (zyn_test_...). Test transactions hit sandbox endpoints without affecting real tax records.

SII submission deadline

Invoices must be reported to SII within four calendar days of the invoice issue date (or the accounting date for received invoices). Sundays and national holidays do not count toward this deadline.

FiscalAPI submits invoices to AEAT in real-time when you create the transaction, so you meet the deadline as long as the transaction is created within the four-day window.

SII error handling

AEAT responds with one of three statuses:

AEAT statusWhat it meansYour transaction status
CorrectoInvoice acceptedsuccess
ParcialmenteCorrectoSome records accepted, some rejected (batch submissions)partial
IncorrectoInvoice rejectedfailed

Common error codes:

CodeWhat went wrongWhat to do
1106Invalid NIF (tax ID format error)Check the merchant's tax_id and counterparty NIF
2000Duplicate invoice (already registered with AEAT)The invoice was already submitted -- no action needed
3000XML schema validation errorCheck transaction data for missing/invalid fields

SII certificate setup

SII requires a qualified electronic certificate for mutual TLS authentication with AEAT. This is how AEAT verifies the identity of the submitting business. Upload the certificate through the certificates API:

curl -X POST https://api.zyntem.dev/v1/certificates \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-F "name=spain-sii-prod-2026" \
-F "type=pkcs12" \
-F "file=@certificate.p12" \
-F "password=cert-password"

Then reference the certificate ID in your location's country_config.certificate_id.

You don't need to worry about this

FiscalAPI handles the mutual TLS connection setup using the uploaded certificate. You don't need to manage TLS handshakes, certificate chains, or SOAP envelope construction.

Invoice chaining

You don't need to worry about this

Both TicketBAI and Verifactu create a tamper-evident chain where each invoice references the hash of the previous one (for the same merchant tax ID). This is how the government detects deleted or modified invoices. FiscalAPI manages the chain automatically -- you never need to track chain state.

Switching systems

If a merchant moves to a different region or crosses the revenue threshold for SII, you can change their fiscalization system by updating the location:

curl -X PATCH https://api.zyntem.dev/v1/locations/{id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"country_config": {
"system": "verifactu",
"legal_name": "Acme Corp SL",
"certificate_id": "spain-prod-2026",
"software_nif": "B99999999"
}
}'

Validation rules

FiscalAPI validates your configuration when you create or update a Spanish location:

  1. country_config is required -- Spain locations must include a country_config with a system field
  2. Valid systems -- system must be ticketbai, verifactu, or sii
  3. Territory -- Required for TicketBAI; must be gipuzkoa, araba, or bizkaia
  4. NIF -- The merchant's tax ID (provided in the location's tax_id field)
  5. Legal name -- Required for all systems
  6. Certificate -- A valid signing certificate must be uploaded and referenced by certificate_id

Error examples

Missing country config:

{
"error": "Spain locations require country_config with system field (ticketbai, verifactu, or sii)"
}

Invalid system:

{
"error": "system must be \"ticketbai\", \"verifactu\", or \"sii\", got \"invalid\""
}

Territories

TerritorySystemHow invoices are submitted
GipuzkoaTicketBAIReal-time XML POST to regional authority
ArabaTicketBAIReal-time XML POST to regional authority
BizkaiaTicketBAIDaily batch (LROE) at 2 AM UTC
Rest of Spain (small/medium business)VerifactuReal-time SOAP to AEAT
Rest of Spain (large business >= EUR 6M)SIISOAP to AEAT within 4 days