Skip to content

Spain (VeriFactu)

VeriFactu support in Space Invoices is limited to Spanish entities in the ordinary territory that do not use SII. Basque Country, Navarra, and SII are outside this release. Confirm the taxpayer’s obligations with a Spanish adviser.

Current scope and dates

The official deadlines are 1 January 2027 for corporate income-tax taxpayers and 1 July 2027 for other taxpayers. These dates come from the AEAT deadline notice and can change; check the official source before a production launch.

Space Invoices currently keeps production issuance disabled until the product has completed its live conformance and signed-declaration release gates. Sandbox/test use does not represent production approval.

Onboard an entity

Create an entity with country_code: "ES", then confirm the territory and SII status:

await sdk.veriFactu.updateSettings({
territory: "ordinary",
sii: false,
enabled: false,
}, { entity_id: entity.id });

Use sdk.veriFactu.getSettings({ entity_id: entity.id }) to read eligibility, has_certificate, environment, software_version, and the current configuration. Enabling requires an AEAT client certificate, ordinary territory, sii: false, and the applicable release gates.

Upload a P12/PFX certificate with sdk.veriFactu.uploadCertificate({ file, passphrase }, { entity_id: entity.id }). The certificate belongs to the qualified taxpayer or an authorised representative; see AEAT certificate guidance. Space Invoices does not provide a public auto-generated test certificate, and a sandbox certificate is not evidence of production authority.

The software identity recorded by this release is configured with VERIFACTU_PRODUCER_NAME and VERIFACTU_PRODUCER_NIF, or VERIFACTU_PRODUCER_COUNTRY and VERIFACTU_PRODUCER_VAT_ID. It records system name Space Invoices, system ID SI, version 1.0.0, and a per-entity installation identifier. The multiple-billing indicator reflects the account’s installations in that environment, including inactive installations. Configure the producer’s actual legal identity; do not copy an example value into production.

Pilot setup

For an AEAT preproduction pilot, create a separate sandbox entity with the actual taxpayer’s legal name and Spanish NIF, country: "Spain", country_code: "ES", currency_code: "EUR", and locale: "es-ES". Select the sandbox environment explicitly in the existing app sandbox selector; omitting environment defaults to live and the entity environment cannot be changed later. Supply the account ID when creating the entity. The pilot entity must be separate from every live entity.

Choose ordinary territory (AEAT) and leave SII reporting off. The certificate must be the accepted taxpayer or authorised-representative P12/PFX certificate for that entity. Document the taxpayer’s consent and the representative’s authority before uploading it. Upload the existing certificate through the Space Invoices UI over the secured settings flow; never send a certificate or passphrase through chat or email. A provider-owned certificate is not needed for this pilot; use the taxpayer’s or representative’s own certificate.

In the app, open Settings → VeriFactu, then complete Eligibility, Certificate, and Enable VeriFactu. A different third-party identity needs the appropriate representation arrangement (for example, AEAT apoderamiento or social collaboration); see the AEAT VeriFactu FAQ.

For a backend integration, supply a sandbox account API key with permission to create the sandbox entity and manage its settings. Pass the certificate and passphrase from your secure credential input:

import SpaceInvoices from "@spaceinvoices/js-sdk/sdk";
async function setupVerifactuPilot(input: {
apiKey: string;
accountId: string;
legalName: string;
nif: string;
file: File;
passphrase: string;
}) {
const sdk = new SpaceInvoices({ accessToken: input.apiKey, accountId: input.accountId });
const entity = await sdk.entities.create({
name: input.legalName,
tax_number: input.nif,
country: "Spain",
country_code: "ES",
currency_code: "EUR",
locale: "es-ES",
environment: "sandbox",
}, { account_id: input.accountId });
const settings = await sdk.veriFactu.getSettings({ entity_id: entity.id });
if (settings.environment !== "test") throw new Error("Refusing to change or issue from a non-test entity");
await sdk.veriFactu.updateSettings(
{ territory: "ordinary", sii: false, enabled: false },
{ entity_id: entity.id },
);
await sdk.veriFactu.uploadCertificate(
{ file: input.file, passphrase: input.passphrase },
{ entity_id: entity.id },
);
await sdk.veriFactu.updateSettings(
{ territory: "ordinary", sii: false, enabled: true },
{ entity_id: entity.id },
);
const readback = await sdk.veriFactu.getSettings({ entity_id: entity.id });
if (readback.environment !== "test" || readback.enabled !== true || readback.eligibility !== "eligible" || !readback.has_certificate) {
throw new Error("Pilot settings did not reach the expected test state");
}
return { entityId: entity.id, settings: readback };
}

Run this once for a new pilot entity; retain its ID and resume that entity for repeat checks so you do not create duplicate installations. If setup fails after entity creation, find that sandbox entity in the app and continue its setup. Keep the environment === "test" guard before enabling, uploading, or calling issuance.

