Use the Facturovia API to integrate invoicing into your apps, web shops, or automations.
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 →All routes return JSON. IDs are UUIDs. Body dates are accepted as YYYY-MM-DD.
| Method | Path | Description |
|---|---|---|
| GET | /invoices | List invoices. Supports ?external_reference=ORDER-2847 for exact match. |
| POST | /invoices | Create a draft invoice. Accepts external_reference (optional, ≤ 255 chars). |
| GET | /invoices/:id | Get a single invoice with line items |
| POST | /invoices/:id/issue | Issue a draft invoice (status → issued) |
| POST | /invoices/:id/correct | Rectificative invoice that negates every original line |
| POST | /invoices/:id/rectify | Partial rectificative — only the items you specify (positive qty, server negates) |
| GET | /invoices/:id/pdf | Download PDF of an issued invoice |
| GET | /clients | List clients |
| POST | /clients | Create a client |
| POST | /clients/find-or-create | Find existing client by tax_id or create new one (idempotent) |
| GET | /quotes | List quotes. Supports ?external_reference= filter. |
| POST | /quotes | Create a quote. Accepts external_reference (optional). |
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();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>, ... } }Each key carries one of the following access levels:
The API returns standard HTTP codes:
401 — Invalid or missing key.402 — Plan limit reached or not allowed. Consider upgrading to Professional.403 — Key is read-only or role lacks permission.422 — Invalid data. The response lists offending fields.