Gestió d'errors
Embolcall d'error normalitzat, catàleg de type i code amb àncores estables, i estratègia de reintents.
Tota resposta d'error de l'API pública usa un embolcall JSON
consistent. L'estat HTTP indica la categoria general; el camp type
desambigua i el camp code apunta a la causa específica.
Embolcall
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "El campo client_id es obligatorio.",
"param": "client_id",
"request_id": "req_01HKQS5N8VR7QXJ9K3T6BWPMZA",
"doc_url": "https://docs.factuarea.com/guides/errors#parameter_invalid"
}
}Camps:
type— categoria general de l'error. Estable i enumerada (llista a sota).code— causa específica. Estable i enumerada.subcode— opcional. Present quan elcodeper si sol és ambigu: en els conflictes de duplicació409assenyala la clau duplicada exacta (p. ex.subcode: "tax_id_already_exists"), i en els errors de pagament402assenyala quin gate ha rebutjat la crida (p. ex.subcode: "webhooks_addon_required"). Com elcode, és estable i invariant entre idiomes i versions de l'API.message— text per a persones en castellà. No es garanteix estable entre versions; útil per a logging i visualització.param— opcional, present en errors de validació. Apunta al primer camp problemàtic. En errors de validació de diversos camps el conjunt complet és aerrors[](vegeu a sota).errors[]— opcional, present en errors de validació422. Llista tots els camps fallits (vegeu Errors de validació de diversos camps).details— opcional. Portaexisting_resource_iden els conflictes de duplicació409(vegeu Conflictes de duplicació) ipayment_setup_urlen els errors402que necessiten un mètode de pagament configurat (vegeu payment_required_error).doc_url— opcional. Enllaç a aquesta guia amb àncora alcodeespecífic (#{code}).request_id— identificador únic de la petició (req_<ULID>). Inclou-lo sempre quan contactis amb suport. També es retorna al header de respostaX-Request-Id.
L'objecte error sempre porta type, code i message; la resta
de camps són presents quan és rellevant.
Errors de validació de diversos camps
Un error de validació 422 reporta tots els camps fallits, no només
el primer. Els param/message plans continuen reflectint el primer
camp (per retrocompatibilitat), i errors[] porta un ítem per camp
fallit — així corregeixes tots en una sola petició en lloc d'una petició
per camp.
{
"error": {
"type": "invalid_request_error",
"code": "invalid_param_value",
"message": "El campo client_id es obligatorio.",
"param": "client_id",
"errors": [
{ "param": "client_id", "code": "parameter_missing", "message": "El campo client_id es obligatorio." },
{ "param": "issue_date", "code": "parameter_invalid_format", "message": "El formato de la fecha no es válido.", "expected_format": "YYYY-MM-DD" },
{ "param": "status", "code": "parameter_invalid_enum", "message": "El valor no es válido.", "allowed_values": ["draft", "sent", "paid"] }
],
"request_id": "req_01HKQS5N8VR7QXJ9K3T6BWPMZA",
"doc_url": "https://docs.factuarea.com/guides/errors#invalid_param_value"
}
}Cada ítem de errors[] porta:
param— el nom del camp fallit.code— un codi estable i machine-readable derivat de la regla de validació fallida (p. ex.parameter_missing,parameter_invalid_format,parameter_invalid_enum,parameter_invalid_integer).message— descripció llegible de l'error del camp.expected_format— opcional. Present només en errors de format; el patró esperat (p. ex.YYYY-MM-DD,uuid,email,url).allowed_values— opcional. Present només en errors d'enum; la llista de valors legals.
errors[] és purament additiu — les integracions que només llegeixen
param, code i message continuen funcionant sense canvis.
Conflictes de duplicació
Un conflicte de duplicació 409 (code: resource_already_exists amb un
subcode de tax_id_already_exists, external_id_already_exists o
sku_already_exists) retorna l'id del recurs preexistent a
details.existing_resource_id. Resol-lo amb un sol GET en lloc d'un
find_by_* addicional.
{
"error": {
"type": "conflict_error",
"code": "resource_already_exists",
"subcode": "tax_id_already_exists",
"message": "Ya existe un cliente con ese NIF.",
"details": { "existing_resource_id": "0193e2a1-7c4e-7b3a-9f21-2d6c8e5a1b40" },
"request_id": "req_01HKQS5NKW1C6W9T4G5HAIBZVM",
"doc_url": "https://docs.factuarea.com/guides/errors#resource_already_exists"
}
}Un GET /v1/clients/0193e2a1-7c4e-7b3a-9f21-2d6c8e5a1b40 retorna el
recurs existent (200). El mateix aplica en PUT quan un external_id
ja pertany a un altre recurs de l'empresa.
Detalls del problema — Problem Details (RFC 9457)
Envia Accept: application/problem+json per rebre el mateix error com un
document Problem Details de RFC 9457
amb Content-Type: application/problem+json. Amb
Accept: application/json, Accept: */* o sense header Accept obtens
l'embolcall pla de dalt.
{
"type": "https://docs.factuarea.com/errors/resource_already_exists",
"title": "Resource already exists",
"status": 409,
"detail": "Ya existe un cliente con ese NIF.",
"instance": "/v1/clients",
"code": "resource_already_exists",
"subcode": "tax_id_already_exists",
"details": { "existing_resource_id": "0193e2a1-7c4e-7b3a-9f21-2d6c8e5a1b40" },
"request_id": "req_01HKQS5NKW1C6W9T4G5HAIBZVM"
}type— la pàgina de documentació d'aquestcodeconcret, per exemplehttps://docs.factuarea.com/errors/resource_already_exists. Elcodeés l'únic segment variable, així que pots construir i comparar la URI pel teu compte. Abans era una sola URI compartida per tots els problemes: si hi compares, compara millor contracode, que no es mou mai.title— un resum humà breu del tipus de problema.status— el codi d'estat HTTP.detail— el missatge llegible.instance— el path del recurs afectat.
La variant problem+json no descarta cap dada estesa: code,
subcode, param, errors[], details, doc_url i request_id es
conserven com a membres d'extensió RFC 9457.
Missatges localitzats
El message (i el detail de problem+json) es localitza via el header
Accept-Language per als codis del catàleg estable. Els idiomes
suportats són es, en i ca, amb fallback a es quan el header és
absent o demana un idioma no suportat. El code i el subcode són
invariants entre idiomes — ramifica sempre per code, mai per
message.
Accept-Language: en → missatge en anglès
Accept-Language: ca-ES → missatge en català
(absent / Accept-Language: de) → missatge en castellà (fallback)Els missatges dinàmics emesos per excepcions de domini queden en castellà; només es localitzen els missatges del catàleg estable.
Tipus d'error
| type | HTTP | Descripció |
|---|---|---|
invalid_request_error | 400 o 422 | Payload malformat, paràmetres absents/invàlids o fallada de validació de negoci. |
authentication_error | 401 | L'API key falta, és invàlida, està revocada, ha expirat o la IP no és a la llista d'accés. |
payment_required_error | 402 | L'operació cobra diners i el pagament no s'ha pogut completar: no hi ha mètode de pagament configurat, el càrrec s'ha denegat, o cal una subscripció o un add-on que no està contractat. |
authorization_error | 403 | La key és vàlida però el scope no cobreix l'endpoint. |
permission_error | 403 | El pla de l'empresa no dona accés a la funcionalitat. |
not_found_error | 404 | El recurs sol·licitat no existeix o no pertany a l'empresa de la key. |
conflict_error | 409 | Conflicte de creació, lock d'idempotència o recurs duplicat (p. ex. un tax_id ja registrat). |
idempotency_error | 409 | Reutilització d'Idempotency-Key amb un payload diferent. |
rate_limit_error | 429 | Superada la quota per minut o mensual, o massa fallades d'autenticació. |
api_error | 500 | Error inesperat del backend. Els reintents poden ajudar; reporta a suport amb el request_id. |
service_unavailable_error | 503 | API pública deshabilitada via kill-switch, o caiguda d'una dependència (Stripe, mailer). |
402 i 403 no són intercanviables. Un 402
(payment_required_error) significa que l'operació és al teu abast i
que l'única cosa que s'hi interposa són els diners: configura un mètode de
pagament, resol el càrrec denegat, o contracta el pla o l'add-on al
qual es factura. Un 403 significa que l'accés mateix està denegat —o
la key no té el scope (authorization_error), o el pla de l'empresa no
inclou la funcionalitat (permission_error)— i cap reintent de
pagament no ho canvia. La parella addon_required (402, l'add-on no
està contractat) i addon_not_active (403, cap key no arriba a una
funcionalitat no contractada) és la que convé llegir dues vegades.
Les violacions de regles de negoci (transició d'estat invàlida, una acció no
permesa en l'estat actual del document) responen 422 amb
type: invalid_request_error i code: invalid_status_transition —
no 409. 409 conflict_error es reserva per a creació duplicada,
conflictes d'idempotència i locks de concurrència.
Catàleg de codes
L'àncora de cada encapçalament H3 coincideix exactament amb el valor del camp
code de l'embolcall. El doc_url que retorna l'API resol a la secció
específica. La llista de sota cobreix els codes que trobaràs a la pràctica;
la referència OpenAPI en viu documenta els codes exactes per endpoint.
Per a la referència completa de cada error code agrupat per bounded
context, amb el seu estat HTTP i type, consulta Tots els error codes.
invalid_request_error
parameter_invalid
Un paràmetre de la petició falta o és invàlid. param apunta al
camp problemàtic (p. ex. client_id, lines[0].quantity).
parameter_invalid_format
El format d'un valor és incorrecte per a la seva semàntica (regex, longitud, codificació, un UUID malformat, una data fora de format).
parameter_invalid_range
Un valor numèric o de data està fora del rang permès (p. ex. limit
fora d'1..100).
parameter_invalid_cursor
El cursor starting_after / ending_before no és un id de recurs
vàlid. Consulta Paginació.
parameter_unknown
El body conté un camp no documentat (en endpoints estrictes).
invalid_param_format
Va fallar una restricció de format en un camp tipat — p. ex. el header
Idempotency-Key o el header Factuarea-Version està malformat.
invalid_param_value
El valor no compleix una restricció (enum, format, regla semàntica).
invalid_period
El període de report sol·licitat és invàlid (p. ex. un trimestre/any que no existeix).
invalid_status_transition
La transició sol·licitada està prohibida per la màquina d'estats del document
(p. ex. enviar una factura que no està en un estat enviable). Les violacions
de regles de negoci com aquesta són 422, no 409.
invoice_already_paid
mark-paid sobre una factura ja pagada.
quote_already_accepted
Acció que entra en conflicte amb un pressupost ja acceptat.
business_rule_violation
Una invariant de domini va bloquejar l'operació. El subcode identifica la
regla i param el camp infractor. El fa servir el ledger de pagaments
(Registrar pagaments):
payment_exceeds_pending_amount(param: "amount") — l'import del pagament és més gran que el saldo pendent de la factura. S'aplica tant aPOST /v1/invoices/{id}/paymentscom aPOST /v1/purchase_invoices/{id}/payments.invalid_payment_date(param: "paid_on") — la data de pagament cau fora de la finestra permesadata_emissió … avui(factures de compra).purchase_invoice_not_payable(param: "status") — la factura de compra està cancel·lada i ja no admet pagaments.
{
"error": {
"type": "invalid_request_error",
"code": "business_rule_violation",
"subcode": "payment_exceeds_pending_amount",
"message": "El importe del pago (1.500,00 €) supera el importe pendiente de la factura (710,00 €).",
"param": "amount",
"doc_url": "https://docs.factuarea.com/guides/errors#business_rule_violation",
"request_id": "req_..."
}
}unsupported_format
El format d'exportació/report sol·licitat no està suportat.
insufficient_data_for_report
No hi ha prou dades per generar el report d'impostos sol·licitat.
signature_payload_too_large
La imatge de signatura de l'albarà supera la mida màxima.
authentication_error
missing_api_key
No hi ha header d'autenticació present (Authorization: Bearer o
X-API-Key).
invalid_api_key
La key no existeix o el secret no coincideix amb el hash emmagatzemat.
api_key_revoked
La key va ser revocada. Crea'n una de nova al dashboard.
too_many_auth_failures
S'han limitat fallades d'autenticació repetides des del teu client. Espera (back off) i verifica les teves credencials.
payment_required_error
Tot 402 ve d'una operació que cobra alguna cosa en el moment en què la
crides: un seient d'empresa gestionada, un seient d'empleat o un add-on.
Cap no es pot reintentar tal com està: resol abans el pagament i
repeteix la mateixa petició.
Nota de versió. Cinc d'aquests codis es van publicar abans que
existís aquesta categoria i se servien com a invalid_request_error.
Porten payment_required_error des de
Factuarea-Version: 2026-09-01 endavant:
payment_method_required, seat_charge_failed,
gestoria_plan_required, employee_seat_payment_method_required i
employee_seat_charge_failed. Les peticions en una versió anterior
conserven el type de sempre. error.code, error.subcode i l'estat
402 són idèntics a totes les versions: ramifica per code i no hauràs
de pensar en això.
payment_method_required
POST /v1/companies i els endpoints d'activació cobren un seient
immediatament, i la gestoria opera en mode real sense mètode de pagament
configurat. La resposta porta details.payment_setup_url: obre'l,
registra una targeta i repeteix la crida.
{
"error": {
"type": "payment_required_error",
"code": "payment_method_required",
"message": "La gestoría no tiene un método de pago configurado: configúralo para añadir la empresa.",
"details": { "payment_setup_url": "https://billing.stripe.com/p/session/live_YWNjdF8xS2ZHM0RLb0h4RXBGV3lY" },
"request_id": "req_01HKQS5NPAYMENTMETHODREQ01",
"doc_url": "https://docs.factuarea.com/guides/errors#payment_method_required"
}
}seat_charge_failed
El càrrec prorratejat del seient de l'empresa gestionada va ser denegat —targeta rebutjada, autenticació requerida, o el proveïdor de pagament inaccessible—. L'empresa no es crea si el seient no es cobra. Arregla el mètode de pagament al portal de facturació i reintenta.
gestoria_plan_required
La gestoria no té una subscripció de pagament activa, així que no hi ha subscripció sobre la qual cobrar el seient. Contracta un pla (o reprèn el que va cancel·lar) abans d'afegir empreses gestionades.
employee_seat_payment_method_required
Donar d'alta o reactivar un empleat cobra un seient immediatament, i
l'empresa opera en mode real sense mètode de pagament configurat. El
remei és el mateix que a payment_method_required, i la resposta també
porta details.payment_setup_url.
employee_seat_charge_failed
El càrrec prorratejat del seient d'empleat va ser denegat. L'empleat no s'activa si el seient no es cobra. Arregla el mètode de pagament i reintenta; consulta amb el teu banc si la targeta es continua denegant.
addon_required
L'operació pertany a un add-on que l'empresa no ha contractat: per
exemple, POST /v1/webhook_endpoints requereix l'add-on Developer API,
el nivell gratuït del qual permet zero endpoints
(subcode: webhooks_addon_required). Contracta l'add-on i repeteix la
crida. A
diferència d'addon_not_active (403), aquí el que
falta és la contractació, no el scope.
authorization_error
insufficient_scope
La key no té el scope que requereix l'endpoint. Consulta el catàleg a Autenticació › Scopes.
permission_error
feature_not_available_in_plan
El pla actual no inclou el mòdul requerit (p. ex.
recurring_invoices).
addon_not_active
L'empresa no té un pla de Factuarea actiu que inclogui accés a l'API pública — per exemple, el trial de 10 dies va caducar o la subscripció va vèncer fora del seu període de gràcia. Contracta o renova un pla per continuar fent servir l'API.
not_found_error
resource_not_found
El recurs no existeix o no pertany a la teva empresa.
tax_report_not_found
El report d'impostos sol·licitat no existeix.
conflict_error
resource_already_exists
Intent de crear un duplicat (p. ex. un tax_id ja registrat). El
subcode (p. ex. tax_id_already_exists) assenyala la clau duplicada.
resource_conflict
L'operació entra en conflicte amb l'estat actual del recurs (p. ex. una modificació concurrent).
max_api_keys_exceeded
L'empresa ha assolit el seu nombre màxim d'API keys actives.
idempotency_error
idempotency_key_reused
Mateix Idempotency-Key, body de petició diferent. Usa una key nova. Consulta
Idempotència.
rate_limit_error
rate_limit_exceeded
Vas superar la quota per minut o mensual del teu tier. El header
Retry-After indica els segons a esperar. Consulta
Límits de peticions.
api_error
internal_error
Error inesperat. Ja està capturat per la nostra banda, però comparteix el request_id
amb suport.
service_unavailable_error
service_unavailable
L'API pública no està disponible temporalment — deshabilitada globalment via kill-switch, en una finestra de manteniment, o una dependència (base de dades, mailer, Stripe) no està sana. Reintenta després d'un back-off curt.
Errors tipats amb el SDK oficial
Els SDKs de TypeScript i PHP mapegen aquest embolcall a una jerarquia
d'excepcions tipada, així ramifiques segons una classe (i llegeixes code, type,
param, request_id) en lloc de parsejar JSON. La teva API key mai no
s'inclou en cap excepció.
import {
FactuareaError,
ValidationError,
RateLimitError,
} from "@factuarea/sdk";
try {
await factuarea.invoices.create(body);
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.fields); // { client_id: ["obligatorio"], … }
} else if (error instanceof RateLimitError) {
console.error(error.retryAfter); // seconds to wait
} else if (error instanceof FactuareaError) {
console.error(error.code, error.type, error.requestId);
}
}La jerarquia també exporta AuthenticationError, NotFoundError,
ConflictError, ServerError i ConnectionError.
use Factuarea\Sdk\Models\Errors\ErrorThrowable;
try {
$factuarea->invoices->publicApiV1InvoicesCreate($body);
} catch (ErrorThrowable $e) {
$error = $e->container->error;
echo $error->type->value; // e.g. "invalid_request_error"
echo $error->code; // e.g. "parameter_invalid"
echo $error->param; // e.g. "client_id"
echo $error->requestId; // quote this to support
}Consulta SDKs › Gestió d'errors per veure la jerarquia completa. La política de reintents de sota l'apliquen automàticament ambdós SDKs.
request_id i suport
Tota resposta inclou un request_id. Adjunta'l a qualsevol tiquet o
petició a support@factuarea.com:
Subject: 422 on POST /v1/invoices — request_id req_01JBVH7K9Y4N3CDQ2EHJB1AGSVAmb el request_id correlacionem logs, mètriques i traces per
investigar ràpid.
Estratègia de reintents
4xxexcepte429→ no reintentis: l'error és a la petició. Corregeix-lo i reenvia.429→ respecta el headerRetry-After. Implementa back-off exponencial amb jitter.5xx→ back-off exponencial (2^n * 100ms) amb jitter, màxim 5 intents.
Stripe publica un patró canònic que també aplica aquí: stripe.com/docs/error-handling.