Spain Quickstart
This quickstart walks you through signing your first compliant invoice with fiskaly SIGN ES, covering both TicketBAI (Basque Country) and Verifactu (rest of Spain).
Before you start
Section titled “Before you start”SIGN ES is fiskaly’s software-only, platform-independent API for Spanish fiscal compliance. It covers Verifactu and SII across most of Spain, TicketBAI in the Basque Country, and NaTicket in Navarre (upcoming) — all through one REST API.
Verifactu
National regulation for most of Spain under the Anti-Fraud Law, Royal Decree 1007/2023, and Order HAC/1177/2024.
TicketBAI
Basque Country fiscalization framework for Araba, Bizkaia, and Gipuzkoa.
SII
Electronic VAT reporting to AEAT for large taxpayers in mainland Spain. Mutually exclusive with Verifactu.
NaTicket
Navarra's upcoming invoice-control system, announced September 2025. Expected from 2026–2027.
Real-time API flow
SIGN ES generates invoice XML, signs it, chains it, and transmits it to the relevant tax authority.
| Regulation | Applies to | Key output |
|---|---|---|
| Verifactu | Most of Spain, excluding Basque Country and currently Navarre | Signed XML, real-time AEAT transmission, Verifactu phrase and QR code |
| TicketBAI | Araba, Bizkaia, Gipuzkoa | Signed XML, regional tax authority transmission, TicketBAI ID and QR code |
| SII | Mainland Spain (large VAT taxpayers) | Electronic VAT records submitted to AEAT within 4 days; no QR code |
| NaTicket | Navarre (upcoming, ~2026–2027) | Planned: signed XML transmitted to Hacienda Foral de Navarra |
See the Introduction for the full Verifactu, TicketBAI, BATUZ, and LROE background before starting the integration steps.
Prerequisites
Section titled “Prerequisites”- A fiskaly account with a Spanish organization (sign up at hub.fiskaly.com)
- An API Key and Secret generated in the TEST environment
- Taxpayer information: legal name, NIF (tax number), and territory
Your API Secret is shown only once. Store it immediately in a secure location.
Authenticate
curl -X POST https://test.es.sign.fiskaly.com/api/v1/auth \ -H "Content-Type: application/json" \ -d '{ "content": { "api_key": "YOUR_API_KEY", "api_secret": "YOUR_API_SECRET" } }'const BASE = "https://test.es.sign.fiskaly.com/api/v1"; const response = await fetch(`${BASE}/auth`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content: { api_key: "YOUR_API_KEY", api_secret: "YOUR_API_SECRET", }, }), }); const { access_token } = await response.json(); const headers = { "Authorization": `Bearer ${access_token}`, "Content-Type": "application/json", };import requests, uuid BASE = "https://test.es.sign.fiskaly.com/api/v1" resp = requests.post(f"{BASE}/auth", json={ "content": {"api_key": "YOUR_API_KEY", "api_secret": "YOUR_API_SECRET"} }) access_token = resp.json()["access_token"] hdrs = {"Authorization": f"Bearer {access_token}"}// POST https://test.es.sign.fiskaly.com/api/v1/auth // Body: {"content":{"api_key":"...","api_secret":"..."}} // Response: { "access_token": "..." }using var client = new HttpClient(); var authResp = await client.PostAsJsonAsync( "https://test.es.sign.fiskaly.com/api/v1/auth", new { content = new { api_key = "YOUR_API_KEY", api_secret = "YOUR_API_SECRET" } }); var token = (await authResp.Content.ReadFromJsonAsync<JsonElement>()) .GetProperty("access_token").GetString();📘NoteSIGN ES wraps all request bodies in a
contentenvelope. This applies to authentication and all subsequent API calls.Create a Taxpayer
Register the taxpayer information. The
territorydetermines which regulation applies:ARABA,BIZKAIA,GIPUZKOA→ TicketBAISPAIN_OTHER,CANARY_ISLANDS,CEUTA,MELILLA→ Verifactu
curl -X PUT "https://test.es.sign.fiskaly.com/api/v1/taxpayer" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "content": { "issuer": { "tax_number": "B12345678", "legal_name": "My Company S.L." }, "territory": "SPAIN_OTHER", "sii": { "state": "ENABLED" } } }'await fetch(`${BASE}/taxpayer`, { method: "PUT", headers, body: JSON.stringify({ content: { issuer: { tax_number: "B12345678", legal_name: "My Company S.L.", }, territory: "SPAIN_OTHER", sii: { state: "ENABLED", }, }, }), });requests.put(f"{BASE}/taxpayer", headers=hdrs, json={ "content": { "issuer": {"tax_number": "B12345678", "legal_name": "My Company S.L."}, "territory": "SPAIN_OTHER", "sii": {"state": "ENABLED"}, } })// PUT /taxpayer // Body: {"content":{"issuer":{"tax_number":"B12345678","legal_name":"My Company S.L."},"territory":"SPAIN_OTHER","sii":{"state":"ENABLED"}}}await client.PutAsJsonAsync($"{BASE}/taxpayer", new { content = new { issuer = new { tax_number = "B12345678", legal_name = "My Company S.L." }, territory = "SPAIN_OTHER", sii = new { state = "ENABLED", }, } });Create a Signer
The signer handles the electronic signature of invoices. A certificate is automatically allocated based on the territory.
SIGNER_ID=$(uuidgen) curl -X PUT "https://test.es.sign.fiskaly.com/api/v1/signers/${SIGNER_ID}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"content": {}}'const signerId = crypto.randomUUID(); await fetch(`${BASE}/signers/${signerId}`, { method: "PUT", headers, body: JSON.stringify({ content: {} }), });signer_id = str(uuid.uuid4()) requests.put(f"{BASE}/signers/{signer_id}", headers=hdrs, json={"content": {}})// PUT /signers/{signerId} // Body: {"content":{}}var signerId = Guid.NewGuid().ToString(); await client.PutAsJsonAsync($"{BASE}/signers/{signerId}", new { content = new {} });Create a Client
CLIENT_ID=$(uuidgen) curl -X PUT "https://test.es.sign.fiskaly.com/api/v1/clients/${CLIENT_ID}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"content": {"signer_id": "'${SIGNER_ID}'"}}'const clientId = crypto.randomUUID(); await fetch(`${BASE}/clients/${clientId}`, { method: "PUT", headers, body: JSON.stringify({ content: { signer_id: signerId } }), });client_id = str(uuid.uuid4()) requests.put(f"{BASE}/clients/{client_id}", headers=hdrs, json={"content": {"signer_id": signer_id}})// PUT /clients/{clientId} // Body: {"content":{"signer_id":"<signerId>"}}var clientId = Guid.NewGuid().ToString(); await client.PutAsJsonAsync($"{BASE}/clients/{clientId}", new { content = new { signer_id = signerId } });Create your first invoice
INVOICE_ID=$(uuidgen) curl -X PUT "https://test.es.sign.fiskaly.com/api/v1/clients/${CLIENT_ID}/invoices/${INVOICE_ID}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "content": { "type": "SIMPLIFIED", "number": "INV-001", "text": "Sales receipt", "full_amount": "12.10", "items": [ { "text": "Product A", "quantity": "1", "unit_amount": "10.00", "full_amount": "12.10", "system": { "type": "REGULAR", "rate": "21.00" } } ] } }'const invoiceId = crypto.randomUUID(); const invoice = await fetch( `${BASE}/clients/${clientId}/invoices/${invoiceId}`, { method: "PUT", headers, body: JSON.stringify({ content: { type: "SIMPLIFIED", number: "INV-001", text: "Sales receipt", full_amount: "12.10", items: [ { text: "Product A", quantity: "1", unit_amount: "10.00", full_amount: "12.10", system: { type: "REGULAR", rate: "21.00" }, }, ], }, }), } ).then(r => r.json()); console.log("Signed invoice:", invoice);invoice_id = str(uuid.uuid4()) invoice = requests.put( f"{BASE}/clients/{client_id}/invoices/{invoice_id}", headers=hdrs, json={ "content": { "type": "SIMPLIFIED", "number": "INV-001", "text": "Sales receipt", "full_amount": "12.10", "items": [{ "text": "Product A", "quantity": "1", "unit_amount": "10.00", "full_amount": "12.10", "system": {"type": "REGULAR", "rate": "21.00"}, }], } }, ).json() print("Signed:", invoice)// PUT /clients/{clientId}/invoices/{invoiceId} // Body: {"content":{"type":"SIMPLIFIED","number":"INV-001",...}} // Response contains the signed, compliant invoice datavar invoiceId = Guid.NewGuid().ToString(); var inv = await client.PutAsJsonAsync( $"{BASE}/clients/{clientId}/invoices/{invoiceId}", new { content = new { type = "SIMPLIFIED", number = "INV-001", text = "Sales receipt", full_amount = "12.10", items = new[] { new { text = "Product A", quantity = "1", unit_amount = "10.00", full_amount = "12.10", system = new { type = "REGULAR", rate = "21.00" }, }}, }});The response contains the signed, compliant invoice data including all information required by TicketBAI or Verifactu regulations.
Run the Script
Section titled “Run the Script”Want to run through all the steps automatically? Download and run our quickstart script:
# Download and run
curl -O https://workspace.fiskaly.com/scripts/sign-es-quickstart.sh
export API_KEY="your_api_key"
export API_SECRET="your_api_secret"
bash sign-es-quickstart.sh# Download and run
curl -O https://workspace.fiskaly.com/scripts/sign-es-quickstart.mjs
API_KEY="your_key" API_SECRET="your_secret" node sign-es-quickstart.mjsNext Steps
Section titled “Next Steps”Was this page helpful?