Skip to main content

Validation

A-Cube validates every document before it builds the billing record, so that AEAT does not reject a record A-Cube could have caught. Validation is synchronous: if anything is wrong, the request fails immediately with a 4xx response, nothing is stored, and nothing is sent to AEAT.

A request that passes validation is answered 202 Accepted, and AEAT's own verdict is then reported in the invoice status (see Invoice lifecycle).

Where validation runs​

CallWhat is checked
POST /invoiceseverything: document structure and formats, AEAT rules, duplicate registration
POST /invoices/simplifiedeverything, as above
POST /invoices/draft/{uuid}/senddraft completeness, then everything, as above
POST /invoices/{uuid}/correctiondocument structure and formats, AEAT rules, unchanged invoice identity
POST / PUT on the draft endpointsdocument structure and the mandatory identity only: a draft may be incomplete

The checks run in steps: the document is parsed first, then the structure and the field formats are validated, then the AEAT rules together with the duplicate check. Only the errors of the first failing step are returned, so fixing them can reveal errors of a later step.

Error response format​

Errors are returned as RFC 7807 problem details (Content-Type: application/problem+json):

FieldMeaning
statusthe HTTP status code
titlealways An error occurred
detaila human-readable summary; for validation errors, every violation as propertyPath: message, one per line
type/errors/{status} for request errors, /validation_errors/{code} for validation errors
violationsonly on validation errors: the list of individual violations

Each entry of violations has:

FieldMeaning
propertyPathwhere the error is, in the terms of the document you sent (see below)
messagewhat is wrong
codefor an AEAT rule, the AEAT error code (for example 1150); otherwise a generic validator identifier or null. Match on it only for AEAT codes

propertyPath follows the document you sent:

InputpropertyPath example
Facturae (XML or JSON)Facturae/Invoices/Invoice[0]/TaxesOutputs/Tax[0]/TaxRate
native VERI*FACTU or simplified invoiceRegistroAlta/Desglose/DetalleDesglose[0]/CuotaRepercutida
XML that cannot be parsedline 1 (the line of the XML error)
draft send (completeness)the invoice field, for example invoiceNumber

The AEAT rules are written against the VERI*FACTU record, but they also apply to a Facturae document: A-Cube reports them on the Facturae element the record value comes from.

Error groups​

StatusGroupWhen
400Malformed JSONthe JSON body cannot be parsed
403Permissionyou cannot issue invoices for the sender tax id
409Conflictthe operation is not allowed in the current state of the invoice
415Unsupported formatthe Content-Type is not accepted by the endpoint
422Malformed XMLthe XML body is not well formed
422Structure and formatsa required element is missing or a value has the wrong format
422AEAT rulesthe record breaks a VERI*FACTU validation rule, or is already registered
422Incomplete drafta draft is sent before it is complete
422Correction identitya correction changes the identity of the invoice it corrects

Malformed JSON​

The JSON body is not valid JSON. Fix the syntax and send it again.

{
"title": "An error occurred",
"detail": "The JSON data provided is invalid: Syntax error",
"status": 400,
"type": "/errors/400"
}

Permission​

The sender tax id (SellerParty in Facturae, IDEmisorFactura in a native or simplified record) must belong to a legal entity you own with customerInvoiceEnabled. The same 403 is returned when your token lacks the ROLE_WRITER role.

{
"title": "An error occurred",
"detail": "User is not allowed to send invoices for this tax id",
"status": 403,
"type": "/errors/403"
}

Conflict​

The operation does not fit the current state of the invoice. The detail explains which case applies:

detailCause
Invoice is not in draft stateupdating, sending or deleting a draft that was already sent
Invoice is a simplified invoice draft; update it with PUT /invoices/draft/simplified/{uuid}PUT /invoices/draft/{uuid} on a simplified draft
Invoice is not a simplified invoice draft; update it with PUT /invoices/draft/{uuid}PUT /invoices/draft/simplified/{uuid} on a complete-invoice draft
Only an invoice accepted with errors can be correcteda correction of an invoice that is not accepted_with_errors
{
"title": "An error occurred",
"detail": "Only an invoice accepted with errors can be corrected",
"status": 409,
"type": "/errors/409"
}

Unsupported format​

The Content-Type is not one the endpoint accepts: for example a Facturae document sent to POST /invoices/simplified, or a correction in a different format from the invoice it corrects. The accepted values are listed in Send invoice and Simplified invoices.

{
"title": "An error occurred",
"detail": "Please send the simplified invoice as a native VERI*FACTU xml or as json.",
"status": 415,
"type": "/errors/415"
}

Malformed XML​

The XML body is not well formed. Each parser error is a violation whose propertyPath is the line where it occurred.

