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://api.facturovia.com/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. Paginated: { data, total, page } (?page=1&limit=20). 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 |
| DELETE | /invoices/:id | Delete a draft invoice (unnumbered drafts only; 409 otherwise). Read-only keys get 403. |
| GET | /clients | List clients. Paginated: { data, total, page } (?page=1&limit=50, max 200); ?search= matches name, email or the canonical tax_id. |
| POST | /clients | Create a client |
| POST | /clients/find-or-create | Find the client whose canonical tax_id (uppercase, no separators) matches — among non-deleted clients, the oldest wins — or create it. 200 { client, created:false } · 201 { client, created:true } · 422 without tax_id/name. Requires an active subscription; the plan client limit applies even on a match. |
| GET | /quotes | List quotes. Paginated: { data, total, page } (?page=1&limit=20). Supports ?external_reference= filter. |
| POST | /quotes | Create a quote. Accepts external_reference (optional). |
apiDocs.findOrCreateDesc
// 1. Find or create a client by tax_id (idempotent)
const clientRes = await fetch('https://api.facturovia.com/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://api.facturovia.com/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://api.facturovia.com/api/invoices/${invoice.id}/issue`, {
method: 'POST',
headers: { 'Authorization': 'Bearer fv_live_...' },
});
const pdfRes = await fetch(`https://api.facturovia.com/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://api.facturovia.com/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://api.facturovia.com/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://api.facturovia.com/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.