Factuarea API

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 el code per si sol és ambigu: en els conflictes de duplicació 409 assenyala la clau duplicada exacta (p. ex. subcode: "tax_id_already_exists"), i en els errors de pagament 402 assenyala quin gate ha rebutjat la crida (p. ex. subcode: "webhooks_addon_required"). Com el code, é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 a errors[] (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. Porta existing_resource_id en els conflictes de duplicació 409 (vegeu Conflictes de duplicació) i payment_setup_url en els errors 402 que necessiten un mètode de pagament configurat (vegeu payment_required_error).
  • doc_url — opcional. Enllaç a aquesta guia amb àncora al code específic (#{code}).
  • request_id — identificador únic de la petició (req_<ULID>). Inclou-lo sempre quan contactis amb suport. També es retorna al header de resposta X-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'aquest code concret, per exemple https://docs.factuarea.com/errors/resource_already_exists. El code é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 contra code, 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

typeHTTPDescripció
invalid_request_error400 o 422Payload malformat, paràmetres absents/invàlids o fallada de validació de negoci.
authentication_error401L'API key falta, és invàlida, està revocada, ha expirat o la IP no és a la llista d'accés.
payment_required_error402L'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_error403La key és vàlida però el scope no cobreix l'endpoint.
permission_error403El pla de l'empresa no dona accés a la funcionalitat.
not_found_error404El recurs sol·licitat no existeix o no pertany a l'empresa de la key.
conflict_error409Conflicte de creació, lock d'idempotència o recurs duplicat (p. ex. un tax_id ja registrat).
idempotency_error409Reutilització d'Idempotency-Key amb un payload diferent.
rate_limit_error429Superada la quota per minut o mensual, o massa fallades d'autenticació.
api_error500Error inesperat del backend. Els reintents poden ajudar; reporta a suport amb el request_id.
service_unavailable_error503API 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_transitionno 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 a POST /v1/invoices/{id}/payments com a POST /v1/purchase_invoices/{id}/payments.
  • invalid_payment_date (param: "paid_on") — la data de pagament cau fora de la finestra permesa data_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_01JBVH7K9Y4N3CDQ2EHJB1AGSV

Amb el request_id correlacionem logs, mètriques i traces per investigar ràpid.

Estratègia de reintents

  • 4xx excepte 429no reintentis: l'error és a la petició. Corregeix-lo i reenvia.
  • 429 → respecta el header Retry-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.

En aquesta pàgina