Factuarea APIDevelopers

VeriFactu submission states

The lifecycle of a VeriFactu billing record — pending, submitted, accepted, rejected, error — what csv and huella mean, how retries and blocked records work, and when to retry, release or subsanar.

Every invoice your company issues under VeriFactu produces a billing record: a signed XML declaration that is transmitted to the AEAT and cryptographically chained to the previous record of the same company. The invoice and its record are two different objects with two different lifecycles — an invoice can be sent and paid while its record is still rejected by the AEAT.

This page is about the record. If you integrate against Factuarea and you only watch invoice status, you will not notice that the tax authority refused a declaration.

When this applies

The record lifecycle applies to every company that has VeriFactu effectively enabled, from the moment an invoice leaves draft. It does not apply to:

  • Companies still in no_verifactu mode: records are still created and chained locally, but never transmitted, so they stay outside the accepted/rejected cycle (BR-VFC-018, RD 1007/2023 art. 16).
  • Historical invoices imported with the VeriFactu step skipped — no record is created at all, so there is nothing to poll (BR-VFC-009).

Read VeriFactu auto-submission for the gates that decide whether a record is created in the first place.

The five states

statusMeaningTerminal?What you do
pendingThe record exists and is chained, but has not been transmitted yet.NoNothing. Transmission is queued.
submittedSent to the AEAT, awaiting the definitive answer.NoNothing. Poll.
acceptedThe AEAT registered the declaration. aeat_csv is populated.Yes — immutableNothing. A data error in it is corrected with a new record (subsanar) or, when the invoicing rules require it, a corrective.
rejectedThe AEAT refused it because of a data error (unknown recipient tax ID, schema, totals), or Factuarea stopped it before sending because it broke the AEAT schema (SCHEMA_INVALID).Definitive answer, but repairableFix the data, then subsanar.
errorTechnical transmission failure (timeout, AEAT unreachable), or the record is blocked waiting for something only you can provide (a certificate, a representation).NoNothing for a technical failure (it retries by itself); for a blocked record, fix the cause and retry-blocked.

The distinction that matters is rejected versus error. rejected is the AEAT saying "I read your declaration and it is wrong". error is the declaration never arriving. They are repaired by different operations, and confusing them is the most common integration mistake on this endpoint.

accepted is the only genuinely immutable state: the transition matrix refuses every transition out of it, because RD 1007/2023 makes a registered record unalterable. Correcting an accepted record never edits it: subsanar generates a new record that follows it. Every other state can transition again, which is what makes retry possible.

What the API sends

You never create a record with a payload — it is created for you. What you do is read it. The v1 record object is returned by GET /v1/companies/{company}/verifactu/records/{id}, GET /v1/companies/{company}/verifactu/records and, keyed by invoice, by GET /v1/companies/{company}/invoices/{id}/verifactu:

FieldMeaning
statusOne of the five states above.
typeALTA (the invoice was issued) or ANULACION (it was annulled).
invoice_typeThe AEAT invoice type frozen at issue time: F1, F2, F3, R1–R5.
huellaThe SHA-256 fingerprint of this record, uppercase hex. It is the chain link the next record will point at.
is_blockedtrue when the record is in error with no automatic retry: it needs your action and holds back the records after it.
block_reasonWhy it is blocked: MISSING_CERTIFICATE, MISSING_REPRESENTATION, PRESENTER_NOT_ENABLED or SUBMISSION_REJECTED; null otherwise.
aeat_error_codeThe last AEAT code (for example 1110 or 4112), an internal one (MISSING_CERTIFICATE, SCHEMA_INVALID…) or the warning code of a record accepted with errors; null when there is none.
can_subsanarWhether POST …/subsanar admits this record: an alta that is accepted, accepted with errors or rejected, is the last one of its invoice and the invoice is not annulled.
aeat_csvThe Código Seguro de Verificación the AEAT returns on acceptance. null until then. This is the value you reconcile against the tax authority.
aeat_submission_idOur transmission identifier, for support conversations.
transmitted_atISO 8601 of the last transmission that reached the AEAT. Populated in submitted and accepted; null in pending, rejected and error.
environmentThe AEAT environment for this company — production or the AEAT test bed. A CSV obtained in the test bed is not a real registration.
is_simplificada / is_substitute_for_simplifiedWhether the source invoice was an F2, and whether this record substitutes simplified invoices with an F3.

Two fields deserve their own warning.

