Factuarea APIDevelopers

Registrar pagaments

Registra pagaments parcials, consulta el saldo en curs des del ledger i anul·la un pagament que s'ha retornat — sense emetre una rectificativa que ningú no t'ha demanat.

Les factures i les despeses mantenen un ledger de pagaments: una llista de pagaments individuals, cadascun amb el seu propi import, data i mètode. Registra els pagaments d'un en un a mesura que entra els diners — l'API recalcula els imports cobrat i pendent després de cada entrada, i la factura es reporta com a paid quan el saldo arriba a zero.

El ledger és de només addició. Els diners que van entrar i van tornar a sortir (una devolució de rebut, una retrocessió de targeta, un pagament imputat a la factura equivocada) no s'esborren: l'entrada s'anul·la, conserva el seu import, data, mètode i referència, i guanya un motiu, un instant i un autor. Un pagament anul·lat deixa de computar, així que la factura torna al circuit de cobrament.

El status que llegeixes a la factura es deriva del ledger, no s'emmagatzema: paid quan els pagaments vigents cobreixen l'import exigible, partially_paid mentre en cobreixen una part, i en qualsevol altre cas l'estat propi del document (sent, o overdue així que passa el seu venciment). Registrar o anul·lar un pagament el canvia en la lectura següent — no hi ha cap pas de sincronització que pugui quedar-se enrere.

Els dos imports que hi ha al darrere són paid_amount (suma dels pagaments vigents) i pending_amount (import exigible − paid_amount).

El cicle complet d'un pagament de venda és, doncs:

Registra'l — POST /v1/companies/{company}/invoices/{id}/payments.
Llegeix el saldo a la factura — paid_amount / pending_amount, o el sub-recurs del ledger.
Anul·la'l si els diners s'han retornat — POST /v1/companies/{company}/invoices/{id}/payments/{payment_id}/reversal. La factura torna a sent o a overdue i admet un pagament nou.

Registrar un pagament de venda

POST /v1/companies/{company}/invoices/{id}/payments afegeix un pagament a una factura de venda. El body és petit:

CampTipusRequeritNotes
amountnumberSíMés gran que 0. No pot superar pending_amount.
paid_onstring (YYYY-MM-DD)SíLa data en què es va rebre els diners. No pot ser posterior a avui (422); una data anterior a la d'emissió registra un cobrament anticipat.
payment_methodstring (enum)SíUn dels valors del catàleg (vegeu a sota).
referencestringNoLa teva pròpia referència (p. ex. un número de transferència).
notesstringNoNota interna lliure.

payment_method és un enum tancat de set valors: bank_transfer, direct_debit, cash, credit_card, check, paypal, other. Obtén el catàleg amb etiquetes des de GET /v1/payment-methods en lloc de fixar els valors a mà.

La resposta és 201 Created amb el pagament acabat de crear sota data:

{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
    "object": "payment",
    "invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
    "amount": 500.00,
    "payment_date": "2026-05-20",
    "payment_method": "bank_transfer",
    "payment_method_text": "Transferencia bancaria",
    "reference": "TRF-2026-0042",
    "notes": null,
    "is_reversed": false,
    "reversed_at": null,
    "reversal_reason": null,
    "reversal_reason_text": null,
    "reversal_note": null,
    "created_at": "2026-05-20T10:30:00Z",
    "updated_at": "2026-05-20T10:30:00Z"
  }
}

Els cinc camps revers* descriuen l'estat d'aquella entrada al ledger. Un pagament vigent informa is_reversed: false i null als altres quatre; consulta Anul·lar un pagament.

import os, requests

resp = requests.post(
    'https://api.factuarea.com/v1/companies/{company}/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments',
    json={
        'amount': 500.00,
        'paid_on': '2026-05-20',
        'payment_method': 'bank_transfer',
        'reference': 'TRF-2026-0042',
    },
    headers={'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}"},
)
resp.raise_for_status()
payment = resp.json()['data']
print(payment['id'], payment['amount'])
const res = await fetch(
  'https://api.factuarea.com/v1/companies/{company}/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: 500.0,
      paid_on: '2026-05-20',
      payment_method: 'bank_transfer',
      reference: 'TRF-2026-0042',
    }),
  },
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
console.log(data.id, data.amount);
curl -s -X POST \
  https://api.factuarea.com/v1/companies/{company}/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 500.00,
    "paid_on": "2026-05-20",
    "payment_method": "bank_transfer",
    "reference": "TRF-2026-0042"
  }' | jq '.data'

