Errors
Every /api/v1/* endpoint returns the same error envelope. error.code is stable, so your application can branch on it without depending on message wording.
The OAuth2 /oauth/token and /oauth/revoke endpoints use protocol errors (for example, {"error":"invalid_grant"} for an invalid code or refresh token), rather than this public API envelope. See Authentication and scopes.
Envelope
{
"error": {
"type": "validation_error",
"code": "invoice.nif_receptor_invalid",
"message": "The recipient tax ID has an invalid format",
"param": "partner.id_num"
}
}| Field | Description |
|---|---|
type | General error category. |
code | Stable machine-readable code. Branch on this field. |
message | Human-readable debugging guidance. Do not parse it. |
details | Optional structured error-specific data. |
param | Affected field, when applicable. |
Categories
type | HTTP | Meaning |
|---|---|---|
validation_error | 400 | Invalid body or parameters. |
authentication_error | 401 | Missing, invalid, or expired credential. |
permission_error | 403 | Missing scope or forbidden operation. |
not_found_error | 404 | The resource does not exist in the tenant. |
conflict_error | 409 / 422 | Conflict with current state or idempotency rules. |
billing_error | 402 | The plan reached a usage limit. |
verifactu_error | 422 | The AEAT would reject the operation. |
internal_error | 500 | Unexpected service error. |
Common codes
Authentication and permissions
code | When |
|---|---|
api_key.missing | The Authorization header is missing. |
api_key.invalid | The key does not exist or has been revoked. |
api_key.no_scopes | The key has no scopes. |
oauth.invalid_token | The OAuth access token is invalid, expired, or revoked. |
api_key.insufficient_scopes | The endpoint's required scope is missing from the key. |
oauth.insufficient_scopes | The OAuth token lacks the required scope. |
mutability.invoice_locked | An invoice outside draft state is being edited. |
plan_permission_required | The tenant plan does not include the feature. |
Plan limit (402)
plan_limit_reached includes metric, plan_code, limit, used, and, for monthly metrics, reset_at in details. Do not repeat the same operation until usage is released, a new month starts, or the plan changes.
Validation (400)
code | When |
|---|---|
invalid_body | JSON does not match the endpoint schema. |
param.invalid_uuid | A path UUID is malformed. |
pagination.invalid_cursor | The supplied cursor is invalid. |
invoices.partner_required | The invoice type requires partner_id. |
idempotency.key_too_long | Idempotency-Key exceeds 255 characters. |
Not found (404)
Codes include invoice.not_found, invoice_line.not_found, partner.not_found, product.not_found, serie.not_found, tax.not_found, economic_activity.not_found, verifactu_record.not_found, and external_invoice.not_found.
Conflict (409)
code | When |
|---|---|
partner.external_id_taken / product.external_id_taken | The external ID is already in use. |
partner.id_num_taken / product.name_taken | The identifier or name already exists. |
serie.code_taken | A series already uses that code. |
serie.in_use | The series has documents and cannot be deleted. |
serie.numbering_started | The series has numbered a document; only name and is_active may change. |
serie.type_immutable | A series type cannot be changed. |
serie.last_of_type | The last series of a type cannot be deleted. |
economic_activity.in_use | Invoices reference the activity. |
economic_activity.last_activity | The tenant's last activity cannot be deleted. |
idempotency.request_in_progress | A request with the same key is still running. |
issuance_authorization_required | Invoice issuance has not been enabled. |
external_invoices_not_enabled | Verifactu Direct API has not been enabled. |
Reusing an Idempotency-Key with a different body returns 422 and idempotency.key_reused_with_different_body. Use a new key per operation.
VeriFactu / unprocessable (422)
With type: verifactu_error, the request shape is valid but the AEAT would reject its data: inconsistent breakdown, a simplified invoice above its legal limit, or an unidentified recipient tax ID. message describes the cause.
Recommended handling
- Branch on
error.code, never onmessage. - For
401or403, inspect the key and scopes; do not retry unchanged. - For
409, read the current server state and reconcile it. - For
422during issuance, the invoice remains a draft: fix and retry. - Retry
idempotency.request_in_progressafter a few seconds; use a new key foridempotency.key_reused_with_different_body.