← Back to home
API Reference

Facturovia API

Use the Facturovia API to integrate invoicing into your apps, web shops, or automations.

Authentication

Every request requires an Authorization header with your API key:

curl -H "Authorization: Bearer fv_live_YOUR_KEY" \
     https://facturo-production-c176.up.railway.app/api/invoices
Generate a key in Settings

Available endpoints

All routes return JSON. IDs are UUIDs. Body dates are accepted as YYYY-MM-DD.

MethodPathDescription
GET/invoicesList invoices. Supports ?external_reference=ORDER-2847 for exact match.
POST/invoicesCreate a draft invoice. Accepts external_reference (optional, ≤ 255 chars).
GET/invoices/:idGet a single invoice with line items
POST/invoices/:id/issueIssue a draft invoice (status → issued)
POST/invoices/:id/correctRectificative invoice that negates every original line
POST/invoices/:id/rectifyPartial rectificative — only the items you specify (positive qty, server negates)
GET/invoices/:id/pdfDownload PDF of an issued invoice
GET/clientsList clients
POST/clientsCreate a client
POST/clients/find-or-createFind existing client by tax_id or create new one (idempotent)
GET/quotesList quotes. Supports ?external_reference= filter.
POST/quotesCreate a quote. Accepts external_reference (optional).

Find or create

Look up a client by tax_id. If none exists yet, it's created automatically. Common pattern for e-commerce integrations: your storefront has the customer's tax_id, this endpoint returns a client_id you can use right away to create the invoice.

// 1. Find or create a client by tax_id (idempotent)
const clientRes = await fetch('https://facturo-production-c176.up.railway.app/api/clients/find-or-create', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer fv_live_...', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tax_id: 'B12345674',
    name: 'Talleres García S.L.',
    email: 'info@talleresgarcia.es',
    address: 'Calle Mayor 1',
    city: 'Madrid', postal_code: '28001', country: 'ES',
  }),
});
const { client, created } = await clientRes.json();
// created === true if new, false if it already existed

// 2. Create the invoice using the returned client.id
const invoiceRes = await fetch('https://facturo-production-c176.up.railway.app/api/invoices', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer fv_live_...', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    client_id: client.id,
    invoice_date: new Date().toISOString().split('T')[0],
    currency: 'EUR', exchange_rate: 1,
    items: [{
      description: 'Filtro de aceite Toyota Corolla',
      quantity: 2, unit_price: 15.99,
      vat_rate: 21, discount_percent: 0,
    }],
  }),
});
const invoice = await invoiceRes.json();

// 3. Issue the invoice and download the PDF
await fetch(`https://facturo-production-c176.up.railway.app/api/invoices/${invoice.id}/issue`, {
  method: 'POST',
  headers: { 'Authorization': 'Bearer fv_live_...' },
});
const pdfRes = await fetch(`https://facturo-production-c176.up.railway.app/api/invoices/${invoice.id}/pdf`, {
  headers: { 'Authorization': 'Bearer fv_live_...' },
});
const pdfBlob = await pdfRes.blob();

Returns and corrective invoices via external_reference

Invoices and quotes accept an optional external_reference field (≤ 255 chars) to store your own order id. You can later filter by it (?external_reference=ORDER-2847) to look up the original invoice without maintaining a mapping table on your side.

// E-commerce return / rectification workflow using external_reference

// 1. Storefront creates the original invoice with its order id
await fetch('https://facturo-production-c176.up.railway.app/api/invoices', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer fv_live_...', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    external_reference: 'ORDER-2847',
    client_id: client.id,
    invoice_date: new Date().toISOString().split('T')[0],
    currency: 'EUR', exchange_rate: 1,
    items: [
      { description: 'Filtro aceite REF-001',      quantity: 1, unit_price: 15.99, vat_rate: 21, discount_percent: 0 },
      { description: 'Correa distribución REF-045', quantity: 2, unit_price: 89.50, vat_rate: 21, discount_percent: 0 },
    ],
  }),
});

// 2. When the customer initiates a return, look up the original by your
//    own order id — no need to store the Facturovia invoice id locally.
const r = await fetch('https://facturo-production-c176.up.railway.app/api/invoices?external_reference=ORDER-2847', {
  headers: { 'Authorization': 'Bearer fv_live_...' },
});
const { data: [original] } = await r.json();

// 3. Issue a partial rectificativa for ONLY the returned items.
//    Quantities are positive — the server negates them. Items must
//    match a line on the original (by reference if provided, else by
//    description) and quantity is capped to the original quantity.
await fetch(`https://facturo-production-c176.up.railway.app/api/invoices/${original.id}/rectify`, {
  method: 'POST',
  headers: { 'Authorization': 'Bearer fv_live_...', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    items: [
      { description: 'Filtro aceite REF-001',      reference: 'REF-001', quantity: 1, unit_price: 15.99, vat_rate: 21, discount_percent: 0 },
      { description: 'Correa distribución REF-045', reference: 'REF-045', quantity: 2, unit_price: 89.50, vat_rate: 21, discount_percent: 0 },
    ],
    notes: 'Devolución parcial — piezas defectuosas',
    causa_rectificacion: 'R1',
  }),
});
// → 201 with { invoice: { id, status: "draft", is_corrective: true,
//                          original_invoice_id, total: <negative>, ... } }

Permissions

Each key carries one of the following access levels:

  • full: Full access. Can create, read and modify.
  • read: Read-only. Any non-GET request will be rejected with 403.

Error codes

The API returns standard HTTP codes:

  • 401Invalid or missing key.
  • 402Plan limit reached or not allowed. Consider upgrading to Professional.
  • 403Key is read-only or role lacks permission.
  • 422Invalid data. The response lists offending fields.