> ## Documentation Index
> Fetch the complete documentation index at: https://www.finseed.es/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Reference for the error codes the API can return, with their HTTP status and meaning.

Every non-`2xx` response uses the same `application/json` envelope:

```json theme={null}
{
  "code": "invalid_request",
  "detail": "The request is not valid. A parameter is missing, malformed, or out of range. See the errors array for the fields that failed.",
  "errors": [
    { "field": "limit", "code": "too_big", "message": "Must be 100 or less." }
  ],
  "doc_url": "https://www.finseed.es/docs/api-reference/errors#invalid_request"
}
```

* **`code`** is a stable, machine-readable identifier. Branch your integration on this value. It never changes meaning, so it is safe to compare against.
* **`detail`** is 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.
* **`errors`** is an array of field-level problems, present mainly on `invalid_request`. Each entry has a `field` (the parameter that failed), a `code` (a short reason), and a `message` (a human explanation). It is an empty array when no single field is at fault.
* **`doc_url`** links to this page, anchored on the section for the returned `code`, so a logged error carries a clickable reference to its documentation.

## HTTP status codes

The HTTP status reflects the broad category of the failure, while `code` 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 new `code` 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. The `errors` 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 the `Authorization` 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 supplied `external_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, so `external_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](/docs/api-reference/customer-portal/create-a-customer-portal-session#body-integration-id) for the full parameter reference.

Raised by: `POST /v1/customer_portal/sessions`.

### idempotency\_conflict

**HTTP 409**

The supplied `Idempotency-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**

The `download_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: the `number` 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 with `rectifies.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**

The `number_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**

The `recargo_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**

The `rectifies` 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 until `verifactu.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 chosen `reason` 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 a `recipient` 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**

The `rectifies.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. The `number_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: send `type: "complete"` with a full recipient block.

Raised by: `POST /v1/invoices` · `POST /v1/invoices/validate`.

### simplified\_incompatible\_tax\_type

**HTTP 422**

The invoice combines `type: "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 supplied `tax_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 a `Retry-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 same `Idempotency-Key` makes the retry safe.

Raised by: `POST /v1/invoices` · `POST /v1/invoices/validate`.