Pagaments parcials i saldo

El saldo en curs no viu a l'objecte del pagament — viu a la factura. Després de registrar un o diversos pagaments, llegeix la factura (GET /v1/companies/{company}/invoices/{id}) per veure com està:

{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
    "object": "invoice",
    "status": "sent",
    "total": 1210.00,
    "paid_amount": 500.00,
    "pending_amount": 710.00,
    "payments": {
      "detail": [
        {
          "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
          "object": "payment",
          "invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
          "amount": 500.00,
          "payment_date": "2026-05-20",
          "payment_method": "bank_transfer",
          "payment_method_text": "Transferencia bancaria",
          "reference": "TRF-2026-0042",
          "notes": null,
          "is_reversed": false,
          "reversed_at": null,
          "reversal_reason": null,
          "reversal_reason_text": null,
          "reversal_note": null,
          "created_at": "2026-05-20T10:30:00Z",
          "updated_at": "2026-05-20T10:30:00Z"
        }
      ],
      "total": 500.00,
      "pending": 710.00
    }
  }
}
  • paid_amount / pending_amount — els totals cobrat i pendent. Sempre presents, calculats a partir dels pagaments vigents: una entrada anul·lada continua a payments.detail, però ja no suma a paid_amount.
  • payments.total / payments.pending — les mateixes dues xifres, reflectides dins de l'objecte payments. Sempre presents.
  • payments.detail — l'array de pagaments individuals. Es materialitza només a l'endpoint de detall (GET /v1/companies/{company}/invoices/{id}); als endpoints de llistat arriba com a [] (mentre total i pending continuen poblats) perquè els llistats siguin lleugers. Fes servir el sub-recurs per obtenir el detall per separat.

Quan l'últim pagament tanca el saldo (pending_amount arriba a 0), la factura reporta status: "paid".

Un pagament l'amount del qual supera pending_amount es rebutja amb 422 i subcode: "payment_exceeds_pending_amount" (param: "amount"). Un pagament exactament igual a l'import pendent és vàlid i salda la factura. Consulta Errors.

Llistar pagaments

GET /v1/companies/{company}/invoices/{id}/payments retorna el ledger complet d'una factura, ordenat per data de pagament. Una factura sense pagaments retorna { "data": [] }, mai un 404.

{
  "data": [
    {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
      "object": "payment",
      "invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
      "amount": 500.00,
      "payment_date": "2026-05-20",
      "payment_method": "bank_transfer",
      "payment_method_text": "Transferencia bancaria",
      "reference": "TRF-2026-0042",
      "notes": null,
      "is_reversed": false,
      "reversed_at": null,
      "reversal_reason": null,
      "reversal_reason_text": null,
      "reversal_note": null,
      "created_at": "2026-05-20T10:30:00Z",
      "updated_at": "2026-05-20T10:30:00Z"
    }
  ]
}

Els pagaments anul·lats continuen en aquesta llista. La seva absència no és mai el senyal: el senyal és is_reversed: true. El codi que detecti una anul·lació perquè una entrada desapareix del ledger no s'activarà mai, perquè no s'esborra res. Filtra pel flag i, si recalcules el saldo pel teu compte, suma només les entrades amb is_reversed: false.

Anul·lar un pagament

POST /v1/companies/{company}/invoices/{id}/payments/{payment_id}/reversal anul·la un pagament d'una factura indicant-ne el motiu. El pagament no s'esborra: un pagament que va existir i va deixar de tenir efecte és informació comptable, i el seu rastre (motiu, instant i autor) és el que explica per què la factura va deixar d'estar cobrada.

CampTipusRequeritNotes
reasonstring (enum)SíUn dels cinc valors del catàleg tancat de sota.
notestringNoNota lliure, màx. 500 caràcters.

