2xx response uses the same application/json envelope:
codeis a stable, machine-readable identifier. Branch your integration on this value. It never changes meaning, so it is safe to compare against.detailis a human-readable sentence meant for logs and debugging. Treat it as informational only: the wording can change at any time, so never match on it.errorsis an array of field-level problems, present mainly oninvalid_request. Each entry has afield(the parameter that failed), acode(a short reason), and amessage(a human explanation). It is an empty array when no single field is at fault.doc_urllinks to this page, anchored on the section for the returnedcode, so a logged error carries a clickable reference to its documentation.
HTTP status codes
The HTTP status reflects the broad category of the failure, whilecode identifies the exact case:
- 4xx: the request was rejected as sent. Fix the request before retrying (bad input, missing or invalid key, resource not found).
- 5xx: the request failed on our side. It is safe to retry, ideally with backoff.
Handle unknown codes gracefully
This catalog grows over time. We may add newcode values, and new endpoints may return existing codes in new places. Treat the set as open: branch on the codes you know, and fall back to handling anything unrecognized by its HTTP status (for example, retry on 5xx, surface a generic message on 4xx) instead of failing.
Error codes
invalid_request
HTTP 400 The request was rejected during validation. Theerrors array lists each failing field with a reason, so you can map the failure back to a specific parameter.
Raised by: GET /v1/invoices · POST /v1/invoices · POST /v1/invoices/validate · POST /v1/number_series · GET /v1/number_series · POST /v1/customer_portal/sessions · POST /v1/tax_ids/validate.
unauthorized
HTTP 401 Authentication failed: the API key is missing, malformed, or revoked. Send a valid key as a bearer token in theAuthorization header. Each key is bound to a single environment (test or live).
no_active_subscription
HTTP 402 Live invoice issuance requires an active subscription. When the account has no active plan, live keys receive this error until a subscription is in place. Test-environment keys are unaffected. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
aeat_mandate_not_approved
HTTP 403 Live invoice issuance requires the company’s AEAT representation mandate to be approved, which happens during onboarding. Until then, live keys receive this error; test-environment keys can issue freely (test invoices are never sent to AEAT). Raised by:POST /v1/invoices · POST /v1/invoices/validate.
customer_not_found
HTTP 404 No customer matches the suppliedexternal_customer_id in the environment the API key belongs to. A customer recorded with a live key is not visible to a test key, and vice versa.
invoice_not_found
HTTP 404 No invoice matches the supplied identifier in the environment the API key belongs to. An invoice created with a live key is not visible to a test key, and vice versa. Raised by:GET /v1/invoices/{id} · GET /v1/invoices/{id}/download.
number_series_not_found
HTTP 404 No number series matches the supplied identifier in the environment the API key belongs to. A number series created with a live key is not visible to a test key, and vice versa. Raised by:GET /v1/number_series/{id} · DELETE /v1/number_series/{id}.
customer_ambiguous
HTTP 409 The environment has more than one integration, soexternal_customer_id on its own does not identify a single customer (the same id can exist in two integrations for different customers). Supply integration_id alongside external_customer_id to scope the session to one integration. You do not need it when the environment has a single integration. Find the id in the Finseed app under Configuración > Integraciones: open the integration and copy the “ID de integración” shown below its name. See Create a customer portal session for the full parameter reference.
Raised by: POST /v1/customer_portal/sessions.
idempotency_conflict
HTTP 409 The suppliedIdempotency-Key cannot be honored. This happens in three situations: the key was already used with a different request body; the original request with this key is still being processed, in which case retrying with the same key and body once it completes replays its response; or the original request ended in an unknown state, in which case the key stays blocked for up to 24 hours. If you never received a stored response, send the request again with a fresh key.
Raised by: POST /v1/invoices · POST /v1/number_series.
number_series_in_use
HTTP 409 The number series cannot be deleted because invoices have been issued on it or an integration is configured to use it. A series is immutable once created (there is no update endpoint), so the way to change numbering is to create a new series and issue future invoices on it. A series referenced by an integration can be deleted after the integration is pointed elsewhere. Raised by:DELETE /v1/number_series/{id}.
number_series_prefix_taken
HTTP 409 The prefix of the series being created is already taken in this environment. Prefixes are normalized before the uniqueness check (whitespace is removed and letters are uppercased), so two prefixes that only differ in casing or spacing are the same prefix. Pick a different prefix, or reuse the existing series. Raised by:POST /v1/number_series.
download_link_expired
HTTP 410 Thedownload_url returned by the download endpoint has expired or was altered. These links are time-limited bearer URLs; request a new one from the download endpoint rather than storing them indefinitely.
external_reference_invalid
HTTP 422 The external original reference is malformed: thenumber exceeds 60 characters or contains forbidden characters (", ', <, >, =), or issued_on is not a valid calendar date.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
external_reference_matches_existing_invoice
HTTP 422 The external number matches an invoice that was actually issued through Finseed. Rectify it as a local invoice withrectifies.invoice_id; rectifies.external is only for originals that never existed in Finseed.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
family_balance_below_zero
HTTP 422 A rectificativa por diferencias adjusts the original by a delta. This delta would make the accumulated total of the correction chain negative, which is not a valid invoice. Reduce the delta. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
insufficient_recipient_data
HTTP 422 The recipient block does not meet the legal minimums for a complete invoice (for example a too-short name or tax id, or an unrecognized country code). Fix the flagged recipient fields or issue a simplified invoice when no identified recipient exists. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
invalid_number_series
HTTP 422 Thenumber_series_id in the request body cannot be used to issue this invoice: it is malformed, belongs to another environment, or names a rectifying series (reserved for corrective invoices). List the usable series with the Number Series endpoints.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
invalid_recargo_equivalencia
HTTP 422 Therecargo_equivalencia flag requires every line to carry exactly one vat tax whose rate has a legal surcharge pairing (21%→5.2%, 10%→1.4%, 4%→0.5%). Lines with other tax types or unpaired VAT rates cannot be invoiced under this regime.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
invalid_rectifies_reference
HTTP 422 Therectifies block did not name exactly one original to correct. Supply either invoice_id for an invoice issued through Finseed, or external for one issued elsewhere — not both, and not neither.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
invalid_tax_data
HTTP 422 The combination of tax types, rates and computed amounts is one the tax agency rejects (for example a positive rate that yields a zero tax amount on a non-zero line). Review the tax on each line. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
invalid_tax_id
HTTP 422 The recipient tax id failed just-in-time validation. Either the format is invalid for its type, or the census lookup (AEAT for Spanish NIFs, VIES for EU VAT numbers) did not recognize the id together with the supplied name. Verify the id and use the exact name the recipient is registered under. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
invalid_tax_rate
HTTP 422 A line item carries a tax rate the tax agency (AEAT) would reject for that tax type on the current date, a rate on a rate-free type, or a missing rate on a rated type. Spanish VAT (vat) currently accepts 0, 4, 10 and 21 percent for invoices issued today; historical temporary rates (2, 5, 7.5) only applied to past operation windows.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
invoice_already_replaced
HTTP 422 The referenced invoice has already been fully replaced by a rectificativa por sustitución. Only the active head of the correction chain can be rectified; rectify the replacement instead. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
invoice_already_voided
HTTP 422 The referenced invoice was voided (anulada). A voided invoice is legally cancelled, so it cannot be corrected with a rectificativa. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
invoice_not_family_tip
HTTP 422 The referenced invoice is not the active head of its correction chain: a later rectificativa already corrects it. Rectify the current head of the chain instead. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
invoice_not_rectifiable_at_aeat
HTTP 422 The referenced invoice is not in a state the tax agency (AEAT) allows a rectificativa against: its registration failed, was skipped, or never happened. Only invoices that registered successfully can be rectified. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
invoice_pending_at_aeat
HTTP 422 The referenced invoice has not finished registering at the tax agency (AEAT). Poll it untilverifactu.status leaves pending, then rectify it.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
line_item_without_tax_items
HTTP 422 A rectificativa line item was submitted without a tax. Every line must declare its tax so the corrective document reconciles with the tax agency. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
reason_incompatible_with_recipient
HTTP 422 The chosenreason restricts which recipient tax id types are allowed. insolvency accepts a Spanish NIF, an EU VAT number, or an unregistered recipient; uncollectible accepts a Spanish NIF or an unregistered recipient. The recipient on this invoice does not meet that restriction.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
recipient_overrides_not_allowed_for_differences
HTTP 422 A rectificativa por diferencias inherits its recipient from the corrected invoice, so arecipient block cannot be supplied. Omit it, or issue a rectificativa por sustitución (method: "substitution") when the recipient must change.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
rectified_amounts_required_for_substitution
HTTP 422 When replacing an external original by substitution, the tax agency requires the original amounts (ImporteRectificación), which Finseed cannot compute because the original never existed here. Supply rectifies.external.rectified_amounts.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
rectified_invoice_not_found
HTTP 422 Therectifies.invoice_id does not match any invoice in the environment the API key belongs to. Use the opaque inv_ identifier of an invoice issued through Finseed; to correct an invoice issued elsewhere, use rectifies.external instead.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
rectifying_series_invalid
HTTP 422 A rectificativa must be issued on a rectifying series. Thenumber_series_id is malformed, belongs to another environment, or names a standard series. List your rectifying series with the Number Series endpoints, or omit it to use the environment default.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
replacement_total_negative
HTTP 422 A rectificativa por sustitución restates the full invoice, so its net total cannot be negative. Adjust the line items so the total is at or above zero. Raised by:POST /v1/invoices · POST /v1/invoices/validate.
simplified_amount_exceeded
HTTP 422 Spanish law caps simplified invoices at 3,000 EUR including tax. Above that amount the recipient must be identified: sendtype: "complete" with a full recipient block.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
simplified_incompatible_tax_type
HTTP 422 The invoice combinestype: "simplified" with a tax type that legally requires an identified recipient, such as es_inversion_sujeto_pasivo (reverse charge). Issue a complete invoice for these operations.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
tax_id_type_country_mismatch
HTTP 422 The suppliedtax_id_type and tax_id_country contradict each other. Each type constrains the country: es_nif and not_registered require ES; eu_vat requires an EU country other than ES; official_document, residence_certificate and other require a country other than ES; passport accepts any country. Fix the pair, or omit tax_id_country when the type implies it (or omit both to auto-detect them from the tax id).
Raised by: POST /v1/invoices · POST /v1/invoices/validate.
rate_limited
HTTP 429 The per-key rate limit for this endpoint was exceeded. The response carries aRetry-After header with the seconds to wait. Spread out bursts or batch your work; sustained limits are documented per endpoint.
Raised by: POST /v1/invoices/validate · POST /v1/tax_ids/validate.
internal_error
HTTP 500 An unexpected error occurred on our side. No partial work is persisted, so the request can be retried safely.tax_id_validation_unavailable
HTTP 503 Complete invoices validate the recipient tax id against the census (AEAT or VIES) at creation time. When that external service is down, creation fails with this code and nothing is persisted. Retry later; sending the sameIdempotency-Key makes the retry safe.
Raised by: POST /v1/invoices · POST /v1/invoices/validate.