Autenticación y scopes
La API pública (/api/v1/*) acepta API keys y tokens OAuth2 en la cabecera Authorization: Bearer. Usa una API key para una integración propia entre servidores; usa OAuth2 cuando otra aplicación necesite que un usuario autorice el acceso a su empresa. La BFF (/app/*) se accede con la sesión del navegador por cookie sobre HTTPS: los tokens OAuth2 no autentican la BFF.
API key
La API pública se autentica con una API key del tenant, enviada como Bearer token:
Authorization: Bearer bk_live_xxxxxxxxxxxxxxxxbk_live_…→ tenant de producción.bk_test_…→ tenant de pruebas (remite a los servicios de test de la AEAT). Ver Entorno de pruebas.
Una clave inválida o ausente devuelve 401; una clave sin el scope requerido para la operación devuelve 403.
OAuth2 para aplicaciones externas
Las aplicaciones externas pueden conectar mediante authorization code con PKCE S256. Un administrador de Bilteo registra la aplicación en el admin con su nombre, las URL de retorno exactas y los scopes máximos. El client_id es público; no hay client secret. El usuario que autoriza necesita el permiso api_keys.admin en el tenant elegido.
Genera un code_verifier aleatorio (43–128 caracteres), el code_challenge = base64url(SHA256(code_verifier)) sin padding y un state aleatorio de al menos 16 caracteres. Redirige el navegador a la página de la aplicación https://<host-app>/oauth/authorize con response_type=code, client_id, redirect_uri, scope (separados por espacios), state, code_challenge y code_challenge_method=S256. Bilteo muestra el selector de empresa y los permisos para que el usuario acepte o cancele. Puedes enviar tenant_id si ya conoces la empresa; el token siempre queda ligado a una sola empresa. Si el usuario cancela, Bilteo vuelve a redirect_uri con error=access_denied y el state original.
Ejemplo de URL de autorización (codifica cada parámetro de consulta; en scope, los espacios se codifican como %20):
https://<host-app>/oauth/authorize?response_type=code&client_id=<uuid>&redirect_uri=https%3A%2F%2Fapp.ejemplo.com%2Fcallback&scope=invoices%3Aread%20partners%3Aread&state=<valor-aleatorio>&code_challenge=<desafio-base64url>&code_challenge_method=S256Tras aceptar, verifica el state recibido y canjea el code una sola vez antes de cinco minutos:
curl -X POST https://api.test.bilteo.com/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=<uuid>' \
--data-urlencode 'code=<codigo-recibido>' \
--data-urlencode 'redirect_uri=https://app.ejemplo.com/callback' \
--data-urlencode 'code_verifier=<verifier-original>'La respuesta incluye access_token, token_type (Bearer), expires_in (900 segundos), refresh_token y scope.
Usa Authorization: Bearer bo_at_... en /api/v1/*. El access token dura 15 minutos. Renuévalo con POST /oauth/token y los campos grant_type=refresh_token, client_id y refresh_token=bo_rt_.... Guarda el nuevo refresh token: cada uso lo rota y reutilizar uno anterior revoca toda la conexión. El refresh token vence a los 30 días. Para desconectar, envía client_id y token (access o refresh) a POST /oauth/revoke como formulario. La metadata está en /.well-known/oauth-authorization-server.
curl -X POST https://api.test.bilteo.com/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'client_id=<uuid>' \
--data-urlencode 'refresh_token=bo_rt_...'
curl -X POST https://api.test.bilteo.com/oauth/revoke \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<uuid>' \
--data-urlencode 'token=bo_rt_...'OAuth2 y API keys usan exactamente los mismos scopes y endpoints. El token OAuth queda ligado al tenant autorizado: no envíes X-Tenant para cambiarlo. Una petición sin autenticación o con un token vencido devuelve 401; si faltan scopes, 403. Consulta los errores de autenticación.
Identidad de la credencial
GET /api/v1/me dice a qué empresa está ligada la credencial. Funciona con API key y con token OAuth y no necesita ningún scope.
{
"tenant": {
"id": "…",
"name": "Peluquería Ejemplo SL",
"issuer_name": "Peluquería Ejemplo SL",
"issuer_id_num": "B12345678",
"livemode": true
},
"plan_code": "pro",
"auth": { "type": "oauth", "scopes": ["invoices:read", "invoices:write"] }
}livemode vale false en el tenant de pruebas. Con OAuth, llámalo justo después de obtener el token. El usuario elige la empresa en Bilteo y la respuesta del token no la incluye.
Scopes
Cada API key o conexión OAuth lleva un conjunto de scopes que delimitan qué puede hacer. Concede solo los necesarios (mínimo privilegio). Siguen el patrón recurso:acción; los catálogos de solo lectura únicamente exponen :read.
Esta es la lista completa (21 scopes):
| Scope | Permite |
|---|---|
invoices:read | Leer facturas y sus líneas |
invoices:write | Crear/editar borradores y sus líneas, y crear rectificativas |
invoices:issue | Emitir facturas (numerar + remitir a la AEAT) |
invoices:cancel | Anular facturas emitidas |
invoices:replace_simplified | Flujo de sustitutivas (F2 → F3): crear/emitir la F3 y gestionar sus relaciones replace |
invoices:fix | Subsanar el registro Veri*Factu de una factura emitida: la reabre (needs_fix) si nunca fue aceptado, o encola la subsanación directamente si lo fue. Se usa junto con invoices:issue |
verifactu:read | Consultar los registros Veri*Factu y las facturas externas (Verifactu Direct API) |
verifactu:write | Verifactu Direct API: registrar / anular / subsanar en Veri*Factu facturas ya emitidas en otro programa (/external-invoices) |
partners:read | Leer clientes |
partners:write | Crear, editar y borrar clientes (incluido por by-external-id) |
products:read | Leer productos |
products:write | Crear, editar y borrar productos (incluido por by-external-id) |
series:read | Leer series de numeración |
series:write | Crear, editar y borrar series |
economic_activities:read | Leer actividades económicas |
economic_activities:write | Crear, editar y borrar actividades económicas |
taxes:read | Leer el catálogo de impuestos (solo lectura) |
fiscal_positions:read | Leer el catálogo de posiciones fiscales (solo lectura) |
payment_terms:read | Leer las condiciones de pago (solo lectura) |
usage:read | Consultar el plan efectivo, sus límites y el consumo actual (GET /usage) |
webhooks:read | Listar y consultar la configuración de endpoints de webhook (sin el secret) |
webhooks:write | Crear, editar y borrar endpoints; regenerar su secret |
La fuente de verdad son las rutas
Cada operación de la Referencia de la API declara el scope que exige. Si alguna vez esta tabla y la Referencia no coinciden, manda la Referencia (se genera desde el propio servicio).
Subsanar requiere también emitir
POST /invoices/{id}/actions/request-fix exige invoices:fix y invoices:issue: reabrir solo tiene sentido si la clave también puede re-emitir la subsanación. Ver Subsanación.