Autenticació i scopes
L'API pública (/api/v1/*) accepta claus API i tokens OAuth2 a la capçalera Authorization: Bearer. Fes servir una clau API per a una integració pròpia entre servidors; fes servir OAuth2 quan una altra aplicació necessiti que un usuari autoritzi l'accés a la seva empresa. La BFF (/app/*) fa servir la sessió del navegador amb cookie sobre HTTPS; els tokens OAuth2 no hi donen accés.
API key
L'API pública s'autentica amb una API key del tenant, enviada com a token Bearer:
Authorization: Bearer bk_live_xxxxxxxxxxxxxxxxbk_live_…→ tenant de producció.bk_test_…→ tenant de proves, que remet als serveis de test de l'AEAT. Consulta l'entorn de proves.
Una clau invàlida o absent retorna 401; una clau sense l'scope necessari retorna 403.
OAuth2 per a aplicacions externes
Les aplicacions externes també poden connectar-se amb authorization code i PKCE S256. Un administrador de Bilteo registra l'aplicació a l'admin amb el nom, les URL de retorn exactes i els scopes màxims. El client_id és públic; no hi ha client secret. L'usuari que autoritza necessita api_keys.admin al tenant escollit.
Genera un code_verifier aleatori de 43–128 caràcters, el seu code_challenge = base64url(SHA256(code_verifier)) sense padding, i un state aleatori d'almenys 16 caràcters. Redirigeix el navegador a la pàgina de l'app https://<host-app>/oauth/authorize amb response_type=code, client_id, redirect_uri, scope (separats per espais), state, code_challenge i code_challenge_method=S256. Després del consentiment, verifica state i bescanvia code una sola vegada abans de cinc minuts. Bilteo permet escollir el tenant; pots enviar tenant_id si ja el coneixes:
Si l'usuari rebutja l'accés, Bilteo torna a redirect_uri amb error=access_denied i el state original.
Exemple d'URL d'autorització (codifica cada paràmetre de consulta; a scope, els espais es codifiquen com %20):
https://<host-app>/oauth/authorize?response_type=code&client_id=<uuid>&redirect_uri=https%3A%2F%2Fapp.exemple.com%2Fcallback&scope=invoices%3Aread%20partners%3Aread&state=<valor-aleatori>&code_challenge=<desafiament-base64url>&code_challenge_method=S256Bescanvia el codi rebut:
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=<codi-rebut>' \
--data-urlencode 'redirect_uri=https://app.exemple.com/callback' \
--data-urlencode 'code_verifier=<verifier-original>'La resposta inclou access_token, token_type (Bearer), expires_in (900 segons), refresh_token i scope.
Envia Authorization: Bearer bo_at_... a /api/v1/*. L'access token dura 15 minuts. Renova'l amb POST /oauth/token, enviant grant_type=refresh_token, client_id i refresh_token=bo_rt_.... Desa el nou refresh token: cada ús el rota i reutilitzar-ne un d'antic revoca tota la connexió. El refresh token caduca als 30 dies. Per desconnectar, envia client_id i token (access o refresh) a POST /oauth/revoke com a formulari. La metadata és a /.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 i API keys fan servir exactament els mateixos scopes i endpoints. Cada token OAuth queda vinculat al tenant autoritzat: no facis servir X-Tenant per canviar-lo. Les credencials absents o caducades retornen 401; si falten scopes, 403. Consulta els errors d'autenticació.
Identitat de la credencial
GET /api/v1/me diu a quina empresa està vinculada la credencial. Funciona amb clau API i amb token OAuth i no necessita cap scope.
{
"tenant": {
"id": "…",
"name": "Perruqueria Exemple SL",
"issuer_name": "Perruqueria Exemple SL",
"issuer_id_num": "B12345678",
"livemode": true
},
"plan_code": "pro",
"auth": { "type": "oauth", "scopes": ["invoices:read", "invoices:write"] }
}livemode val false al tenant de proves. Amb OAuth, crida'l just després d'obtenir el token. L'usuari tria l'empresa a Bilteo i la resposta del token no la inclou.
Scopes
Cada clau API o connexió OAuth té un conjunt de scopes que limita què pot fer. Concedeix només els necessaris. Segueixen el patró recurs:acció; els catàlegs de només lectura exposen únicament :read.
| Scope | Permet |
|---|---|
invoices:read | Llegir factures i les seves línies |
invoices:write | Crear i editar esborranys i línies, i crear rectificatives |
invoices:issue | Emetre factures: numerar-les i remetre-les a l'AEAT |
invoices:cancel | Anul·lar factures emeses |
invoices:replace_simplified | Crear o emetre F3 que substitueixen F2 i gestionar les relacions replace |
invoices:fix | Subsanar el registre Veri*Factu d'una factura emesa: la reobre (needs_fix) si mai ha estat acceptat, o encua la subsanació directament si ho ha estat; s'usa amb invoices:issue |
verifactu:read | Consultar registres Veri*Factu i factures externes de Verifactu Direct API |
verifactu:write | Registrar, anul·lar i subsanar factures externes a Verifactu Direct API |
partners:read / partners:write | Llegir o gestionar clients |
products:read / products:write | Llegir o gestionar productes |
series:read / series:write | Llegir o gestionar sèries de numeració |
economic_activities:read / economic_activities:write | Llegir o gestionar activitats econòmiques |
taxes:read | Llegir el catàleg d'impostos |
fiscal_positions:read | Llegir el catàleg de posicions fiscals |
payment_terms:read | Llegir les condicions de pagament (només lectura) |
usage:read | Consultar el pla efectiu, els límits i el consum actual |
webhooks:read | Llistar i consultar la configuració dels endpoints (sense el secret) |
webhooks:write | Crear, editar i esborrar endpoints; regenerar-ne el secret |
La font de veritat són les rutes
Cada operació de la referència de l'API declara l'scope que exigeix. Si aquesta taula i la referència no coincideixen, preval la referència, que es genera des del servei.
Subsanar també requereix emetre
POST /invoices/{id}/actions/request-fix exigeix invoices:fix i invoices:issue: reobrir només té sentit si la clau també pot tornar a emetre la subsanació. Consulta Subsanació.