VeriFactu record subsanación
Correct a VeriFactu billing record — rejected or accepted by the AEAT — with a NEW record that follows it. When subsanación applies, when you need an annulment or a corrective instead, and the exact API flow.
When the AEAT rejects a VeriFactu billing record because of a data error
(record status: rejected), or when an accepted record turns out to carry wrong
data that the invoicing rules do not require a corrective invoice for, the
VeriFactu regulation (Royal Decree 1007/2023, art. 11) lets you subsanar it. The
AEAT does not accept an edit in place: the correction is always a new record,
and the original stays untouched (AEAT web services description, §9.1.2 and §9.1.3).
It is not a new invoice, not a corrective and not an annulment.
POST /v1/companies/{company}/verifactu/records/{record}/subsanar- Scope:
verifactu:write - Request body: none — the content is regenerated server-side from the source invoice and the current master data.
- Response:
202 Acceptedwithdata.id, the id of the new record, which is the one that is transmitted. The new record starts aspending; read it withGET /v1/companies/{company}/verifactu/records/{id}.
The new record has the same invoice key (issuer tax ID, series, number and
date) as the one it corrects, its own fingerprint (huella) and its own
position in the chain, after the last record of the company: the chain is never
rewritten. It always declares Subsanacion=S, and its RechazoPrevio depends on
the case — none for an accepted record (§9.1.2); X for a rejected record whose
invoice the AEAT does not hold, because the rejected record never existed there
(§9.1.3); S for a rejected subsanación of an invoice the AEAT does hold. There is
no cap on subsanación attempts.
When to use it — and when not
The record tells you whether it admits subsanación: can_subsanar on the record
object is true when the record is an alta, is accepted, accepted with errors or
rejected, is the last alta of its invoice and the invoice is not annulled.
| Situation | What to do |
|---|---|
Record rejected by the AEAT for a data error — recipient NIF not identified in the census, wrong recipient name, description issues… | Subsanación. Fix the data at its source, then call POST …/subsanar. |
Record rejected with aeat_error_code: SCHEMA_INVALID: it did not meet the AEAT schema and was never sent. | Subsanación, after fixing the offending data. It does not stop the rest of the chain. |
Record accepted (or accepted with errors) with an internal data error that the invoicing rules do not require a corrective for. | Subsanación: a new alta follows the accepted one. |
Record accepted and the invoicing rules require a corrective (an error that must be corrected with a corrective invoice, a return, a different amount…). | A corrective invoice (R1–R5). |
Record in error (technical failure: timeout, AEAT down) or pending. | Nothing — transmission retries automatically (manual retry available). Subsanación answers record_not_rejected. |
| The correction changes a fingerprint field: issuer NIF, series + number, issue date, invoice type, total tax amount or total amount. | Annulment + new record (new invoice or corrective). Subsanación answers requires_annulment without creating anything. |
The flow
Detect it. For a rejection, subscribe to the invoice.verifactu_failed
webhook event, or poll the
record list
for status: rejected (or use the app's «needs attention» filter). The record
carries the AEAT rejection detail in aeat_error_code.
Fix the data at its source. The new record is regenerated from the source invoice and the current master data — e.g. correct the client's NIF or registered name and the new values are picked up automatically. The invoice's frozen legal snapshot is deliberately refreshed so the PDF matches what the AEAT receives.
Call the endpoint. A new record joins the transmission queue of the company:
curl -X POST https://api.factuarea.com/v1/companies/{company}/verifactu/records/0197a2a8-4cf0-7a31-9a5e-3f2b8c1d6e42/subsanar \
-H "Authorization: Bearer fact_live_3pXnR2VbY7TcA9eFmN5z8KqW"Response (202):
{
"data": {
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8d11",
"message": "Subsanación encolada. El registro se reenviará a la AEAT en unos segundos."
}
}data.id is the new record, never the one you sent.
Watch the outcome. The new record is transmitted and ends accepted — or
rejected once more if the data is still wrong, in which case you subsanar again
(there is no attempt limit).
Errors
Business-rule violations return 422 with code: business_rule_violation and a
subcode that pinpoints the cause:
{
"error": {
"type": "invalid_request_error",
"code": "business_rule_violation",
"subcode": "record_not_rejected",
"message": "El registro no está rechazado ni aceptado por la AEAT (estado actual: pending). Solo los registros rechazados o aceptados admiten subsanación; para fallos técnicos usa el reintento."
}
}| HTTP | code / subcode | When |
|---|---|---|
| 404 | resource_not_found | The record doesn't exist or belongs to another company. |
| 422 | business_rule_violation / record_not_rejected | The record is neither accepted nor rejected: it is pending, submitted — or a technical error, which the automatic retry covers. |
| 422 | business_rule_violation / requires_annulment | The correction touches fingerprint fields. Annul the record and issue a new invoice or a corrective. |
| 422 | business_rule_violation / record_not_subsanable | The record is not an alta; it is not the last alta of its invoice (a later subsanación already replaces it); the invoice is annulled; or it has no source invoice to regenerate from. |
| 403 | insufficient_scope | The key lacks the verifactu:write scope. |
Avoid the rejection in the first place
The most frequent data rejection is a recipient that the AEAT census does not
identify (rejection 1239). Verify the name + NIF pair of a client with
POST /v1/companies/{company}/contacts/census-verification
before issuing VeriFactu invoices to them — subsanación then remains what
it should be: a safety net, not a routine.
A data field that does not fit the AEAT schema does not even get that far: the
invoice is not issued (422 verifactu_not_eligible, see
VeriFactu auto-submission).