La resposta és 200 OK amb el pagament ja anul·lat sota data — la mateixa forma InvoicePaymentDetail que retornen l'alta i el llistat:

{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
    "object": "payment",
    "invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
    "amount": 500.00,
    "payment_date": "2026-05-20",
    "payment_method": "direct_debit",
    "payment_method_text": "Domiciliación bancaria",
    "reference": "TRF-2026-0042",
    "notes": null,
    "is_reversed": true,
    "reversed_at": "2026-06-02T08:15:00Z",
    "reversal_reason": "direct_debit_return",
    "reversal_reason_text": "Devolución de adeudo SEPA",
    "reversal_note": "Devuelto por el banco con motivo MD01.",
    "created_at": "2026-05-20T10:30:00Z",
    "updated_at": "2026-06-02T08:15:00Z"
  }
}

El catàleg de motius és tancat

reason és obligatori i només s'admeten aquests cinc valors. No existeix un valor refund a propòsit: un reemborsament genuí és una factura rectificativa, no una anul·lació (consulta Anul·lació o rectificativa?).

reasonQuan fer-lo servir
direct_debit_returnEl banc del client va retornar el rebut SEPA.
card_disputeEl pagament amb targeta es va retrocedir després d'una disputa.
misapplied_paymentEls diners van entrar, però es van imputar a la factura equivocada.
bounced_effectUn efecte o pagaré va resultar impagat al venciment.
recording_errorL'entrada va ser un error: mai no va correspondre a diners reals.

Qualsevol altre valor retorna 422 payment_reversal_reason_invalid amb param: "reason".

Un sisè valor del catàleg, issued_in_error, existeix però està reservat: només es fixa quan anul·les una factura emesa per error amb revert_collections: true (consulta Anul·lar o rectificar), i apareix al reversal_reason dels pagaments revertits d'aquesta manera. Demanar-lo aquí retorna 422 reversal_reason_reserved.

Què li passa a la factura

L'import anul·lat deixa de computar a paid_amount, pending_amount i a qualsevol agregat de tresoreria, així que una factura paid torna al circuit de cobrament en la lectura següent:

  • overdue si la seva data de venciment ja ha passat,
  • sent altrament,
  • i torna a admetre un pagament nou.

No hi ha manera de «despagar» una factura a mà: paid és una lectura del ledger, no un estat que es pugui escriure. L'endpoint genèric de canvi d'estat no l'admet com a destinació, precisament perquè el saldo cobrat i el ledger no puguin discrepar mai. Anul·lar un pagament és l'únic camí de tornada.

import os, requests

resp = requests.post(
    'https://api.factuarea.com/v1/companies/{company}/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01'
    '/payments/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b/reversal',
    json={
        'reason': 'direct_debit_return',
        'note': 'Devuelto por el banco con motivo MD01.',
    },
    headers={'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}"},
)
resp.raise_for_status()
payment = resp.json()['data']
print(payment['is_reversed'], payment['reversal_reason'])
const res = await fetch(
  'https://api.factuarea.com/v1/companies/{company}/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01' +
    '/payments/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b/reversal',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      reason: 'direct_debit_return',
      note: 'Devuelto por el banco con motivo MD01.',
    }),
  },
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
console.log(data.is_reversed, data.reversal_reason);
curl -s -X POST \
  https://api.factuarea.com/v1/companies/{company}/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b/reversal \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "direct_debit_return",
    "note": "Devuelto por el banco con motivo MD01."
  }' | jq '.data | {is_reversed, reversal_reason, reversal_reason_text}'

Irreversible. No hi ha des-anul·lació: anul·lar per segona vegada el mateix pagament retorna 422 payment_already_reversed, perquè trepitjar el primer rastre esborraria el registre de per què la factura va deixar d'estar cobrada. Si els diners van tornar a entrar, registra un pagament nou.

La ruta està imbricada a propòsit: a l'API v1 un pagament de venda només existeix penjant de la seva factura. Creuar una factura i un pagament que no es corresponen retorna 404 —no 422—, igual que un pagament d'una altra empresa: els tres casos col·lapsen en la mateixa resposta sense revelar quin d'ells era.