huella is identity, not a checksum you may recompute. It is calculated from the issuer tax ID, series and number, issue date, invoice type, total tax amount, total amount, the previous record's fingerprint and the generation timestamp — in that exact order and format. If any of those change, the chain breaks and the whole company's integrity proof fails (BR-VFC-013). This is why some corrections cannot be repaired in place; see Retry or subsanar.

aeat_csv is written once. On acceptance it is persisted and never overwritten, even if the AEAT returns the same CSV on a later transmission. A record that goes rejected after having been submitted keeps the previous CSV for audit purposes, so a non-null aeat_csv on a rejected record is expected, not a bug (BR-VFC-016).

curl https://api.factuarea.com/v1/companies/{company}/invoices/0197a2a8-4cf0-7a31-9a5e-3f2b8c1d6e42/verifactu \
  -H "Authorization: Bearer fact_live_3pXnR2VbY7TcA9eFmN5z8KqW"
{
  "data": {
    "id": "0197b3c9-1de2-7c40-8b71-a4d5e6f70123",
    "object": "verifactu_record",
    "type": "ALTA",
    "invoice_type": "F1",
    "invoice_number": "F-2026-0042",
    "date": "2026-05-15",
    "amount": 121.0,
    "status": "accepted",
    "huella": "9F2C1A0B7E4D6835A1C0B9E8D7F6A5B43C2D1E0F9A8B7C6D5E4F3A2B1C0D9E8F",
    "aeat_submission_id": "sub_0197b3c9",
    "aeat_csv": "FCT-2026-A1B2C3D4-E5F6",
    "environment": "production",
    "transmitted_at": "2026-05-15T09:41:02Z",
    "is_simplificada": false,
    "is_substitute_for_simplified": false,
    "created_at": "2026-05-15T09:40:58Z"
  }
}

Retries have no cap, and some records are blocked

Behind error there are two very different situations.

A technical failure retries by itself, without a limit. If a submission gets no answer, a server fault or a network failure, its records are retried with growing intervals — 1, 5, 15 and 30 minutes and then every 55 — so that no more than one hour passes between two attempts, for as long as the incident lasts (art. 16.4 of Order HAC/1177/2024). There is no maximum number of attempts, so max_retries_exceeded is no longer returned for billing records (it still applies to the retry of an event in no_verifactu mode). While there is an incident, the next submission carries the incident flag, and records generated meanwhile wait behind the earlier ones.

A blocked record does not retry, because retrying would fail again until you act: no usable certificate, no active representation, a presenter that the AEAT has not enabled or a client-side rejection. It shows is_blocked: true and its block_reason, and it holds back every record after it in the chain of the company. Fix the cause and it is released by itself (uploading the certificate, registering the representation or changing the mode reactivates what that event can free) or in one call to POST /v1/companies/{company}/verifactu/records/retry-blocked, which answers 202 with how many records it reactivated. The causes and their meaning are in VeriFactu auto-submission.

GET /v1/companies/{company}/verifactu/stats reports how many records are still waiting (pending_incident_count), how many of them are blocked (blocked_incident_count) and the age of the oldest (oldest_pending_at).

The attempt counter and the schedule of the next retry are still not exposed in the record object; the state is what you poll. The audit timeline is in GET /v1/companies/{company}/verifactu/records/{id}/activities.

Retry or subsanar

Both operations act on a record that already exists, and a third one releases the blocked ones. They are not interchangeable.

Record stateCauseOperationWhy
error, not blockedThe declaration never reached the AEAT.POST /v1/companies/{company}/verifactu/records/{id}/retryThe stored XML is correct. It goes out again in the next batch of the company, in order.
error, blockedSomething needs you: certificate, representation, presenter.Fix the cause, then POST /v1/companies/{company}/verifactu/records/retry-blockedIt reactivates every blocked record of the company at once.
rejectedThe AEAT read it and refused the data, or it never left because it broke the schema.POST /v1/companies/{company}/verifactu/records/{id}/subsanarA new record, regenerated from corrected master data.
acceptedA data error that the invoicing rules do not require a corrective for.POST /v1/companies/{company}/verifactu/records/{id}/subsanarA new record follows the accepted one, which stays untouched.
accepted, and the invoicing rules require itA wrong amount, a return, an error that must be corrected with a corrective.Issue a corrective invoice.That is the route the invoicing rules prescribe.
rejected or accepted, but the fix touches a fingerprint fieldThe wrong total, date, number, tax ID or invoice type was declared.Neither — annul and re-issue.Changing a fingerprint field would invalidate the chain. subsanar refuses it up front.

