Skip to main content

Spain — Verifactu Readiness Calls

Two calls exist for setting up Verifactu, Spain's regime for billing records sent to the Spanish tax agency (the Agencia Estatal de Administración Tributaria, AEAT):

GET  /api/v1/jurisdictions/es/status
POST /api/v1/jurisdictions/es/connection-check

One reports how far your setup has got. The other proves a certificate end to end against AEAT's test service. Neither sends a real invoice and neither files anything that counts.

These calls do not exist on GoRoute's production service, and what they do reach has no tax effect

They are only present where GoRoute's Spain pack is switched on. It is not switched on for the production service (https://app.goroute.ai/peppol-api), so there the path answers 404 — the route is not there, as opposed to refusing you. It is switched on for the test service (https://peppol-api-test.goroute.ai/peppol-api).

You can see the difference without an API key, because a route that exists asks you to authenticate and a route that does not exist does not get that far:

# Production: 404 — no such route.
curl -o /dev/null -s -w '%{http_code}\n' \
"https://app.goroute.ai/peppol-api/api/v1/jurisdictions/es/status"

# Test: 401 — the route is there, and wants your API key.
curl -o /dev/null -s -w '%{http_code}\n' \
"https://peppol-api-test.goroute.ai/peppol-api/api/v1/jurisdictions/es/status"

And where the pack is on, it talks to AEAT's test service. Records go to the tax agency's test endpoint, which has no tax effect: nothing sent through it is a filing, settles anything, or discharges any obligation a Spanish business has.

Do not take that from this page — read it from the service. GET /status returns aeat_environment, which is test or production. That field is the service telling you which AEAT endpoint it uses. Believe it over anything written here, and check it again before you treat any Verifactu record as filed.

This page does not tell you what Spanish law requires of you. Verifactu obligations fall on the business issuing the invoices, and who is in scope, from when, is a question for your tax adviser or AEAT — not for GoRoute's documentation.

GET /api/v1/jurisdictions/es/status​

Readiness, the stored certificate's public facts, and the counts of your Verifactu records.

Permission: an authenticated organisation. No further permission is needed, so any working API key for the organisation can read it.

The response is a fixed object. Every field is always present; several are null until the setup that fills them has happened.

FieldWhat it is
readytrue when nothing is outstanding, otherwise false.
missingnull when ready is true. Otherwise one sentence naming the next thing to do — Verifactu not enabled for the account, the NIF missing, the legal name missing, the two declarations not confirmed, or the certificate not uploaded. One at a time, in that order.
nifThe business's Spanish tax number as stored, or null.
legal_nameThe business's registered name as stored, or null.
tax_territorypeninsula (mainland Spain and the Balearics, IVA) or canarias (the Canary Islands, IGIC).
aeat_environmenttest or production: which AEAT service this deployment sends to. Read this one.
certificatenull when none is stored. Otherwise an object — see below.
recordsYour Verifactu records counted by state, as an object keyed by state name. A state with no records is simply absent, so an empty object means no records at all. The states are pending (written, not sent), sent (sent, answer awaited), accepted, accepted_with_errors and rejected.
last_csvThe reference AEAT returned for the most recent submission (its código seguro de verificación), or null.
next_send_atWhen the next submission may go, as an ISO 8601 timestamp, or null if nothing is waiting.

certificate, when one is stored:

FieldWhat it is
subjectThe certificate's subject, as the certificate itself states it.
holder_idsThe tax numbers the certificate identifies as its holder, as a list.
not_afterWhen it expires, as an ISO 8601 timestamp.
days_leftWhole days from now until not_after. Negative once it has expired.
expiredtrue once not_after has passed.

Nothing in this response contains the certificate itself or its password. Both live in an encrypted store, and this call reads only the public facts above.

POST /api/v1/jurisdictions/es/connection-check​

One end-to-end test of your setup against AEAT's test service: the certificate, the network path, and the shape of the message GoRoute builds. It sends a single throwaway record that is not part of your real sequence of invoice records, so running it does not disturb anything and does not create a filing.

Permission: settings:manage, the same permission as replacing a country's settings.

Body: none. There is nothing to pass.

It needs the NIF, the legal name and an uploaded certificate first; without them it refuses with the 422 below.

The reply always carries ok and stage. stage says how far the check got, and the rest of the fields depend on it:

stageMeansAlso returned
answeredAEAT answered. This is the only stage where ok: true is possible.aeat_state (the answer for the submission), record_state (for the one record), error_code and message if AEAT objected to it, and csv, AEAT's reference for the submission.
aeat_refused_messageAEAT rejected the message itself rather than the record in it. ok is false.code and message, as AEAT gave them.
aeat_unreachableGoRoute could not reach AEAT's test service. ok is false.message saying what failed.

Treat ok: false with stage: aeat_unreachable as "try again" and ok: false with either other stage as "something in the setup or the data is wrong"; message is written to be shown to the person fixing it.

When setup is incomplete: one 422 for both calls​

Both calls report a setup problem the same way, with HTTP 422 and this body:

{
"error": "verifactu_setup",
"message": "Upload the business's digital certificate first.",
"request_id": "6f1c0b2e-6b1a-4a44-9f41-2b0f5a7c8e13"
}

message is a sentence written to be shown to the person doing the setup, so pass it through rather than mapping it to your own wording. error is always verifactu_setup, and request_id is the identifier GoRoute adds to a refusal — quote it if you ask us about one. Note that this shape carries error, not the error_code that most of the API's refusals use, so match on error here.

The connection check is the call that normally produces this, when the NIF, the legal name or the certificate is not in place yet. The status call produces it only if the organisation's stored Spanish settings cannot be read at all, which is why you should treat ready and missing as the normal way of asking "is setup finished?".

What this page does not cover​

The Spain pack has seven routes. The five not described above are deliberately left out, and each needs more than a paragraph alongside something else:

  • POST /api/v1/jurisdictions/es/certificate and DELETE /api/v1/jurisdictions/es/certificate — uploading and removing the business's own qualified signing certificate. Handing a signing certificate and its password to a service deserves its own page about how it is held, not a mention here.
  • POST /api/v1/jurisdictions/es/test-invoice — AEAT's own "invoice in test mode" method. It writes a record in your real sequence (in a PRU- series) and cancels it immediately, which is a different thing from the connection check above and needs saying carefully.
  • GET /api/v1/jurisdictions/es/records — exporting a period's records as CSV or XML.
  • GET /api/v1/public/verifactu/declaracion-responsable — the formal statement a billing software producer makes about its own product. That is a compliance document, not an integration feature.

Settings for Spain, like any other country's, go through the generic PUT /api/v1/jurisdictions/{code}/settings with ES as the code — see Jurisdiction Settings.