Errors de l'anul·lació

EstatCodiSignificat
422payment_reversal_reason_invalidreason és fora del catàleg tancat (param: "reason").
422payment_reversal_invalidnote no és vàlida — p. ex. supera els 500 caràcters (param: "note").
422payment_already_reversedEl pagament ja estava anul·lat.
404resource_not_foundFactura o pagament desconeguts, parella que no es correspon, o dades d'una altra empresa.

L'anul·lació necessita el scope invoices:write, el mateix que registra un pagament. No existeix una família de scopes payments:*: a l'API pública un pagament de venda és part de la seva factura, i la credencial que pot cobrar una factura és la que pot desfer aquest cobrament.

Anul·lació o rectificativa?

Aquesta és la decisió que importa, i equivocar-se té conseqüències fiscals. Anul·lar un pagament no és emetre una factura rectificativa.

  • Els diners van tornar, però l'operació no es minora — una devolució de rebut, una retrocessió de targeta, un efecte impagat, un pagament imputat a la factura equivocada. El client té els seus diners de tornada, però tu vas vendre el que vas vendre i encara t'ho deuen. Anul·la el pagament: el deute continua viu, la factura torna a sent o a overdue i reapareix al teu pendent de cobrament. Els ingressos no canvien, així que no hi ha res a rectificar.
  • El que es minora és l'operació — un reemborsament genuí, un descompte concedit a posteriori, una devolució de mercaderia, un import mal facturat. Aquí els ingressos sí que baixen, i això és el que documenta una factura rectificativa (POST /v1/companies/{company}/invoices/{id}/corrective). La factura original conserva el seu cobrament; la rectificativa és el document que minora la base imposable.
Què ha passatAnul·lar el pagamentFactura rectificativa
Devolució de rebut SEPAdirect_debit_return—
Retrocessió o disputa de targetacard_dispute—
Efecte impagatbounced_effect—
Pagament imputat a la factura equivocadamisapplied_payment—
Entrada que mai no va correspondre a diners realsrecording_error—
Reemborsament genuí al client—Sí
Descompte posterior, devolució de mercaderia—Sí
Import, impost o client equivocats—Sí

Una anul·lació mai no emet una rectificativa, i mai no toca el registre VeriFactu: la factura que vas enviar continua sent la factura que vas enviar. L'única cosa que va canviar va ser el seu cobrament.

Un rebut retornat no és un crèdit incobrable. Anul·lar un pagament no minora el teu IVA repercutit i no és la via de l'art. 80.Cuatro LIVA, que té els seus propis requisits formals — reclamació judicial o requeriment notarial, els seus propis terminis i una comunicació a l'Administració. Una integració que reaccioni a una devolució emetent una rectificativa declararà una minoració d'ingressos que no ha passat.

L'esdeveniment payment.reversed

Cada anul·lació emet payment.reversed, perquè una integració s'assabenti que un cobrament s'ha desfet sense haver-ho d'anar a preguntar. El payload porta el pagament anul·lat sota data.object més un bloc data.reversal amb el reason i l'origin: gateway quan va ser la passarel·la de pagament la que va informar de la devolució, disputa o retrocessió (ja hi va haver moviment real al banc), i manual quan ho va registrar una persona.

Subscriu-t'hi allà on ja reacciones a payment.received: una factura que vas donar per cobrada pot deixar d'estar-ho i, fins que aquest esdeveniment va existir, no hi havia manera d'assabentar-se'n. Consulta Esdeveniments per al payload i Webhooks per a l'entrega i la signatura.

Pagaments de despesa

Les despeses mantenen el seu propi ledger (total_retention, la retenció IRPF agregada, viu al recurs de la despesa). El contracte és asimètric respecte al de venda — llegeix-lo amb atenció abans de reutilitzar codi:

  • POST /v1/companies/{company}/purchase-invoices/{id}/payments retorna 201 amb el pagament creat sota data (objecte purchase_invoice_payment), no la despesa completa.
  • GET /v1/companies/{company}/purchase-invoices/{id}/payments retorna { "data": [...] }, del més recent al més antic.
  • No hi ha endpoint d'anul·lació al costat de compra: anul·lar és una operació de la factura, perquè el que restitueix és un deute a favor teu.
  • El body afegeix un bank_account_id opcional (enter), i aquí payment_method és un string lliure (màx. 30 caràcters), no l'enum tancat que es fa servir al costat de venda.
