POST /api/expenses
Documentación API
Esta página documenta POST /api/extract-invoice: JSON con file_url pública; el servidor descarga el archivo y devuelve campos estructurados. Para subir un archivo directamente sin URL pública, usa POST /api/extract-invoice-file (formulario multipart). Autentica con una clave API del panel o un token de acceso de sesión (el mismo JWT que envía el navegador tras iniciar sesión). Las rutas de facturas guardadas están en la referencia principal del API.
Endpoint
- Método
POST- Ruta
/api/expenses- URL completa (producción)
https://TU_APP_HOST/api/expenses
Autenticación
Cada petición debe enviar un bearer token. El servidor acepta:
- Clave API — créala en el panel; las claves empiezan por
bf_live_. Usa la cadena tal cual como bearer token. - JWT de sesión — el mismo
access_token que la web tras iniciar sesión.
Authorization: Bearer <YOUR_API_KEY_OR_ACCESS_TOKEN>
Envía Content-Type: application/json con cuerpo JSON (ver abajo). No uses form-data en esta ruta.
Cuerpo de la petición
El campo JSON file_url debe apuntar a un PDF u otro formato admitido (incluidas imágenes cuando aplica OCR). El servidor descarga el archivo; la URL debe ser pública y pasar las comprobaciones de seguridad de inbill.dev.
{
"file_url": "https://example.com/invoices/invoice-2026-01.pdf",
"invoice_kind": "expense"
}file_url es obligatorio y debe ser una URL válida. Opcional invoice_kind: expense (por defecto) o revenue.
Ejemplos
cURL (clave API)
Sustituye YOUR_HOST y YOUR_API_KEY.
curl -X POST "https://YOUR_HOST/api/expenses" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"file_url":"https://cdn.example.com/docs/invoice.pdf","invoice_kind":"expense"}'cURL (JWT de sesión)
curl -X POST "https://YOUR_HOST/api/expenses" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"file_url":"https://example.com/invoice.pdf","invoice_kind":"expense"}'Node.js (fetch)
const res = await fetch("https://YOUR_HOST/api/expenses", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INBILL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
file_url: "https://example.com/invoice.pdf",
invoice_kind: "expense",
}),
});
const data = await res.json();
if (!res.ok) {
throw new Error(JSON.stringify(data.error ?? data));
}
return data;Python (requests)
import os
import requests
r = requests.post(
"https://YOUR_HOST/api/expenses",
headers={
"Authorization": f"Bearer {os.environ['INBILL_API_KEY']}",
"Content-Type": "application/json",
},
json={
"file_url": "https://example.com/invoice.pdf",
"invoice_kind": "expense",
},
timeout=120,
)
r.raise_for_status()
print(r.json())Respuesta correcta
200 OK con JSON. Los campos pueden ser null u omitirse; items por defecto es []. counterparty nombra a la contraparte en la factura. Un objeto meta opcional puede existir (p. ej. source: extracted | cache | duplicate, invoiceId) si la fila está en caché, deduplicada o recién guardada.
{
"invoice_number": "INV-12345",
"date": "2026-03-20",
"counterparty": "Acme Corp",
"total": 1200,
"subtotal": 1300,
"discount": 100,
"tax": 180,
"currency": "USD",
"items": [
{ "description": "Service", "quantity": 1, "price": 1020, "line_total": null }
],
"meta": { "source": "extracted", "invoiceId": "uuid" }
}Respuestas de error
Los errores usan JSON con un objeto error con code y message (a veces detalles de validación).
400 — 400 — VALIDATION (JSON inválido o no cumple el esquema) o INVALID_URL (URL no permitida).401 — 401 — UNAUTHORIZED — falta el bearer o no es válido.402 — 402 — QUOTA_EXCEEDED — cupo del plan agotado.422 — 422 — FETCH_FAILED (no se pudo descargar) o EXTRACTION_FAILED (error de análisis, OCR o modelo).500 — 500 — PERSIST_FAILED u otros errores al guardar uso.
Ejemplo de cuerpo de error:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid credentials"
}
}Cuotas
Gratis: extracciones correctas limitadas por ventana móvil (por defecto 30 días) y un tope por cuenta. Pro: cupo mensual del plan. Solo cuentan las ejecuciones correctas que pasan el registro de uso.