Retrying blind has no attempt limit either. Subsanación is a deliberate manual correction with new data, the regulation sets no cap on it, and it never edits a record: it generates a new alta, with the same invoice key as the one it corrects, its own fingerprint and its own position in the chain, and the original stays untouched (BR-VFC-020, BR-VFC-046). Before creating anything it compares the regenerated fingerprint-relevant fields against the stored ones; if any differs, it answers 422 with the subcode that tells you an annulment is required, and nothing is created. The full flow, the error subcodes and the prevention advice live in VeriFactu record subsanación.

What appears on the PDF

The record state does not change the PDF. Whatever the state, the invoice prints the same legal QR block in the top-right corner of the first page: the QR tributario: label, a 30×30 mm code addressing the AEAT verification service with the issuer tax ID, series and number, date and total, and the legend underneath (BR-VFC-015).

Three consequences worth designing for:

  • The QR is printed as soon as a record exists — including while it is pending, error or rejected. A recipient who scans it before acceptance sees the AEAT reporting no registration. That is correct behaviour, not a defect.
  • The CSV is not printed on the PDF. It is only available through the API and the dashboard.
  • The fingerprint and the registration timestamp stopped being printed as well. If you were scraping them from the PDF, read them from the record instead.

Subsanación is the one operation that also touches the printed document: it deliberately re-freezes the invoice's immutable recipient and issuer snapshots from current master data, so that the PDF matches what was re-declared to the AEAT (BR-INV-024, BR-VFC-020). It is the only path that rewrites an already-frozen snapshot.

What reaches the AEAT

Each record transmits one declaration, chained to the previous record of the same company by its fingerprint. Three record kinds exist, and they do not share one chain: registrations (ALTA) and annulments (ANULACION) share the invoicing chain, while system-event records keep a separate chain of their own, because the regulation treats operational events as separate evidence (BR-VFC-014).

A subsanación is declared as a new alta that always carries Subsanacion=S, plus RechazoPrevio when it follows a rejection: X for a rejected alta of an invoice the AEAT does not hold, S for a rejected subsanación of an invoice it does hold, and none for a subsanación of an accepted record. Neither flag enters the fingerprint calculation, so declaring them does not disturb the chain (BR-VFC-026, BR-VFC-046). The records of a company reach the AEAT in ordered batches of up to 1.000, respecting the waiting time the AEAT states (BR-VFC-056).

You can verify the whole chain yourself with GET /v1/companies/{company}/verifactu/chain/validate, which recomputes every fingerprint and reports anomalies. It is rate-limited to one call per minute per company because it walks the entire ledger.

Traceability

Derived from the domain rules of the Factuarea backend:

  • BR-VFC-006 — the earlier capped retry policy; for billing records it is superseded by BR-VFC-040.
  • BR-VFC-040 — periodic retry, at least every hour, with no cap on attempts, in chain order, with the pending alert.
  • BR-VFC-048 — a blocked record (no automatic retry) and its release, by its cause or with retry-blocked.
  • BR-VFC-049 — a record that breaks the AEAT schema is isolated as rejected (SCHEMA_INVALID) and never sent.
  • BR-VFC-013 — the fingerprint chain is immutable and verifiable; any alteration invalidates the AEAT integrity guarantee.
  • BR-VFC-014 — the three record kinds and their independent chains.
  • BR-VFC-015 — the mandatory QR block on the printed invoice.
  • BR-VFC-016 — the CSV as public identity of the record, persisted unaltered.
  • BR-VFC-018 — no_verifactu mode: local chain, no transmission.
  • BR-VFC-020 — subsanación of accepted or rejected records as a new alta, and the fingerprint guard.
  • BR-VFC-046 — several altas per invoice: ordinal, RechazoPrevio and the last alta.
  • BR-VFC-026 — the AEAT flags of a subsanación alta (Subsanacion, RechazoPrevio).
  • BR-VFC-056 — remission in ordered batches with flow control.
  • BR-INV-024 — the immutable recipient snapshot and the single exception that refreshes it.

Also derived from the transmission state machine documented alongside those rules (AeatTransmissionStatus), which is the source of truth for the transition matrix quoted in The five states.

On this page

Need a hand?Contact support