Before the first invoice, configure the producer variables listed above with the actual software producer identity; never replace it with the pilot taxpayer’s identity. For a Slovenian producer using the foreign-identity option, set VERIFACTU_PRODUCER_COUNTRY to SI and use its actual legal name and VAT identifier. The customer’s Spanish NIF stays on the entity.

Use dedicated synthetic transactions in AEAT preproduction. Retain the actual AEAT acceptance or rejection, response details, record identifiers and software version. Exercise issuance, correction and cancellation, check retry evidence where applicable, and verify the retained test QR in the final PDF. Local mocks or an upload-success response do not prove AEAT acceptance or representative authority.

Production remains disabled until the signed producer declaration and existing release checks are satisfied. AEAT requires the producer’s version-specific declaration, rather than external product certification or prior product registration; a pilot does not replace that obligation. See AEAT’s declaration requirements.

Issue and correct invoices

Create ordinary invoices through the normal invoice API with the optional verifactu input. Complete invoices use F1. Simplified invoices use F2 and this release limits them to EUR 400 including VAT. Credit notes use R1 through R5 according to the applicable rectification reason and must describe only the difference being corrected. R5 references a simplified F2 invoice; R1–R4 reference complete invoices. F3 replacement invoices and substitution rectifications are not supported by this release.

When VeriFactu is enabled, default invoice, advance-invoice and credit-note series use F-, A- and R- prefixes respectively. Custom numbering formats are preserved and must keep these invoice families distinct. The fiscal transaction rejects reuse of a series, number and date even after rejection or cancellation.

Ordinary positive-rate domestic VAT rows receive the default classification. Zero-rate, exempt, reverse-charge, and cross-border rows need explicit verifactu.tax_treatments entries that refer to the persisted tax-summary row. The API reconciles the classified bases, VAT, and total before recording the operation.

After a record exists, the invoice is immutable even while the record is pending or rejected. Use a fiscal correction for metadata errors, a rectifying invoice for amount, identity, or recipient changes, and cancellation only when the invoice was issued in error. A refund or economic adjustment belongs in a rectifying invoice workflow.

Evidence, retry, and output

Read evidence with sdk.veriFactu.getEvidence(recordId, { entity_id: entity.id }). The response includes the immutable record, request XML, chain fields, current projection, and the latest 100 submission or consultation attempts. Retry with sdk.veriFactu.retryRecord(recordId, { entity_id: entity.id }); retries retain the original hash and XML, respect AEAT flow control, and use a consultation when a duplicate response needs confirmation. The retry worker preserves record order, respects AEAT flow control, and caps outage backoff below one hour. The product’s incident state reports how many generated records await AEAT confirmation while the incident continues.

Use sdk.veriFactu.correctRecord(recordId, { reason, correction }, { entity_id: entity.id }) for a metadata correction and sdk.veriFactu.cancelRecord(recordId, { reason }, { entity_id: entity.id }) for an issuance-error cancellation. Repeating the same operation is idempotent; changing its input requires a new operation or returns a conflict.

Supply only the metadata fields being corrected. Omitted or null correction fields preserve the retained description, recipient identification type and tax classifications. Invoice amounts and party details are never changed by this operation.

Issued invoices remain printable with their retained QR while submission is pending, so an AEAT outage does not stop invoice delivery. The app reports the pending count and submission status separately. Records accepted with admissible errors also remain printable while their correction warning stays visible. Rejected records and cancelled or locally voided invoices cannot be delivered; resolve their fiscal state first. Printing an invoice does not imply AEAT has accepted its submission.

The branded templates display the stored verification URL as a tax QR once, at the beginning of the first page, before the invoice content. The QR uses medium error correction and a white quiet zone, following AEAT’s placement guidance. Spain’s VeriFactu support does not enable country-specific structured exports such as Slovenian e-SLOG or German XRechnung.

The API paths are GET/PUT /fiscalization/verifactu/settings, POST /fiscalization/verifactu/certificate, GET /fiscalization/verifactu/records/{id}, and POST to /retry, /correct, and /cancel under that record path. The JavaScript SDK exposes these as sdk.veriFactu.getSettings, updateSettings, uploadCertificate, getEvidence, retryRecord, correctRecord, and cancelRecord.

For legal obligations and current technical material, consult AEAT’s VeriFactu portal, Real Decreto 1007/2023, and AEAT technical information. For the product’s declaration draft, see VeriFactu declaration of responsibility (draft).

One Spanish fiscal regime per entity

An entity can use either TicketBAI or VeriFactu. Enabling a second provider, or issuing through it after the other provider has created fiscal records, returns HTTP 409. Disabling the first provider does not clear its fiscal history. Contact support to review a regime transition. Separate workshop entities may use different providers.

Custom outgoing document routes are unavailable for Spanish entities, including drafts. Use the standard document routes so the applicable fiscal checks run.