Factuarea API
Conceptos clave

Registrar pagos

Registra pagos parciales contra facturas y facturas de compra, y consulta el saldo en curso desde el ledger.

Las facturas y las facturas de compra mantienen un ledger de pagos: una lista de pagos individuales, cada uno con su propio importe, fecha y método. Registra los pagos de uno en uno a medida que entra el dinero — la API recalcula los importes cobrado y pendiente después de cada entrada y pasa el documento a paid cuando el saldo llega a cero.

No existe un estado «parcialmente pagada» aparte. El avance del cobro se lee a partir de dos campos derivados, de solo presentación, en la factura: paid_amount (suma del ledger) y pending_amount (total − paid_amount). Un documento con pending_amount > 0 sigue pending; aquel cuyo pending_amount llega a 0 pasa a paid.

Registrar un pago de venta

POST /v1/invoices/{id}/payments añade un pago a una factura de venta. El body es pequeño:

CampoTipoRequeridoNotas
amountnumberMayor que 0. No puede superar pending_amount.
paid_onstring (YYYY-MM-DD)La fecha en que se recibió el dinero.
payment_methodstring (enum)Uno de los valores del catálogo (ver abajo).
referencestringNoTu propia referencia (p. ej. un número de transferencia).
notesstringNoNota interna libre.

payment_method es un enum cerrado de siete valores: bank_transfer, direct_debit, cash, credit_card, check, paypal, other. Obtén el catálogo con etiquetas desde GET /v1/payment-methods en lugar de fijar los valores a mano.

La respuesta es 201 Created con el pago recién creado bajo 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,
    "created_at": "2026-05-20T10:30:00Z",
    "updated_at": "2026-05-20T10:30:00Z"
  }
}
import os, requests

resp = requests.post(
    'https://api.factuarea.com/v1/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/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/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'

Pagos parciales y saldo

El saldo en curso no vive en el objeto del pago — vive en la factura. Tras registrar uno o varios pagos, lee la factura (GET /v1/invoices/{id}) para ver cómo está:

{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
    "object": "invoice",
    "status": "pending",
    "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,
          "created_at": "2026-05-20T10:30:00Z",
          "updated_at": "2026-05-20T10:30:00Z"
        }
      ],
      "total": 500.00,
      "pending": 710.00
    }
  }
}
  • paid_amount / pending_amount — los totales cobrado y pendiente. Siempre presentes, calculados a partir del ledger.
  • payments.total / payments.pending — las mismas dos cifras, reflejadas dentro del objeto payments. Siempre presentes.
  • payments.detail — el array de pagos individuales. Se materializa solo en el endpoint de detalle (GET /v1/invoices/{id}); en los endpoints de listado llega como [] (mientras total y pending siguen poblados) para que los listados sean ligeros. Usa el sub-recurso para obtener el detalle por separado.

Cuando el último pago cierra el saldo (pending_amount llega a 0), la factura pasa a paid.

Un pago cuyo amount supera pending_amount se rechaza con 422 y subcode: "payment_exceeds_pending_amount" (param: "amount"). Un pago exactamente igual al importe pendiente es válido y salda la factura. Consulta Errores.

Listar pagos

GET /v1/invoices/{id}/payments devuelve el ledger completo de una factura, del más reciente al más antiguo. Una factura sin pagos devuelve { "data": [] }, nunca 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,
      "created_at": "2026-05-20T10:30:00Z",
      "updated_at": "2026-05-20T10:30:00Z"
    }
  ]
}

Pagos de factura de compra

Las facturas de compra mantienen su propio ledger (total_retention, la retención IRPF agregada, vive en el recurso de la factura de compra). El contrato es asimétrico respecto al de venta — léelo con atención antes de reutilizar código:

  • POST /v1/purchase_invoices/{id}/payments devuelve 201 con el pago creado bajo data (objeto purchase_invoice_payment), no la factura completa.
  • GET /v1/purchase_invoices/{id}/payments devuelve { "data": [...] }, del más reciente al más antiguo.
  • El body añade un bank_account_id opcional (entero), y aquí payment_method es un string libre (máx. 30 caracteres), no el enum cerrado que se usa en el lado de venta.
CampoTipoRequeridoNotas
amountnumberMayor que 0. No puede superar el importe pendiente.
paid_onstring (YYYY-MM-DD)Entre la fecha de emisión y hoy.
payment_methodstringTexto libre, máx. 30 caracteres.
bank_account_idintegerNoCuenta bancaria desde la que se hizo el pago.
referencestringNoTu propia referencia.
notesstringNoNota interna libre.
{
  "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/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/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/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'

Las reglas de pago de la factura de compra (BR-PUR-019) se aplican como 422: un importe por encima del saldo pendiente (subcode: "payment_exceeds_pending_amount"), una fecha fuera de fecha_emisión … hoy (subcode: "invalid_payment_date"), o un pago sobre una factura cancelada (subcode: "purchase_invoice_not_payable").

Métodos de pago

GET /v1/payment-methods devuelve el catálogo cerrado que respalda el campo payment_method de venta, cada uno con un value y una etiqueta legible (en español). Es un catálogo de enum global — no específico de 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" }
  ]
}

Léelo una vez al arrancar y muestra las etiquetas en tu interfaz; devuelve el value en payment_method.

Errores

  • 422 payment_exceeds_pending_amount — el importe es mayor que el saldo pendiente (param: "amount"). Es una violación de regla de negocio, así que es 422, nunca 409.
  • 409 en un POST de pago se reserva para el envoltorio estándar de idempotencia / conflicto (un Idempotency-Key reutilizado con un body distinto, o un conflicto de concurrencia) — no para los datos del pago en sí.

Consulta Errores para el envoltorio completo y el catálogo de códigos.

En esta página