VeriFactu compliance records (ES)
Two endpoints, one question: how do you show the AEAT that your invoicing follows the Spanish rules? One hands out the software's declaración responsable, the other the complete registro chain of one NIF.
/api/v1/verifactu/declaracion/api/v1/verifactu/chain/{vatId}/exportWhy these two endpoints exist
Real Decreto 1007/2023 requires two things an inspector must be able to see: the software manufacturer's declaration of responsibility (art. 13.2, elaborated in art. 15 Orden HAC/1177/2024) and the records themselves (art. 14). The regulation says they must be viewable inside the system — for an API product that means retrievable over the API. That is exactly what these two calls do.
The declaración responsable
Art. 13.2Hands out the declaration of responsibility for this invoicing system as plain text plus metadata. It describes the manufacturer of the software — us — not your organisation, and is therefore identical for every API key.
1curl https://service.invoice-api.xhub.io/api/v1/verifactu/declaracion \2 -H "Authorization: Bearer sk_live_abc123..."Watch hasPendingLegalReview. While it is true the wording still carries our internal legal-review marker: the document is then a draft, not a signed declaration. For a submission to the AEAT, wait until it reads false.
Response of the declaración
200 OK1{2 "text": "DECLARACIÓN RESPONSABLE\n\nEl productor del sistema informático de facturación ...",3 "version": "1.5.0",4 "hasPendingLegalReview": false,5 "producedAt": "2026-09-28T09:14:22.318Z"6}| Field | Type | Description |
|---|---|---|
text | string | The declaración responsable in full, as plain text (Spanish, art. 13.2). |
version | string | Software version the declaration was signed for — the version from IdSistemaInformatico. |
hasPendingLegalReview | boolean | true while the wording still carries our internal legal-review marker. The document is then a draft. |
producedAt | string (ISO-8601) | ISO-8601 timestamp of this rendering. |
The registro chain export
Art. 14Hands out every registro of one NIF's chain as ONE json document, ordered by sequence number. Each entry carries the frozen registro next to its huella — exactly the object the hash was computed over, passed through unchanged and never reshaped. The recipient can recompute the hash from it.
The export is scoped to the organisation behind the API key. A chain owned by someone else is not visible and comes back as an empty export — not as an error.
Above 5,000 entries a range is mandatory: from, to or both. Without it the API answers 400 and names the entry count. Both values are ISO-8601 and narrow by the entry's timestamp.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string (ISO-8601) | - | Only entries appended at or after this ISO-8601 instant. Mandatory — alone or together with to — once the chain exceeds the export limit. |
to | string (ISO-8601) | - | Only entries appended at or before this ISO-8601 instant. |
Example
1# The whole chain of one NIF2curl "https://service.invoice-api.xhub.io/api/v1/verifactu/chain/B12345678/export" \3 -H "Authorization: Bearer sk_live_abc123..."4 5# Narrowed to a range — mandatory above 5000 entries6curl "https://service.invoice-api.xhub.io/api/v1/verifactu/chain/B12345678/export?from=2026-01-01T00:00:00Z&to=2026-03-31T23:59:59Z" \7 -H "Authorization: Bearer sk_live_abc123..."Response of the chain export
200 OK1{2 "vatId": "B12345678",3 "exportedAt": "2026-09-28T09:20:04.771Z",4 "from": "2026-01-01T00:00:00.000Z",5 "to": "2026-03-31T23:59:59.000Z",6 "entryCount": 2,7 "entries": [8 {9 "sequenceNumber": 1,10 "invoiceId": "inv_01J8Z3K9QW",11 "invoiceNumber": "A/2026-0001",12 "huella": "3C1F9A72B84E05D6C7A1938E4F20B5D8A6C3E71F9B2D48065A1C7E39F2B8D4E60",13 "previousHuella": "",14 "recordType": "ALTA",15 "createdAt": "2026-01-03T08:41:12.004Z",16 "registro": {17 "IDFactura": {18 "IDEmisorFactura": "B12345678",19 "NumSerieFactura": "A/2026-0001",20 "FechaExpedicionFactura": "03-01-2026"21 },22 "ImporteTotal": "1785.00"23 },24 "status": "ACCEPTED",25 "resultCsv": "AAAgOKbwQ8iQ7wAAAAA",26 "lastError": null27 },28 {29 "sequenceNumber": 2,30 "invoiceId": "inv_01J8Z4M2RT",31 "invoiceNumber": "A/2026-0002",32 "huella": "B7E2D46901C8A35F7E1B9D24608C5A3F1E7B9D2460C8A5F3E1B7D9460C2A8F5E3",33 "previousHuella": "3C1F9A72B84E05D6C7A1938E4F20B5D8A6C3E71F9B2D48065A1C7E39F2B8D4E60",34 "recordType": "ALTA",35 "createdAt": "2026-01-04T10:02:55.612Z",36 "registro": {37 "IDFactura": {38 "IDEmisorFactura": "B12345678",39 "NumSerieFactura": "A/2026-0002",40 "FechaExpedicionFactura": "04-01-2026"41 },42 "ImporteTotal": "952.00"43 },44 "status": "PENDING",45 "resultCsv": null,46 "lastError": null47 }48 ]49}| Field | Type | Description |
|---|---|---|
vatId | string | The NIF this chain belongs to — the chain key. |
exportedAt | string (ISO-8601) | ISO-8601 timestamp of THIS export, not of the last entry. |
from | string | null | Lower bound of the exported range, or null. |
to | string | null | Upper bound of the exported range, or null. |
entryCount | integer | Number of entries in entries. |
entries | array | The entries, ordered by sequence number — shape below. |
One entry of the chain
| Field | Type | Description |
|---|---|---|
sequenceNumber | integer | Position in the chain, 1-based and gapless. |
invoiceId | string | Internal id of the invoice this entry belongs to. |
invoiceNumber | string | null | Invoice number as issued, or null if the entry predates numbering. |
huella | string | The entry hash (huella) as stored — recomputable from registro. |
previousHuella | string | Huella of the preceding entry; an empty string for the first entry of the chain. |
recordType | string | null | ALTA for an issuance or ANULACION for a cancellation, as recorded. |
createdAt | string (ISO-8601) | ISO-8601 timestamp the entry was appended to the chain. |
registro | object | The frozen registro exactly as it went to the AEAT — the object the huella was computed over. Not reshaped. |
status | string | null | Submission state of the matching queue row, such as ACCEPTED or PENDING, or null. |
resultCsv | string | null | The AEAT CSV once the registro was accepted. |
lastError | string | null | Last submission error for this entry, verbatim. |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | Bad Request | Either from or to is not a valid ISO-8601 date, or the chain is too large to export without a range — the message names the entry count. |
| 401 | UNAUTHORIZED | API key missing or invalid, or the key has no organisation. |
| 502 | CONFIG_MISSING | The platform is missing mandatory VERIFACTU configuration. The message names every missing variable. |