API REST v1
API para desarrolladores
Conecta Faktu con tu tienda, tu CRM o tus scripts. Incluida en el plan Pro.
Antes de empezar
La API lee y crea los datos de una empresa: clientes, productos, presupuestos, gastos y borradores de factura, y marca facturas como cobradas. Todas las respuestas son JSON en UTF-8; los importes van en euros con dos decimales.
Nada fiscal por API. Ninguna llamada emite, anula ni rectifica una factura: eso se hace en Faktu, que es quien la registra en VeriFactu. Un borrador creado por API se revisa y se emite desde el panel.
URL base:
https://www.faktu.org/api/v1Autenticación
Crea una clave en Faktu, en Ajustes → Integraciones → API REST (solo la persona propietaria o un administrador del espacio). La clave se enseña una sola vez: guárdala en un gestor de secretos. Si se filtra, revócala allí mismo y crea otra.
Mándala en cada petición, siempre por HTTPS:
curl https://www.faktu.org/api/v1/clientes \
-H "Authorization: Bearer fk_live_…"Cada clave tiene un alcance:
lectura: soloGET.lecturayescritura: ademásPOST,PATCHyDELETE.
Endpoints
| Método | Ruta | Qué hace | Alcance |
|---|---|---|---|
| GET | /facturas | Listar facturas | lectura |
| GET | /facturas/{id} | Ver una factura | lectura |
| POST | /facturas | Crear un borrador de factura | escritura |
| POST | /facturas/{id}/cobrar | Marcar una factura como cobrada | escritura |
| GET | /clientes | Listar clientes | lectura |
| GET | /clientes/{id} | Ver un cliente | lectura |
| POST | /clientes | Crear un cliente | escritura |
| PATCH | /clientes/{id} | Cambiar un cliente | escritura |
| GET | /productos | Listar productos | lectura |
| GET | /productos/{id} | Ver un producto | lectura |
| POST | /productos | Crear un producto | escritura |
| PATCH | /productos/{id} | Cambiar un producto | escritura |
| GET | /presupuestos | Listar presupuestos | lectura |
| GET | /presupuestos/{id} | Ver un presupuesto | lectura |
| POST | /presupuestos | Crear un presupuesto | escritura |
| PATCH | /presupuestos/{id} | Cambiar un presupuesto | escritura |
| GET | /gastos | Listar gastos · módulo de gastos | lectura |
| GET | /gastos/{id} | Ver un gasto · módulo de gastos | lectura |
| POST | /gastos | Crear un gasto · módulo de gastos | escritura |
| PATCH | /gastos/{id} | Cambiar un gasto · módulo de gastos | escritura |
| GET | /empresa | Ver la empresa de la clave | lectura |
| POST | /webhooks | Suscribir una URL a eventos | escritura |
| DELETE | /webhooks/{id} | Cancelar una suscripción | escritura |
Para comprobar una clave, GET /empresa devuelve la empresa a la que da acceso y sus alcances.
La especificación completa (campos, tipos, filtros y errores de cada ruta) está en /api/v1/openapi.json (OpenAPI 3.1): impórtala en Postman, Insomnia o tu generador de clientes.
Crear un borrador de factura
curl -X POST https://www.faktu.org/api/v1/facturas \
-H "Authorization: Bearer fk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-1042" \
-d '{
"cliente": { "nombre": "Talleres Martín SL", "nif": "B12345674", "pais": "ES" },
"lineas": [
{ "concepto": "Revisión anual", "unidades": 1, "precio": 120, "iva": 21 }
]
}'Con cliente se usa el cliente que tenga ese NIF o se crea uno; con cliente_id, uno que ya exista. La respuesta (201) es la factura en estado borrador con un número provisional, que Faktu fija al emitirla.
Marcar una factura como cobrada
curl -X POST https://www.faktu.org/api/v1/facturas/{id}/cobrar \
-H "Authorization: Bearer fk_live_…"Solo facturas emitidas. Si ya estaba cobrada, devuelve la factura sin cambios.
Paginación y filtros
Los listados van del más reciente al más antiguo, 25 por página (limit hasta 100). Si has_more es true, pide la siguiente con cursor = next_cursor:
{
"object": "list",
"data": [ … ],
"has_more": true,
"next_cursor": "MjAyNi0xMC0xOVQxMzoyOTo…"
}Filtros: facturas por estado, fecha_desde, fecha_hasta, cliente_id y cobrada; presupuestos por estado, fechas y cliente; gastos por fechas, proveedor_id y categoria; clientes por nif y email (sin distinguir mayúsculas), y productos por sku. Un parámetro desconocido devuelve 400.
Reintentos seguros
Envía Idempotency-Key en los POST (hasta 255 caracteres; un id de pedido o un UUID). Si repites la petición con la misma clave en las siguientes 24 horas, recibes la misma respuesta (con Idempotent-Replayed: true) y no se crea nada dos veces. La misma clave con otra petición devuelve 422.
Límites
120 peticiones por minuto y clave. Cada respuesta lleva X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset; al pasarte, 429 con Retry-After en segundos. El cuerpo de una petición no puede pasar de 256 KB.
Errores
Todos los errores tienen la misma forma. Comprueba code; el texto puede cambiar.
{
"error": {
"code": "invalid_request",
"message": "El NIF no es válido. Revisa el número y la letra de control",
"details": [{ "path": "cliente.nif", "message": "…" }]
}
}| HTTP | code | Significa |
|---|---|---|
| 400 | invalid_request | La petición no es válida. |
| 401 | unauthorized | Falta la clave de API o no es válida. |
| 402 | plan_required | Esta función está incluida en el plan Pro. |
| 403 | forbidden | La clave no tiene permiso para esta operación. |
| 404 | not_found | No existe. |
| 405 | method_not_allowed | Método no permitido. |
| 409 | conflict | Conflicto con el estado actual del recurso. |
| 413 | payload_too_large | El cuerpo de la petición es demasiado grande. |
| 415 | unsupported_media_type | El cuerpo debe ser JSON (Content-Type: application/json). |
| 422 | idempotency_conflict | Esa Idempotency-Key ya se usó con otra petición. |
| 429 | rate_limited | Demasiadas peticiones. Espera un poco y vuelve a intentarlo. |
| 500 | internal_error | Error interno. Inténtalo de nuevo más tarde. |
| 503 | not_configured | Esta integración no está configurada en este servidor. |
Webhooks
En vez de preguntar cada poco, Faktu puede avisar a tu servidor cuando algo pasa. Se configuran en Ajustes → Integraciones → Webhooks (URL https y eventos). El cuerpo de cada aviso lleva el mismo objeto que devuelve la API.
| Evento | Cuándo |
|---|---|
| factura.borrador_creado | Borrador de factura creado |
| factura.emitida | Factura emitida |
| factura.cobrada | Factura cobrada |
| factura.anulada | Factura anulada |
| cliente.creado | Cliente creado |
| cliente.actualizado | Cliente actualizado |
| presupuesto.aceptado | Presupuesto aceptado |
| gasto.creado | Gasto creado |
| producto.creado | Producto creado |
Verificar la firma
Cada aviso lleva la cabecera Faktu-Signature: t=…,v1=…: t es la hora del envío (segundos Unix) y v1, el HMAC-SHA256 en hexadecimal de t + "." + cuerpo con el secreto whsec_… del endpoint. Calcúlalo sobre el cuerpo tal cual llega (sin volver a serializarlo), compáralo en tiempo constante y rechaza los avisos de hace más de 5 minutos.
import { createHmac, timingSafeEqual } from "node:crypto"
function firmaValida(cuerpo, cabecera, secreto) {
const partes = Object.fromEntries(cabecera.split(",").map((p) => p.split("=")))
const t = Number(partes.t)
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false
const esperada = createHmac("sha256", secreto).update(`${t}.${cuerpo}`).digest("hex")
const recibida = Buffer.from(partes.v1 ?? "", "utf8")
return recibida.length === esperada.length &&
timingSafeEqual(recibida, Buffer.from(esperada, "utf8"))
}Cada aviso indica su origen (panel, api, shopify…): si tu integración crea datos por API, ignora los avisos con origen: api para no entrar en bucle.
Suscribirse por API
Una herramienta puede crear su propio endpoint sin pasar por el panel (es lo que hace Faktu para Zapier). La respuesta trae el secreto una sola vez; para darse de baja, DELETE /webhooks/{id}. Por API solo se borran los endpoints creados por API.
curl -X POST https://www.faktu.org/api/v1/webhooks \
-H "Authorization: Bearer fk_live_…" \
-H "Content-Type: application/json" \
-d '{ "url": "https://tu-dominio.com/webhooks/faktu", "eventos": ["factura.emitida"] }'