{
"status": 422,
"violations": [
{
"propertyPath": "line 1",
"message": "Namespace prefix fe on Facturae is not defined",
"code": null
},
{
"propertyPath": "line 1",
"message": "Premature end of data in tag broken line 1",
"code": null
}
],
"detail": "line 1: Namespace prefix fe on Facturae is not defined\nline 1: Premature end of data in tag broken line 1",
"type": "/validation_errors/0=;1=",
"title": "An error occurred"
}

Structure and formats​

A required element is missing, or a value does not have the expected format. For a Facturae document this covers the Facturae 3.2.2 structure and its business rules (for example a single invoice per document, matching ResidenceTypeCode and country, IRPF as the only withheld tax, due dates not earlier than the issue date). For a native or simplified record it covers the AEAT field formats:

ElementRule
IDEmisorFactura, IDDestinatario/NIFa valid Spanish NIF, NIE or CIF, including the check digit
NumSerieFactura1 to 60 printable ASCII characters, without " ' < = >
FechaExpedicionFacturaa real date in dd-mm-yyyy format, not later than today
NombreRazonEmisor, NombreRazonup to 120 characters
DescripcionOperacionnot blank, up to 500 characters
CuotaTotal, ImporteTotal, BaseImponibleOimporteNoSujeto, CuotaRepercutidaa number with up to 12 integer digits and 2 decimals, optionally negative
TipoImpositivoa rate with up to 3 integer digits and 2 decimals
Impuesto01, 02, 03 or 05
ClaveRegimenone of the AEAT codes 01 to 11, 14, 15, 17 to 21
CalificacionOperacionS1, S2, N1 or N2
FacturaSinIdentifDestinatarioArt61dS or N
IDOtro/IDType02 to 07; IDOtro/CodigoPais must be a valid country code

In the sandbox, the reserved ESSANDBOX* recipient tax ids are exempt from the NIF check, so you can use them to simulate AEAT outcomes.

{
"status": 422,
"violations": [
{
"propertyPath": "RegistroAlta/Destinatarios/IDDestinatario[0]/NIF",
"message": "The tax identifier \"B12345675\" is not a valid Spanish NIF, NIE or CIF.",
"code": null
}
],
"detail": "RegistroAlta/Destinatarios/IDDestinatario[0]/NIF: The tax identifier \"B12345675\" is not a valid Spanish NIF, NIE or CIF.",
"type": "/validation_errors/00000000000020590000000000000000",
"title": "An error occurred"
}

AEAT rules​

The record breaks one of the rules AEAT applies to a VERI*FACTU billing record (Validaciones y errores, v1.2.2). A-Cube checks these rules itself and returns the AEAT error code in code, so you can map it exactly as you would map an AEAT rejection.

codeRule
1114TipoFactura R1 to R5 requires TipoRectificativa, which is not supported yet
1111IDOtro/CodigoPais is required when IDType is not 02 (NIF-IVA)
1124TipoImpositivo is not an IVA rate in force on the issue date (0, 4, 10, 21, plus 5 from 01-07-2022 to 30-09-2024 and 2 / 7.5 from 01-10-2024 to 31-12-2024)
1126IDOtro/CodigoPais ES is only allowed with IDType 03 (passport) or 07 (not registered in the AEAT census), and 07 requires ES
1139an ImporteTotal of 100,000,000 or more requires Macrodato, which is not supported yet
1142CuotaRepercutida differs from BaseImponibleOimporteNoSujeto × TipoImpositivo / 100 by more than 10
1143CuotaRepercutida and BaseImponibleOimporteNoSujeto have different signs
1147ClaveRegimen 14 requires FechaOperacion, which is not supported yet
1150a simplified invoice (F2) exceeds 3000 EUR (+10 EUR tolerance) and FacturaSinIdentifDestinatarioArt61d is not S
1152FechaExpedicionFactura is earlier than 28-10-2024, the date VERI*FACTU came into force
1185FacturaSinIdentifDestinatarioArt61d S is only allowed when TipoFactura is F2 or R5
1189Destinatarios is required when TipoFactura is F1, F3 or R1 to R4
1190Destinatarios must not be sent when TipoFactura is F2 or R5
1193the recipient NIF is the same as IDEmisorFactura
1197CalificacionOperacion S2 is only allowed when TipoFactura is F1, F3 or R1 to R4
1198CalificacionOperacion S2 requires TipoImpositivo and CuotaRepercutida to be 0
1200ClaveRegimen 03 requires CalificacionOperacion S1
1201ClaveRegimen 04 requires CalificacionOperacion S2
1202ClaveRegimen 06 requires BaseImponibleACoste, which is not supported yet
1203ClaveRegimen 07 does not allow CalificacionOperacion S2, N1 or N2
1205ClaveRegimen 10 requires CalificacionOperacion N1 and TipoFactura F1
1206ClaveRegimen 11 under IVA requires TipoImpositivo 21
1207CuotaRepercutida can only be different from 0 when CalificacionOperacion is S1
1237CalificacionOperacion N1 / N2 (not subject) under IVA is not supported yet
1246ClaveRegimen 21 is only allowed when Impuesto is 03 (IGIC)
1252ClaveRegimen 08 requires CalificacionOperacion N2
1260ClaveRegimen is only allowed when Impuesto is 01 (IVA), 02 (IPSI) or 03 (IGIC)
3000an invoice with the same issuer, number and issue date already exists; only drafts and rejected invoices do not count
{
"status": 422,
"violations": [
{
"propertyPath": "RegistroAlta/Desglose",
"message": "The sum of BaseImponibleOimporteNoSujeto and CuotaRepercutida of a simplified invoice cannot exceed 3000 (+10) unless FacturaSinIdentifDestinatarioArt61d is S.",
"code": "1150"
}
],
"detail": "RegistroAlta/Desglose: The sum of BaseImponibleOimporteNoSujeto and CuotaRepercutida of a simplified invoice cannot exceed 3000 (+10) unless FacturaSinIdentifDestinatarioArt61d is S.",
"type": "/validation_errors/1150",
"title": "An error occurred"
}

