Ir al contenido

Convenciones de la API ​

Reglas comunes a todos los endpoints: formato, paginación, filtros, idempotencia y cómo el backend resuelve los valores por defecto.

Base y formato ​

Base URL:  https://api.test.bilteo.com/api/v1
  • Transporte: HTTPS. JSON en petición y respuesta (Content-Type: application/json).
  • Autenticación: Authorization: Bearer bk_live_… o Bearer bo_at_… — ver Autenticación.
  • IDs: UUID (los de Bilteo). Para direccionar tus recursos por tu propio identificador, ver external_id.

Importes: decimales como string ​

Todos los importes y cantidades viajan como string, no como número JSON, para no perder precisión:

json
{ "quantity": "1", "unit_price": "100.00", "discount": "10" }

En la respuesta los recibes igual ("base": "90.00"). Al enviarlos, una cadena decimal válida es suficiente; no hace falta fijar los decimales.

Fechas ​

  • Fechas de operación: YYYY-MM-DD (p. ej. operation_date: "2026-01-15").
  • Marcas de tiempo (created_at, updated_at, issued_at, sent_at): RFC 3339 en UTC (2026-01-15T10:00:00Z).

Paginación por cursor ​

Los listados (GET de colección) devuelven un sobre con un cursor opaco:

json
{
  "data": [ /* … */ ],
  "next_cursor": "AAECAwQFBgcICQoLDA0ODw",
  "has_more": true
}
ParámetroDescripción
limitResultados por página. 1–100, por defecto 50.
cursorCursor opaco. Pasa aquí el next_cursor de la respuesta anterior.
Campo de la respuestaDescripción
dataEl array de recursos de esta página.
next_cursorEl cursor de la siguiente página. Pásalo como ?cursor= en la próxima petición.
has_moretrue si quedan más páginas; cuando es false ya estás en la última.

Recorrer las páginas ​

1ª petición — sin cursor (primera página):

bash
curl "https://api.test.bilteo.com/api/v1/invoices?limit=2" \
  -H "Authorization: Bearer bk_live_xxx"
json
{
  "data": [ { "id": "0192aa…" }, { "id": "0192bb…" } ],
  "next_cursor": "AAECAwQFBgcICQoLDA0ODw",   // ← cópialo
  "has_more": true                            // ← hay más páginas
}

2ª petición — pasa ese next_cursor en ?cursor=:

bash
curl "https://api.test.bilteo.com/api/v1/invoices?limit=2&cursor=AAECAwQFBgcICQoLDA0ODw" \
  -H "Authorization: Bearer bk_live_xxx"
json
{
  "data": [ { "id": "0192cc…" }, { "id": "0192dd…" } ],
  "next_cursor": "BQYHCAkKCwwNDg8QERIT",
  "has_more": false                           // ← última página: deja de paginar
}

Repite el paso 2 (usando cada vez el next_cursor de la respuesta anterior) hasta que has_more sea false. El limit puedes mantenerlo constante entre páginas.

El cursor es opaco

No lo parsees ni lo construyas a mano: trátalo como un token. Pásalo tal cual lo recibiste. Un cursor mal formado devuelve 400 (pagination.invalid_cursor).

Filtros ​

Cada listado acepta filtros por query string. Se combinan con la paginación (?state=…&limit=50&cursor=…) y entre sí (AND):

RecursoFiltrosValores
GET /invoicesstate, type, partner_idstate: draft, issued, canceled, needs_fix… · type: F1,F2,F3,R1–R5 · partner_id: uuid
GET /external-invoicesstate, external_idstate: pending, registered, rejected, cancelled
GET /verifactu-recordsinvoice_id, statestate: pending, submitted, accepted, rejected, error · invoice_id: uuid
GET /partnerstype, is_active, search, external_idtype: per,com… · is_active: true/false
GET /productsis_active, search, external_idis_active: true/false
GET /seriestype, is_activetype: tipo de serie · is_active: true/false
GET /taxestype, tax_use, is_activetax_use: sale,purchase,none
GET /economic-activitiesis_activeis_active: true/false
  • search hace coincidencia parcial (ILIKE) sobre los campos de texto del recurso.
  • external_id es exacto y único por tenant, así que devuelve 0 o 1 resultado.
  • Los nombres exactos de los valores de cada filtro están en la Referencia de la API (cada parámetro declara su esquema).

Idempotencia ​

Hay dos mecanismos complementarios, para problemas distintos:

MecanismoPara quéAlcance
Cabecera Idempotency-KeyQue un reintento de una escritura no duplique el efectoTransitorio (24 h), por petición
external_idEnlace permanente entre tu registro y el de Bilteo (lookup/upsert)Persistente, por recurso

Reintentos seguros: Idempotency-Key ​

