BillySSW · Documentazione API

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.

ScopeTesto nel consensoCosa apre
fiscale:readLeggere lo stato dei PEM, consultare documenti commerciali e reportElenco 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:manageEmettere documenti commerciali, annulli e resi, eseguire la chiusura giornalieraEmissione 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:readLeggere reparti e ATECOElenco dei reparti e dei codici ATECO.
catalogo:writeModificare reparti e ATECOCreazione, modifica ed eliminazione di reparti e codici ATECO.
offline_accessRestare connesso senza chiedere nuovamente le credenzialiRefresh token di lunga durata: il gestionale lavora per settimane senza nuovo consenso (vedi Durata).

Flusso Authorization Code con PKCE

text
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.

PKCE e URL di autorizzazione · PHP
// 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);
PKCE e URL di autorizzazione · Node.js
// 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.

http
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=S256

3. 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à.

http
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>
json
{
  "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

Leggi sempre il campo 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.

http
GET https://pem-staging.billyssw.it/api/pem
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...
Accept: application/json

GET /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ù.

http
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

CredenzialeValiditàNote
Codice di autorizzazione60 secondiMonouso; vincolato al redirect_uri e al code_verifier.
Access token10 minutiVerificato senza interrogare il realm: resta valido fino alla scadenza anche dopo una revoca.
Refresh token30 minuti di inattività, 12 ore al massimoSenza offline_access è legato alla sessione dell'esercente sul realm.
Refresh token con offline_access30 giorni di inattività, 90 giorni al massimoDopo 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

CodiceSignificatoAzione
401 token_expiredAccess token scadutoRinnovare con il refresh token e ripetere la richiesta
401 token_invalidFirma, emittente, audience o claim obbligatori non validiRipetere il flusso di autorizzazione
401 unauthenticatedHeader Authorization assente, o utente non abilitato su BillyVerificare l'header; se persiste, contattare il supporto
403Scope non concesso o utente con permessi non sufficientiControllare gli scope concessi e l'endpoint chiamato
404PEM inesistente o di un altro esercenteVerificare il seriale con GET /api/pem
400 invalid_grantDall'endpoint del token: codice scaduto o già usato, code_verifier non corrispondente, refresh token già usato, scaduto o revocatoRipetere il flusso di autorizzazione

Formati completi delle risposte d'errore nella pagina Codici di errore.