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.
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.
| Field | What it is |
|---|---|
ready | true when nothing is outstanding, otherwise false. |
missing | null 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. |
nif | The business's Spanish tax number as stored, or null. |
legal_name | The business's registered name as stored, or null. |
tax_territory | peninsula (mainland Spain and the Balearics, IVA) or canarias (the Canary Islands, IGIC). |
aeat_environment | test or production: which AEAT service this deployment sends to. Read this one. |
certificate | null when none is stored. Otherwise an object — see below. |
records | Your 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_csv | The reference AEAT returned for the most recent submission (its código seguro de verificación), or null. |
next_send_at | When the next submission may go, as an ISO 8601 timestamp, or null if nothing is waiting. |
certificate, when one is stored:
| Field | What it is |
|---|---|
subject | The certificate's subject, as the certificate itself states it. |
holder_ids | The tax numbers the certificate identifies as its holder, as a list. |
not_after | When it expires, as an ISO 8601 timestamp. |
days_left | Whole days from now until not_after. Negative once it has expired. |
expired | true 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:
stage | Means | Also returned |
|---|---|---|
answered | AEAT 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_message | AEAT rejected the message itself rather than the record in it. ok is false. | code and message, as AEAT gave them. |
aeat_unreachable | GoRoute 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/certificateandDELETE /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 aPRU-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.