What VerifactuCloud does

Everything in this list is built and running against AEAT. What does not exist yet is further down, under the roadmap.

  • A registration record for every invoice

    We build the invoicing record with the fields required by Order HAC/1177/2024 and submit it to the tax agency's service.

  • SHA-256 hash chain

    Each record is chained to the hash of the previous one. Alter a record and the chain stops adding up — visibly.

  • Compliant QR code

    We generate the QR code that must be printed on the invoice, carrying the tax agency's verification URL.

  • Cancellations

    Cancellation records are implemented: a cancelled invoice is reported to the AEAT, never deleted. We have not yet had to send one, and we say so on the status page rather than letting you assume otherwise.

  • Chain state recovery

    If you lose your local state, we recover the last record from the AEAT and the chain continues unbroken.

  • API integration

    REST over JSON. Your system calls our API as it issues the invoice; there is nothing to install on your servers.

How it works

  1. You connect your system

    You call our API when you issue an invoice, with the data you already hold.

  2. We create the record

    We build the registration record, compute the hash, chain it to the previous one and submit it to the tax agency.

  3. You get the QR immediately

    The QR comes back in the same response, because you need it to print the invoice and it does not depend on AEAT. The filing result comes later: we give you a URL to read it once the tax agency has ruled on it.

The API, as it answers today

Two calls. You submit the invoice and the QR comes back in the same response; the filing result is read afterwards, once AEAT has ruled on it.

Examples of the service exactly as it responds today. The values are illustrative; the field names, status codes and response shape are what the service returns.

1. You submit the invoice

It answers 202, not 201: the invoice is accepted for filing, not filed. The QR is derived from the invoice itself and does not depend on AEAT, which is why it comes back straight away — you need it to print the invoice.

POST /v1/invoices
Authorization: Bearer <tu-clave>
Content-Type: application/json

{
  "numSerie": "2026/0001",
  "fechaExpedicion": "19-08-2026",
  "descripcion": "Servicios de consultoria",
  "base": "1000.00",
  "tipoImpositivo": "21",
  "cuota": "210.00",
  "total": "1210.00",
  "destinatarioNIF": "B00000000",
  "destinatarioNombre": "Cliente Ejemplo S.L."
}
202 Accepted

{
  "id": 1042,
  "state": "queued",
  "message": "accepted for filing; it will be submitted to AEAT with the next envio",
  "self": "/v1/invoices/1042",
  "qr": "https://.../VerifactuVisor?nif=...&numserie=...&fecha=...&importe=...",
  "qrImage": "/v1/invoices/1042/qr.png"
}

In this form the whole invoice travels as a supply that is subject to VAT and not exempt. So «tipoImpositivo»: «0» means «subject to Spanish VAT, at a zero rate», not «no Spanish VAT applies». An exempt or non-subject supply has to use the line-level form.

2. You read the result

The URL in “self” is where the outcome is read. The CSV appears once AEAT has registered the invoice, not before.

GET /v1/invoices/1042

{
  "id": 1042,
  "numSerie": "2026/0001",
  "state": "filed",
  "attempts": 1,
  "huella": "9F86D081884C7D65...",
  "csv": "A-XXXXXXXXXXXXXXX",
  "timestampPresentacion": "2026-08-19T09:00:00+02:00",
  "estadoRegistro": "Correcto",
  "qrImage": "/v1/invoices/1042/qr.png"
}

When an invoice mixes rates

For an invoice with more than one VAT rate, send «lines» instead of «base», «tipoImpositivo», «cuota» and «total». The two forms are mutually exclusive: sending both is an error. The totals are derived from the lines, so do not send them.

POST /v1/invoices
Authorization: Bearer <your-key>
Content-Type: application/json

{
  "numSerie": "2026/0002",
  "fechaExpedicion": "19-08-2026",
  "descripcion": "Supplies and services",
  "destinatarioNIF": "B00000000",
  "destinatarioNombre": "Example Customer S.L.",
  "lines": [
    { "descripcion": "Consultancy", "base": "100.00",
      "tipoImpositivo": "21", "cuota": "21.00", "total": "121.00" },
    { "descripcion": "Books", "base": "50.00",
      "tipoImpositivo": "4", "cuota": "2.00", "total": "52.00" }
  ]
}

AEAT does not receive your lines. The record carries a tax breakdown of at most twelve entries, with no room for a description, a quantity or a unit price: lines sharing a tax treatment are summed into one entry, and the invoice totals come from those.

Retrying is safe

An invoice's key is your NIF, its «numSerie» and its «fechaExpedicion». Repeat the same submission while the first is still in flight and the response carries the same «id» and «duplicate»: true, rather than queueing a second record. A retry after a timeout duplicates nothing.

The four states

queued
Accepted, not yet filed. Includes an invoice AEAT rejected that is queued for a corrected resubmission.
sending
In the batch currently being transmitted.
filed
AEAT registered it. The CSV is available.
unknown
An attempt was made and we do not know how it ended. The record may already be in AEAT's ledger, so it is not resent: it takes a consulta to settle.

The fourth state exists because a blind resend can duplicate a record in a ledger that cannot be undone. We would rather tell you we do not know than risk duplicating it.

Roadmap

None of this exists yet. We list it so you can see where the product is going, not to sell it as available.

  • No date: we will not announce one until we have it

    Corrective invoices

    Today a wrong invoice is put right by reporting a cancellation record. The corrective invoice — what accounting practice expects — is not built. If your case needs one, tell us before you integrate anything.

For now this is the roadmap. If we add anything it will appear here before it is available, not after.

Let's talk

We are in production and taking on customers. Tell us what you invoice with, what volume you handle and how you want to integrate it, and we will tell you what fits and what does not. If you are integrating the API yourself, you can sign up directly in the partner dashboard.