Autenticazione
Le API di Billy sono protette da OAuth 2.0: il tuo software opera per conto di un esercente, con il suo consenso esplicito e con ambiti di autorizzazione (scope) limitati. Il grant supportato è Authorization Code con PKCE (RFC 7636).
Registrazione dell'applicazione client
Il client OAuth si richiede nel modulo di candidatura come partner affiliato, indicando il nome da mostrare all'esercente nella schermata di consenso e i redirect_uri da autorizzare; il client_id arriva con l'approvazione. Per un altro software, o per cambiare i redirect_uri, scrivi a api@sender.it dall'email registrata come partner. Il redirect_uri usato nel flusso deve corrispondere esattamente a uno di quelli registrati. Sono ammessi URL https o custom scheme per le app native.
Ambiti di autorizzazione (scope)
Gli scope si chiedono nel parametro scope della richiesta di autorizzazione, insieme a openid. Ogni endpoint del riferimento REST indica lo scope che richiede.
| Scope | Testo nel consenso | Cosa apre |
|---|---|---|
| fiscale:read | Leggere lo stato dei PEM, consultare documenti commerciali e report | Elenco e dettaglio dei PEM, storico degli stati, controllo del PEM, richieste AdE ed esiti, storico e dati delle chiusure, documenti commerciali e report, archivio dei documenti gestionali, stato e storico dell'opzione buoni pasto, periodi di inattività. |
| fiscale:manage | Emettere documenti commerciali, annulli e resi, eseguire la chiusura giornaliera | Emissione di DC di vendita, annulli e resi; chiusura di cassa e chiusura di emergenza; cambio di stato, dismissione e cambio sede del PEM; generazione dei documenti gestionali; attivazione e disattivazione dell'opzione buoni pasto; segnalazione dei periodi di inattività. |
| catalogo:read | Leggere reparti e ATECO | Elenco dei reparti e dei codici ATECO. |
| catalogo:write | Modificare reparti e ATECO | Creazione, modifica ed eliminazione di reparti e codici ATECO. |
| offline_access | Restare connesso senza chiedere nuovamente le credenziali | Refresh token di lunga durata: il gestionale lavora per settimane senza nuovo consenso (vedi Durata). |
Flusso Authorization Code con PKCE
GET https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth # autorizzazione (browser dell'esercente)
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/token # scambio del codice e rinnovo
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/revoke # revoca di un refresh token
GET https://auth.billyssw.it/realms/billy/protocol/openid-connect/certs # chiavi pubbliche (JWKS)1. Generazione del PKCE
Genera un code_verifier casuale (da 43 a 128 caratteri) e calcola il code_challenge come Base64 URL-safe, senza padding, dello SHA-256 del verifier. Conserva il verifier per il passaggio 4.
// code_verifier casuale e code_challenge S256
$verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
$state = bin2hex(random_bytes(16));
$_SESSION['pkce'] = compact('verifier', 'state');
// redirect del browser dell'esercente alla pagina di consenso
$url = 'https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth?' . http_build_query([
'client_id' => $clientId,
'redirect_uri' => 'https://gestionale.example.it/callback',
'response_type' => 'code',
'scope' => 'openid fiscale:read fiscale:manage offline_access',
'state' => $state,
'code_challenge' => $challenge,
'code_challenge_method' => 'S256',
], '', '&', PHP_QUERY_RFC3986);
header('Location: ' . $url);// code_verifier casuale e code_challenge S256
import { randomBytes, createHash } from 'node:crypto';
const verifier = randomBytes(32).toString('base64url');
const challenge = createHash('sha256').update(verifier).digest('base64url');
const state = randomBytes(16).toString('hex');
session.pkce = { verifier, state };
// redirect del browser dell'esercente alla pagina di consenso
const url = new URL('https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth');
url.search = new URLSearchParams({
client_id: clientId,
redirect_uri: 'https://gestionale.example.it/callback',
response_type: 'code',
scope: 'openid fiscale:read fiscale:manage offline_access',
state,
code_challenge: challenge,
code_challenge_method: 'S256',
}).toString();
res.redirect(url.toString());2. Richiesta di autorizzazione
Apri nel browser dell'esercente la URL di autorizzazione. state è obbligatorio contro il CSRF: il suo valore va verificato al ritorno sul redirect_uri.
GET https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth
?client_id=<client_id>
&redirect_uri=https%3A%2F%2Fgestionale.example.it%2Fcallback
&response_type=code
&scope=openid%20fiscale%3Amanage%20offline_access
&state=<stringa casuale anti-CSRF>
&code_challenge=<code_challenge>
&code_challenge_method=S2563. Consenso dell'esercente
Se l'esercente non è autenticato, Billy ne chiede il login (password e, dove attivo, il secondo fattore). Poi mostra la schermata di consenso con il nome dell'applicazione e l'elenco degli scope richiesti, da autorizzare o rifiutare in blocco. Se autorizza, il browser torna al redirect_uri con code e state; il codice è monouso e vale 60 secondi. Se rifiuta, il ritorno porta error=access_denied. Un redirect_uri non registrato non viene mai raggiunto: il realm mostra una pagina di errore.
4. Scambio del codice con l'access token
Chiamata diretta (non dal browser) all'endpoint del token, con corpo application/x-www-form-urlencoded e senza client_secret: il possesso del code_verifier corrispondente al code_challenge è la prova di legittimità.
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=<client_id>
&redirect_uri=https%3A%2F%2Fgestionale.example.it%2Fcallback
&code=<code>
&code_verifier=<code_verifier>{
"token_type": "Bearer",
"expires_in": 600,
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...",
"refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...",
"scope": "openid fiscale:read fiscale:manage offline_access"
}Verifica gli scope concessi
scope della risposta e adegua le funzioni offerte: un consenso dato in passato per meno scope, o un client registrato senza uno scope, produce un token con meno permessi di quelli chiesti.Utilizzo dell'access token
Il token va in ogni richiesta come bearer, insieme a Accept: application/json.
GET https://pem-staging.billyssw.it/api/pem
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...
Accept: application/jsonGET /api/pem elenca i PEM che l'utente può usare, con seriale e stato.
Rinnovo del token
L'access token dura 10 minuti. Alla scadenza ottieni una nuova coppia con il refresh token, senza coinvolgere l'esercente. Il refresh token è ruotato a ogni utilizzo e vale una volta sola: persisti sempre l'ultimo ricevuto, il precedente non vale più.
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&client_id=<client_id>
&refresh_token=<refresh_token>Durata e revoca
| Credenziale | Validità | Note |
|---|---|---|
| Codice di autorizzazione | 60 secondi | Monouso; vincolato al redirect_uri e al code_verifier. |
| Access token | 10 minuti | Verificato senza interrogare il realm: resta valido fino alla scadenza anche dopo una revoca. |
| Refresh token | 30 minuti di inattività, 12 ore al massimo | Senza offline_access è legato alla sessione dell'esercente sul realm. |
| Refresh token con offline_access | 30 giorni di inattività, 90 giorni al massimo | Dopo 90 giorni serve un nuovo consenso, qualunque sia l'uso. |
L'esercente revoca il consenso dalla propria account; Sender può disattivare il client, per tutti gli esercenti. In entrambi i casi il refresh token smette subito di funzionare e va ripetuto il flusso di autorizzazione; gli access token già emessi restano validi fino alla loro scadenza.
Codici di errore
| Codice | Significato | Azione |
|---|---|---|
| 401 token_expired | Access token scaduto | Rinnovare con il refresh token e ripetere la richiesta |
| 401 token_invalid | Firma, emittente, audience o claim obbligatori non validi | Ripetere il flusso di autorizzazione |
| 401 unauthenticated | Header Authorization assente, o utente non abilitato su Billy | Verificare l'header; se persiste, contattare il supporto |
| 403 | Scope non concesso o utente con permessi non sufficienti | Controllare gli scope concessi e l'endpoint chiamato |
| 404 | PEM inesistente o di un altro esercente | Verificare il seriale con GET /api/pem |
| 400 invalid_grant | Dall'endpoint del token: codice scaduto o già usato, code_verifier non corrispondente, refresh token già usato, scaduto o revocato | Ripetere il flusso di autorizzazione |
Formati completi delle risposte d'errore nella pagina Codici di errore.