Recetario
Ejemplos copia-y-pega de los flujos más habituales. Sustituye bk_live_xxx por tu key y los … por los UUID de tu tenant. Todos los importes van como string. Si usas OAuth2, sustituye bk_live_xxx por tu access token bo_at_… en los mismos ejemplos. Consulta Autenticación y scopes.
Comprobar la key
curl https://api.test.bilteo.com/api/v1/ping \
-H "Authorization: Bearer bk_live_xxx"
# → { "pong": true }Descubrir lo que necesitas
Antes de facturar, localiza los IDs de tu tenant:
# Impuestos de venta (para main_tax_id / retention_tax_id / surcharge_tax_id)
curl "https://api.test.bilteo.com/api/v1/taxes?tax_use=sale&is_active=true" \
-H "Authorization: Bearer bk_live_xxx"
# Series y actividades (opcional: si los omites, se aplican los defaults)
curl https://api.test.bilteo.com/api/v1/series -H "Authorization: Bearer bk_live_xxx"
curl https://api.test.bilteo.com/api/v1/economic-activities -H "Authorization: Bearer bk_live_xxx"F1 ordinaria: crear y emitir
# 1) Crear el borrador (serie/actividad/moneda por defecto si se omiten)
curl -X POST https://api.test.bilteo.com/api/v1/invoices \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{
"type": "F1",
"partner_id": "…",
"operation_date": "2026-01-15",
"lines": [
{ "description": "Consultoría enero", "quantity": "10", "unit_price": "75.00",
"main_tax_id": "…iva21…" }
]
}'
# → 201, { "id": "INV…", "state": "draft", "total": "907.50", … }
# 2) Emitir: numera, congela importes y encola la remisión a la AEAT
curl -X POST https://api.test.bilteo.com/api/v1/invoices/INV…/actions/issue \
-H "Authorization: Bearer bk_live_xxx"
# → 200, { "state": "issued", "number": "2026/0001", … }F2 simplificada (sin receptor)
curl -X POST https://api.test.bilteo.com/api/v1/invoices \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "type": "F2",
"lines": [{ "description": "Venta mostrador", "quantity": "1",
"unit_price": "30.00", "main_tax_id": "…iva21…" }] }'Varias líneas, descuento y dos tipos de IVA
El descuento es un porcentaje (0–100) por línea. Cada línea puede llevar un main_tax_id distinto: el desglose se agrupa por tipo.
curl -X POST https://api.test.bilteo.com/api/v1/invoices \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{
"type": "F1", "partner_id": "…",
"lines": [
{ "description": "Servicio (21%)", "quantity": "1", "unit_price": "1000.00",
"discount": "10", "main_tax_id": "…iva21…" },
{ "description": "Libro (4%)", "quantity": "2", "unit_price": "20.00",
"main_tax_id": "…iva4…" }
]
}'Con retención de IRPF
Añade retention_tax_id (un impuesto type: "retencion"). Resta del total a pagar:
curl -X POST https://api.test.bilteo.com/api/v1/invoices \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "type": "F1", "partner_id": "…",
"lines": [{ "description": "Servicio profesional", "quantity": "1",
"unit_price": "1000.00",
"main_tax_id": "…iva21…", "retention_tax_id": "…irpf15…" }] }'
# total = 1210.00, total_payable = 1060.00 (1000 + 210 IVA − 150 IRPF)Con recargo de equivalencia
Añade surcharge_tax_id (un impuesto type: "recargo") en las líneas afectadas. Es por línea, no por factura.
"lines": [{ "description": "Mercancía", "quantity": "1", "unit_price": "100.00",
"main_tax_id": "…iva21…", "surcharge_tax_id": "…re52…" }]Exenta, no sujeta, inversión del sujeto pasivo
A diferencia de Verifactu Direct API, aquí la clasificación fiscal no va en la petición: la lleva el impuesto que pones en main_tax_id. Localiza en GET /taxes el de la familia (type) correcta:
| Operación | Impuesto (type) a elegir |
|---|---|
| Exenta (art. 20: sanidad, educación, seguros…) | exento (con su exempt_reason) |
| No sujeta | no_sujeto / no_sujeto_loc |
| Inversión del sujeto pasivo (ISP) | sujeto_isp |
# Exenta: el impuesto exento lleva cuota 0 y su motivo de exención
-d '{ "type": "F1", "partner_id": "…",
"lines": [{ "description": "Formación bonificada", "quantity": "1",
"unit_price": "500.00", "main_tax_id": "…exento-art20…" }] }'
# Inversión del sujeto pasivo: el impuesto sujeto_isp no repercute IVA
-d '{ "type": "F1", "partner_id": "…",
"lines": [{ "description": "Ejecución de obra", "quantity": "1",
"unit_price": "1200.00", "main_tax_id": "…isp…" }] }'Intracomunitaria y exportación
La diferencia la marcan el país del cliente (en /partners) y el impuesto:
| Operación | País del cliente | Impuesto |
|---|---|---|
| Entrega intracom. de bienes (exenta art. 25) | otro país UE, con VAT-IES | exento (art. 25) |
| Servicios B2B intracom. (no sujetos por localización) | otro país UE | no_sujeto_loc |
| Exportación (fuera de la UE, exenta art. 21) | país no UE | exento (art. 21) |
# 1) Cliente UE con su VAT-IES intracomunitario
curl -X PUT "https://api.test.bilteo.com/api/v1/partners/by-external-id/EU-FR-01" \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "type": "com", "name": "Atelier Vionnet SARL",
"id_num": "FR47812904563", "country": "FR" }'
# 2) Entrega intracomunitaria de bienes (exenta)
curl -X POST https://api.test.bilteo.com/api/v1/invoices \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "type": "F1", "partner_id": "…id-del-cliente-UE…",
"lines": [{ "description": "Suministro de material", "quantity": "1",
"unit_price": "800.00", "main_tax_id": "…exento-art25…" }] }'Bilteo traduce impuesto + país al registro Veri*Factu correcto (exención, no sujeción por localización…). Detalle conceptual en Impuestos.
Cerrar el bucle Veri*Factu
La remisión es asíncrona. Lo recomendado es suscribirte a webhooks (verifactu.accepted / verifactu.rejected) y dejar que Bilteo te avise. Si prefieres consultar, sondea el registro hasta que deje de estar pendiente:
curl "https://api.test.bilteo.com/api/v1/verifactu-records?invoice_id=INV…" \
-H "Authorization: Bearer bk_live_xxx"
# state: pending → in-process → accepted | accepted_error | rejected | unknown_responseUn registro con respuesta firme (rejected, accepted, accepted_error) puede subsanarse.
Rectificar una factura emitida
# Por sustitución, error de derecho (F1 → R1). Crea la R1 en borrador.
curl -X POST https://api.test.bilteo.com/api/v1/invoices/INV…/actions/rectify \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "rectificative_type": "S", "rectification_cause": "law_error" }'
# → 201, { "type": "R1", "state": "draft", … } → edítala y emítelaDetalle del mapeo causa → R-type en Corregir o anular.
Convertir una simplificada en completa (F3)
Desde una F2 emitida, crea su sustitutiva (F3) para un cliente identificado (asistente "sustitutiva"); queda en borrador para revisar y emitir:
curl -X POST https://api.test.bilteo.com/api/v1/invoices/INV_F2…/actions/replace \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "partner_id": "…" }'
# → 201, { "type": "F3", "state": "draft", … }
# (?issue=true para emitir en la misma llamada; requiere invoices:issue)Subsanar un registro
Operación extraordinaria
Muy poco frecuente y fuera del flujo normal: solo cuando la AEAT rechazó un registro por un error de remisión (normalmente un bug de integración). No es una rectificativa.
Requiere invoices:fix e invoices:issue. El resultado depende del historial:
curl -X POST https://api.test.bilteo.com/api/v1/invoices/INV…/actions/request-fix \
-H "Authorization: Bearer bk_live_xxx"
# Nunca aceptada (último registro rejected):
# → 200, state: needs_fix → corrige las líneas → vuelve a emitir (issue)
# Alguna vez aceptada (accepted | accepted_error):
# → 200, state: issued (sin cambios) y se encola la subsanación con los
# datos actuales del cliente: edítalos ANTES con PATCH /partners/{id}Flujo completo en Subsanación de errores.
Anular
Operación poco habitual
Solo para una factura emitida por error (que nunca debió existir), no para corregir una factura válida — para eso, una rectificativa. No es parte del día a día.
curl -X POST https://api.test.bilteo.com/api/v1/invoices/INV…/actions/cancel \
-H "Authorization: Bearer bk_live_xxx"Sincronizar clientes y productos (external_id)
Mantén tus clientes y productos sincronizados desde cualquier sistema (ERP, e-commerce, TPV, tu propia app…) usando tu propio identificador (external_id) con el PUT upsert: mandas siempre el mismo PUT con el estado actual y te olvidas de si ya existía en Bilteo. Reejecutar es seguro — 201 si lo creó, 200 si lo actualizó —, así no duplicas ni mantienes una tabla de equivalencias con los UUID de Bilteo. Detalle y resto de operaciones (GET/PATCH/DELETE/búsqueda) en Convenciones → Sincronización por external_id.
# Cliente (partner)
curl -X PUT "https://api.test.bilteo.com/api/v1/partners/by-external-id/CLI-42" \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "type": "com", "name": "Comercial Aldealta SL", "id_num": "B85447761",
"country": "ES" }'
# Producto — idéntico, sobre /products (lleva sus impuestos; la línea los hereda al usar product_id)
curl -X PUT "https://api.test.bilteo.com/api/v1/products/by-external-id/SKU-9" \
-H "Authorization: Bearer bk_live_xxx" -H "Content-Type: application/json" \
-d '{ "name": "Suscripción anual", "unit_price": "240.00", "main_tax_id": "…iva21…" }'Después, al crear facturas, referencia el producto por su UUID de Bilteo (el que devuelve el PUT) o resuélvelo con GET /products/by-external-id/SKU-9.
Descargar la factura
# PDF (por defecto) o HTML según el Accept
curl "https://api.test.bilteo.com/api/v1/invoices/INV…/actions/print" \
-H "Authorization: Bearer bk_live_xxx" -H "Accept: application/pdf" -o factura.pdf
# Facturae 3.2.x (XML)
curl "https://api.test.bilteo.com/api/v1/invoices/INV…/actions/facturae" \
-H "Authorization: Bearer bk_live_xxx" -o factura.xml