CampTipusRequeritNotes
amountnumberSíMés gran que 0. No pot superar l'import pendent.
paid_onstring (YYYY-MM-DD)SíNo posterior a avui (422). Una data anterior a la d'emissió del document del proveïdor s'accepta com a pagament anticipat.
payment_methodstringSíText lliure, màx. 30 caràcters.
bank_account_idintegerNoCompte bancari des del qual es va fer el pagament.
referencestringNoLa teva pròpia referència.
notesstringNoNota interna lliure.
{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0c",
    "object": "purchase_invoice_payment",
    "amount": 423.50,
    "paid_on": "2026-05-21",
    "payment_method": "transferencia",
    "bank_account_id": 12,
    "reference": "TRF-2026-0099",
    "notes": null,
    "created_at": "2026-05-21T09:00:00Z"
  }
}
import os, requests

resp = requests.post(
    'https://api.factuarea.com/v1/companies/{company}/purchase-invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a05/payments',
    json={
        'amount': 423.50,
        'paid_on': '2026-05-21',
        'payment_method': 'transferencia',
        'bank_account_id': 12,
    },
    headers={'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}"},
)
resp.raise_for_status()
print(resp.json()['data']['id'])
const res = await fetch(
  'https://api.factuarea.com/v1/companies/{company}/purchase-invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a05/payments',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: 423.5,
      paid_on: '2026-05-21',
      payment_method: 'transferencia',
      bank_account_id: 12,
    }),
  },
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
console.log(data.id);
curl -s -X POST \
  https://api.factuarea.com/v1/companies/{company}/purchase-invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a05/payments \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 423.50,
    "paid_on": "2026-05-21",
    "payment_method": "transferencia",
    "bank_account_id": 12
  }' | jq '.data'

Les regles de pagament de la despesa s'apliquen com a 422: un import per sobre del saldo pendent (subcode: "payment_exceeds_pending_amount"), una data posterior a avui (subcode: "invalid_payment_date"; una data anterior a la d'emissió s'accepta com a pagament anticipat), o un pagament sobre una despesa cancel·lada (subcode: "purchase_invoice_not_payable").

Mètodes de pagament

GET /v1/payment-methods retorna el catàleg tancat que dona suport al camp payment_method de venda, cadascun amb un value i una etiqueta llegible (en castellà). És un catàleg d'enum global — no específic d'empresa.

{
  "data": [
    { "value": "bank_transfer", "label": "Transferencia bancaria" },
    { "value": "direct_debit",  "label": "Domiciliación bancaria" },
    { "value": "cash",          "label": "Efectivo" },
    { "value": "credit_card",   "label": "Tarjeta de crédito" },
    { "value": "check",         "label": "Cheque" },
    { "value": "paypal",        "label": "PayPal" },
    { "value": "other",         "label": "Otro" }
  ]
}

Llegeix-lo un cop en arrencar i mostra les etiquetes a la teva interfície; retorna el value a payment_method.

Errors

  • 422 payment_exceeds_pending_amount — l'import és més gran que el saldo pendent (param: "amount"). És una violació de regla de negoci, així que és 422, mai 409.
  • 422 payment_reversal_reason_invalid — el reason de l'anul·lació és fora del catàleg tancat (param: "reason").
  • 422 payment_reversal_invalid — la note de l'anul·lació no és vàlida, p. ex. supera els 500 caràcters (param: "note").
  • 422 payment_already_reversed — aquest pagament ja estava anul·lat; registra'n un de nou en comptes de desfer l'anul·lació.
  • 409 en un POST de pagament es reserva per a l'embolcall estàndard d'idempotència / conflicte (un Idempotency-Key reutilitzat amb un body diferent, o un conflicte de concurrència) — no per a les dades del pagament en si.

Consulta Errors per a l'embolcall complet i el catàleg de codis.

En aquesta pàgina

Et donem un cop de mà?Contactar amb suport