Si una petición de escritura (POST/PUT/PATCH/DELETE) se corta por un timeout o un error de red, no sabes si llegó a ejecutarse. Para reintentarla sin riesgo de duplicar el efecto (crear dos facturas, emitir dos veces…), manda una cabecera Idempotency-Key con un valor opaco único por operación (p. ej. un UUID v4):

bash
curl -X POST "https://api.test.bilteo.com/api/v1/external-invoices" \
  -H "Authorization: Bearer bk_live_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7e9f…-uuid-por-operación" \
  -d '{ "numero": "1", "fecha_expedicion": "2026-06-13", "tipo_factura": "F1", … }'

Bilteo guarda la respuesta y, ante un reintento:

SituaciónResultado
Misma clave + mismo cuerpo, ya procesadaSe reproduce la respuesta original (mismo status y body) con la cabecera Idempotent-Replayed: true. No se re-ejecuta el efecto.
Misma clave en vuelo (otra petición igual aún procesándose)409 (idempotency.request_in_progress).
Misma clave + cuerpo distinto422 (idempotency.key_reused_with_different_body).

Una clave por operación

La clave es opcional: sin ella la escritura se procesa con normalidad. Genera un valor nuevo por cada operación lógica (no reutilices el mismo para facturas distintas) y reutiliza el mismo solo al reintentar esa misma operación. El alcance es tu tenant y se conserva 24 h.

Sincronización por external_id ​

Para mantener tus clientes y productos sincronizados desde cualquier sistema (ERP, e-commerce, TPV, tu propia app…) sin tener que guardar una tabla de equivalencias entre tus identificadores y los UUID de Bilteo, partners y products aceptan un external_id: tu propio identificador, único por tenant. Funciona igual para ambos recursos.

Con él direccionas el recurso por tu id en la ruta …/by-external-id/{external_id} (URL-encódealo), en lugar de por el UUID de Bilteo:

OperaciónEjemplo (igual sobre /products)
Crear-o-actualizar (upsert)PUT /partners/by-external-id/{ext} → 201 si lo creó, 200 si lo actualizó
LeerGET /partners/by-external-id/{ext}
Actualizar (parcial)PATCH /partners/by-external-id/{ext}
BorrarDELETE /partners/by-external-id/{ext}
BuscarGET /partners?external_id={ext} → 0 ó 1 resultado

El PUT es la pieza clave para sincronizar: manda siempre el mismo PUT {ext} con el estado actual del recurso en tu sistema y olvídate de si ya existía en Bilteo — reenviarlo converge al mismo estado (201 la primera vez, 200 las siguientes), así no duplicas ni necesitas comprobar antes si existe.

bash
# Cliente (partner)
curl -X PUT "https://api.test.bilteo.com/api/v1/partners/by-external-id/CLI-00042" \
  -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
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": "Licencia anual", "unit_price": "240.00", "main_tax_id": "…iva21…" }'

Luego, al crear facturas, referencia el cliente/producto por su UUID de Bilteo (el que te devuelve cualquiera de estas llamadas) o resuélvelo cuando lo necesites con el GET …/by-external-id/{ext}.

El external_id lo pones tú

Mándalo en el POST de creación, o deja que el PUT lo fije desde la ruta. Para borrarlo en un PATCH, envía "external_id": "". Si intentas usar uno que ya ocupa otro recurso, recibes 409 (partner.external_id_taken / product.external_id_taken).

external_id vs Idempotency-Key

Son cosas distintas: el external_id es el enlace permanente entre tu registro y el de Bilteo (consultable, reutilizable); la Idempotency-Key solo protege un reintento puntual de una escritura. El PUT por external_id ya es convergente de por sí, así que para sincronizar el catálogo no necesitas la cabecera.

Valores por defecto (serie, actividad, moneda) ​

Al crear una factura, serie_id, economic_activity_id y currency_code son opcionales. Si los omites, el backend resuelve los defaults del tenant con la misma cascada que la web:

CampoCascada
Actividad económicaLa única activa → el default del tenant → la primera activa.
SerieLa serie por defecto de la actividad para ese tipo de factura → la primera serie de ese tipo.
MonedaEl default del tenant → EUR.

Envíalos explícitamente solo cuando quieras fijar un valor concreto. Para saber qué serie elegirá una actividad, mira sus normal_serie_id / rectified_serie_id / simplified_serie_id en GET /economic-activities.

Códigos de estado HTTP ​

CódigoCuándo
200OK.
201Recurso creado.
204OK sin cuerpo (p. ej. DELETE).
400Petición mal formada o validación de campos.
401Credencial ausente, inválida o caducada.
403La key no tiene el scope requerido, o el recurso no es editable.
404El recurso no existe (o no es de tu tenant).
409Conflicto (identificador duplicado, recurso en uso…).
422La operación no puede completarse en el estado actual.

El cuerpo de todo error sigue el mismo sobre — ver Errores.

Bilteo · Documentación