A duplicate is reported the same way, with code 3000 on the invoice header:

{
"status": 422,
"violations": [
{
"propertyPath": "Facturae/Invoices/Invoice[0]/InvoiceHeader",
"message": "Invoice \"FV-2026-0001\" of 15-01-2026 has already been registered for IDEmisorFactura \"A58818501\".",
"code": "3000"
}
],
"detail": "Facturae/Invoices/Invoice[0]/InvoiceHeader: Invoice \"FV-2026-0001\" of 15-01-2026 has already been registered for IDEmisorFactura \"A58818501\".",
"type": "/validation_errors/3000",
"title": "An error occurred"
}

Incomplete draft​

POST /invoices/draft/{uuid}/send first checks that the draft holds everything an invoice needs. Each missing value is a violation on the invoice field, as returned by GET /invoices/draft/{uuid}: invoiceNumber, invoiceDate, currency, totalAmount, netAmount, taxAmount and verifactuInvoiceType (the AEAT TipoFactura). The draft is left unchanged: complete it with PUT and send it again.

{
"status": 422,
"violations": [
{ "propertyPath": "invoiceNumber", "message": "This value should not be null.", "code": "ad32d13f-c3d4-423b-909a-857b961eb720" },
{ "propertyPath": "invoiceDate", "message": "This value should not be null.", "code": "ad32d13f-c3d4-423b-909a-857b961eb720" },
{ "propertyPath": "currency", "message": "This value should not be null.", "code": "ad32d13f-c3d4-423b-909a-857b961eb720" },
{ "propertyPath": "totalAmount", "message": "This value should not be null.", "code": "ad32d13f-c3d4-423b-909a-857b961eb720" },
{ "propertyPath": "netAmount", "message": "This value should not be null.", "code": "ad32d13f-c3d4-423b-909a-857b961eb720" },
{ "propertyPath": "taxAmount", "message": "This value should not be null.", "code": "ad32d13f-c3d4-423b-909a-857b961eb720" },
{ "propertyPath": "verifactuInvoiceType", "message": "This value should not be null.", "code": "ad32d13f-c3d4-423b-909a-857b961eb720" }
],
"detail": "invoiceNumber: This value should not be null.\ninvoiceDate: This value should not be null.\ncurrency: This value should not be null.\ntotalAmount: This value should not be null.\nnetAmount: This value should not be null.\ntaxAmount: This value should not be null.\nverifactuInvoiceType: This value should not be null.",
"type": "/validation_errors/0=ad32d13f-c3d4-423b-909a-857b961eb720;1=ad32d13f-c3d4-423b-909a-857b961eb720;2=ad32d13f-c3d4-423b-909a-857b961eb720;3=ad32d13f-c3d4-423b-909a-857b961eb720;4=ad32d13f-c3d4-423b-909a-857b961eb720;5=ad32d13f-c3d4-423b-909a-857b961eb720;6=ad32d13f-c3d4-423b-909a-857b961eb720",
"title": "An error occurred"
}

Once the draft is complete, the send runs the same structure, AEAT rule and duplicate checks as POST /invoices.

Correction identity​

A correction must keep the issuer, the number and the issue date of the invoice it corrects. The invoice is left unchanged.

{
"title": "An error occurred",
"detail": "A correction must keep the IDEmisorFactura, NumSerieFactura and FechaExpedicionFactura of the invoice it corrects",
"status": 422,
"type": "/errors/422"
}