# BillySSW — Documentazione API completa

> Documentazione per gli sviluppatori di gestionali e software di cassa che si integrano con l'API fiscale di BillySSW: emissione dei documenti commerciali tramite punti di emissione (PEM) in cloud, chiusura di cassa, modalità di emergenza, autenticazione OAuth 2.0 con il consenso dell'esercente e riferimento REST completo.

Generata dalle pagine di https://app.billyssw.it/documentazione/panoramica. Ogni sezione riporta l'URL della pagina da cui proviene. Specifica OpenAPI: https://app.billyssw.it/documentazione/openapi.json.

## Indice
- [Panoramica](#panoramica) — Cos'è la Soluzione Software, come lavorano PEM e PEL e cosa resta al gestionale che si integra con le API.
- [Percorso di integrazione](#checklist) — I passi dall'affiliazione al go-live: ambiente di prova, consenso dell'esercente, primo documento, passaggio in produzione.
- [Autenticazione](#auth) — OAuth 2.0 Authorization Code con PKCE: registrazione del client, scope, scambio del codice, rinnovo del token, durata e revoca.
- [Guida rapida](#quickstart) — Dal token al primo documento commerciale: trovare il PEM, emettere una vendita e leggere il PDF firmato della risposta.
- [Registrare un esercente](#esercente) — Richiesta di registrazione di un esercente via API o modulo web, campi richiesti e attivazione dopo l'approvazione.
- [Registrare un PEM](#nuovopem) — Creazione e censimento automatico del PEM, i suoi stati, il cambio di stato e la dismissione.
- [Emettere DC](#receipts) — Vendita, annullo, reso e chiusura di cassa: regole dei campi, importi e quantità, idempotenza.
- [Modalità di emergenza](#emergenza) — Cosa fare con il PEM fuori servizio: motivazioni, registro di emergenza e chiusura di emergenza con i riepiloghi IVA.
- [Stampare un DC](#stampa) — Il PDF firmato restituito dall'emissione, il testo ESC/POS nei metadati XMP, il PDF pubblico e la copia di cortesia.
- [Base URL](#ambienti) — Gli ambienti di staging e di produzione e come comporre l'URL di ogni chiamata.
- [REST API](#api) — Tutte le operazioni REST con parametri, corpo della richiesta, risposte ed errori, generate dalla specifica OpenAPI.
- [Codici di errore](#errors) — Codici HTTP, formati del corpo degli errori e codici di dominio dell'API fiscale, con l'azione da intraprendere.

---

<a id="panoramica"></a>

## Panoramica

Fonte: https://app.billyssw.it/documentazione/panoramica · Markdown: https://app.billyssw.it/documentazione/panoramica.md

### Il registratore telematico diventa una chiamata API

**BillySSW** è una Soluzione Software per la memorizzazione elettronica e la trasmissione telematica dei corrispettivi. Il tuo gestionale invia la vendita via REST; Billy genera il documento commerciale e ti restituisce il PDF firmato con il testo per la stampante termica, poi trasmette i corrispettivi all'Agenzia delle Entrate.

[Inizia l'integrazione](https://app.billyssw.it/documentazione/checklist)[Il primo documento](https://app.billyssw.it/documentazione/quickstart)

0 hardware

Il Punto di Emissione è in cloud: niente RT, niente certificati da installare.

OAuth 2.0

Authorization Code + PKCE, consenso esplicito dell'esercente, scope limitati.

1 POST

Una vendita è una chiamata: la risposta è il PDF firmato, con dentro il testo di stampa.

Staging

Ambiente di prova completo, senza invii all'Agenzia delle Entrate.

Il contesto

#### Cos'è una Soluzione Software per i corrispettivi

La nuova normativa introduce la possibilità di emettere i Documenti Commerciali (ex scontrini) con una soluzione interamente software, consentendo quindi di abbandonare il Registratore Telematico. La Soluzione Software BillySSW è certificata ai sensi dell'art. 24, comma 1, del d.lgs. 8 gennaio 2024, n. 1. BillySSW memorizza elettronicamente i dati dei documenti commerciali e li trasmette telematicamente garantendone sicurezza e inalterabilità. Le regole tecniche sono fissate dalle Specifiche Tecniche allegate al Provvedimento del Direttore dell'Agenzia delle Entrate n. 111204 del 7 marzo 2025.

Le Specifiche dividono la soluzione in due moduli fiscali, strettamente interdipendenti:

##### MF1 · Punto di Emissione (PEM)

Il «registratore»: usato dall'esercente per emettere i documenti commerciali.

-   Registra in modo sicuro i dati fiscali di ogni operazione, compresi i pagamenti elettronici
-   Emette documenti commerciali di vendita, annullo e reso
-   Gestisce i flussi della Lotteria degli scontrini
-   Trasmette i dati al Punto di Elaborazione e ne consente la consultazione

##### MF2 · Punto di Elaborazione (PEL)

Il «server fiscale»: dialoga con il sistema dell'Agenzia delle Entrate.

-   Riceve i dati dai PEM collegati
-   Predispone e trasmette i file XML giornalieri dei corrispettivi
-   Memorizza fiscalmente e conserva digitalmente i dati
-   Consente l'interrogazione dei dati ai verificatori

Cosa espongono le API

Le API di Billy agiscono sui **PEM** dell'esercente: emissione, consultazione, chiusura, stato. Il PEL non è esposto, perché il dialogo con l'Agenzia delle Entrate è gestito interamente da Billy; il tuo software ne legge solo gli esiti.

Architettura

#### Dove si colloca il tuo gestionale

Il gestionale resta il punto in cui l'esercente lavora: cassa, catalogo, clienti. Al momento della vendita chiama le API di Billy con il token ottenuto dal consenso dell'esercente. Tutto ciò che è fiscale avviene a valle.

1.  1L'esercente autorizza il tuo software e tu ricevi l'access token (OAuth 2.0 con PKCE).
2.  2Il gestionale invia la vendita: `POST /api/pem/{serial}/fiscale/invio/dc`.
3.  3Il PEM genera l'XML, lo firma e lo concatena nel Journal.
4.  4La risposta arriva subito: il PDF firmato, con progressivo e hash negli header e il testo ESC/POS per la stampante nei metadati.
5.  5Il documento viene accodato verso il PEL, senza far attendere la cassa.
6.  6Il PEL trasmette i corrispettivi all'Agenzia delle Entrate e ne raccoglie gli esiti, che leggi con le API.

-   **Tu sei un partner affiliato.** Ti candidi una volta sola, indicando già il nome del tuo software e i suoi `redirect_uri`; con l'approvazione ricevi il codice partner e il `client_id` OAuth.
-   **L'esercente ha il proprio account Billy,** su cui sono registrati i suoi PEM, e autorizza il tuo software dalla schermata di consenso: il gestionale non conosce mai la sua password e non custodisce un client secret.
-   **Con il token operi sui suoi PEM**, e solo su quelli: `GET /api/pem` ti dice quali sono, con seriale e stato.

Ruoli

#### Chi fa cosa

Le Specifiche Tecniche definiscono tre ruoli. La software house che si integra via API non assume nessuno di essi: opera per conto dell'esercente, con il suo consenso, sopra una soluzione già certificata.

| Ruolo | Chi | Responsabilità |
| --- | --- | --- |
| Produttore dei moduli fiscali | Sender SRL | Realizza MF1 e MF2 nel rispetto delle Specifiche Tecniche. |
| Erogatore della Soluzione Software | Sender SRL | Rende disponibile la soluzione all'esercente, crea e censisce i PEM, fornisce l'assistenza tecnica. |
| Esercente | Il tuo cliente | Soggetto passivo IVA che effettua le operazioni. Si accredita sul portale Fatture e Corrispettivi e sceglie la tipologia Soluzione Software. |
| Partner integratore | La tua software house | Ruolo commerciale, non fiscale: integra il gestionale con le API e porta i propri esercenti con il codice partner AFF-…. |

##### Billy Se ne occupa la Soluzione

-   Censimento del PEM e certificato presso l'Agenzia
-   Generazione, firma e numerazione dei documenti commerciali
-   Journal con catena di hash e sua integrità
-   Trasmissione dei corrispettivi, esiti, lotteria
-   Memorizzazione fiscale e conservazione digitale
-   Chiusura automatica entro 24 ore dall'apertura di cassa

##### Tu Se ne occupa il tuo gestionale

-   Interfaccia di cassa e composizione delle righe
-   Flusso OAuth e conservazione sicura del refresh token
-   Chiamata di emissione con una idempotency\_key per documento
-   Stampa (ESC/POS) o condivisione del PDF al cliente

Punto di Emissione

#### Il PEM: un registratore in cloud per ogni cassa

Ogni PEM è un'entità cloud che crea, firma e concatena i documenti per conto dell'esercente. Un esercente può averne più di uno, per esempio una cassa per punto vendita o più casse nello stesso locale.

##### Seriale

Undici caratteri, per esempio `ABCD-0012EF`: quattro identificano l'erogatore, un trattino, sei di progressivo. È il `serial_number` di tutte le chiamate sul PEM.

##### Creazione

Il primo PEM nasce con l'approvazione dell'esercente, sulla sua sede operativa. I successivi li crea Sender su richiesta: è un'operazione riservata all'Erogatore.

##### Censimento

Il PEM chiede il certificato all'Agenzia tramite il PEL. Billy verifica l'esito ogni 30 minuti e configura il PEM da sola.

In attesa

Certificato richiesto all'Agenzia, in attesa dell'esito.

In servizio

Emette documenti ed esegue la chiusura. Il referente riceve un'email.

Fuori servizio

Guasto, impossibilità di trasmettere o scelta dell'esercente. Si riattiva via API.

Dismissione

Il PEM è definitivamente cessato.

Il tuo software legge lo stato con `GET /api/pem` e lo storico con `GET …/fiscale/stati`; con lo scope `fiscale:manage` lo cambia con `PUT …/fiscale/stato` e dismette il PEM con `POST …/fiscale/stato/dismissione`. Emissione e chiusura richiedono un PEM **In servizio**. Dettagli in [Registrare un PEM](https://app.billyssw.it/documentazione/nuovopem).

Ciclo operativo

#### Una giornata fiscale, dall'apertura alla trasmissione

1.  **Apertura**
    
    Il primo documento emesso apre la giornata del PEM. Non serve una chiamata dedicata.
    
2.  **Emissioni**
    
    Ogni vendita, annullo o reso genera un XML firmato che entra nel Journal, concatenato al precedente tramite hash, e viene accodato verso il PEL.
    
3.  **Chiusura di cassa**
    
    `POST …/fiscale/invio/chiusura` consolida il Journal e ne avvia la trasmissione. Risponde subito; il resto avviene in background. Non è necessario effettuare la chiusura manualmente, il sistema la gestisce in automatico entro 24 ore dall'apertura di cassa.
    
4.  **Trasmissione all'Agenzia**
    
    Il PEL invia i corrispettivi. `GET …/fiscale/richieste?tipo=CORRISPETTIVI` elenca gli invii, con stato ed esito XML scaricabile.
    

Riferimento

#### Mappa delle API

Tutte le chiamate sul PEM hanno la forma `/api/pem/{serial_number}/…` e indicano lo scope richiesto. Il [riferimento REST](https://app.billyssw.it/documentazione/api) descrive ogni campo.

##### Trasmissionifiscale:manage

-   POSTInvio documento commerciale
-   POSTAnnullo
-   GETRighe rendibili di un DC
-   POSTReso
-   POSTChiusura giornaliera

##### Consultazionefiscale:read

-   GETRecupero dati di un DC
-   POSTReport delle vendite
-   GETStorico delle chiusure

##### PEM e richieste AdEfiscale:read

-   GETElenco e dettaglio dei PEM
-   GETStorico degli stati
-   GETRichieste, stato ed esito XML

##### Gestione del PEMfiscale:manage

-   PUTCambio di stato
-   POSTDismissione
-   POSTCambio sede
-   POSTChiusura di emergenza

##### Catalogocatalogo:\*

-   GETReparti e codici ATECO
-   POSTCrea reparto o ATECO
-   PUTAggiorna
-   DELElimina

##### Documenti gestionali

-   GETArchivio e downloadfiscale:read
-   POSTOperazioni memorizzatefiscale:manage
-   POSTEsiti delle trasmissionifiscale:manage
-   POSTChiusura e controllofiscale:manage

##### Opzioni e segnalazioni

-   GETStato e storico buoni pastofiscale:read
-   POSTAttiva o disattiva buoni pastofiscale:manage
-   GETPeriodi di inattivitàfiscale:read
-   POSTSegnala un periodo di inattivitàfiscale:manage

FAQ

#### Domande frequenti

Devo certificare il mio gestionale presso l'Agenzia delle Entrate?

No. La Soluzione Software certificata è BillySSW, di cui Sender SRL è produttore ed erogatore. Il tuo gestionale ne usa le API per conto dell'esercente, come farebbe con un registratore telematico collegato.

Serve un registratore telematico o un certificato sul dispositivo?

No. Il PEM è in cloud: il certificato viene ottenuto dal censimento automatico e la firma avviene sui server di Billy. Il dispositivo di cassa deve solo raggiungere le API.

Cosa deve fare l'esercente sul portale Fatture e Corrispettivi?

Accreditarsi e indicare la tipologia Soluzione Software. La creazione e il censimento del PEM li gestisce Billy: l'esercente riceve un'email quando il PEM è In servizio.

Posso creare PEM aggiuntivi dal mio software?

No: la creazione di un PEM è un'operazione dell'Erogatore. L'esercente la chiede a Sender e il nuovo seriale compare in `GET /api/pem` appena creato.

Cosa succede se la rete cade durante un'emissione?

Ripeti la chiamata con la stessa `idempotency_key`: se il documento era già stato emesso ricevi quello, altrimenti viene emesso ora. Per questo la chiave va salvata con la vendita prima della chiamata.

Chi trasmette i corrispettivi e chi conserva i dati?

Il PEL di Billy: trasmette i file giornalieri all'Agenzia, ne raccoglie gli esiti e memorizza e conserva digitalmente i dati. Il gestionale può leggere esiti e storico via API.

Quanto dura il collegamento prima di chiedere di nuovo il consenso?

Con lo scope `offline_access` il refresh token resta valido fino a 30 giorni di inattività e al massimo 90 giorni; poi l'esercente rinnova il consenso.

Prossimi passi

#### Da dove partire

[

Percorso di integrazione

Dall'affiliazione al go-live.

](https://app.billyssw.it/documentazione/checklist)[

Autenticazione

OAuth 2.0, scope e durata dei token.

](https://app.billyssw.it/documentazione/auth)[

Guida rapida

Il primo documento commerciale.

](https://app.billyssw.it/documentazione/quickstart)[

Emettere DC

Vendite, annulli, resi e chiusura.

](https://app.billyssw.it/documentazione/receipts)

Per domande sull'integrazione scrivi a [api@sender.it](mailto:api@sender.it).

---

<a id="checklist"></a>

## Percorso di integrazione

Fonte: https://app.billyssw.it/documentazione/checklist · Markdown: https://app.billyssw.it/documentazione/checklist.md

### Percorso di integrazione

1.  **Candidati come partner**
    
    L'accesso alle API è riservato ai partner affiliati. Compila il [modulo di affiliazione](https://app.billyssw.it/affiliati/registrazione) con denominazione, partita IVA ed email, e con i dati del client OAuth del tuo software: il **nome** che l'esercente vede nella schermata di consenso e i `redirect_uri` da autorizzare (URL `https` o custom scheme per le app native, gli stessi in staging e in produzione). Dettagli nella pagina [Autenticazione](https://app.billyssw.it/documentazione/auth).
    
2.  **Ricevi codice partner e client\_id**
    
    Il supporto Sender valuta la candidatura e ti risponde all'email indicata. Se è approvata ricevi il codice partner `AFF-…`, il link di iscrizione per i tuoi esercenti e il `client_id` del tuo software, attivo in staging.
    
3.  **Prepara un esercente di prova**
    
    Registra un esercente di prova in staging. Dopo l'approvazione riceve l'email per scegliere la password; il primo punto di emissione (PEM) viene creato e censito in automatico. Vedi [Registrare un esercente](https://app.billyssw.it/documentazione/esercente).
    
4.  **Collega il gestionale**
    
    Esegui il flusso Authorization Code con PKCE: l'esercente accede e autorizza il tuo software. Con l'access token chiama `GET /api/pem` per conoscere il seriale e lo stato dei suoi PEM.
    
5.  **Testa l'integrazione**
    
    Testa le API in ambiente di staging senza inviare dati all'Agenzia delle Entrate. Per l'emissione del primo documento commerciale vedi la [Guida rapida](https://app.billyssw.it/documentazione/quickstart).
    
6.  **Go-live**
    
    Chiedi il passaggio in produzione a [api@sender.it](mailto:api@sender.it): Sender abilita il tuo software e ti comunica host e client OAuth di produzione, oggi ancora da definire. Da quel momento ogni documento emesso con `invio/dc` è un documento fiscale.
    

I tuoi esercenti aderiscono dal link di iscrizione, che porta il tuo codice nel parametro `?aff=AFF-XXXXXXXX`, oppure il gestionale invia la richiesta per loro con `POST /api/auth/register` e il campo `affiliate_code`.

---

<a id="auth"></a>

## Autenticazione

Fonte: https://app.billyssw.it/documentazione/auth · Markdown: https://app.billyssw.it/documentazione/auth.md

### 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](mailto: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

text

```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

```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

```javascript
// 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

```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

```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

```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

```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

```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

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

---

<a id="quickstart"></a>

## Guida rapida

Fonte: https://app.billyssw.it/documentazione/quickstart · Markdown: https://app.billyssw.it/documentazione/quickstart.md

### Invia il tuo primo documento

#### Prerequisiti

-   Richiesta di affiliazione approvata.
-   Il `client_id` del tuo software, ricevuto con l'approvazione.
-   Un esercente registrato con almeno un punto di emissione (PEM) `In servizio`.

#### Passo 1 — Access token

Ottieni l'access token con il flusso Authorization Code e PKCE, chiedendo almeno lo scope `fiscale:manage` (descritto nella pagina **Autenticazione**). Ogni chiamata porta il token e l'header `Accept: application/json`.

Header di ogni richiesta

```http
Authorization: Bearer <access_token>
Accept: application/json
```

#### Passo 2 — Trova il PEM

GET /api/pem

```bash
curl https://pem-staging.billyssw.it/api/pem \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json"
```

200 OK

```json
{
  "success": true,
  "pem": [
    {
      "id": 7,
      "serial_number": "ASVS-241232",
      "stato": "In servizio",
      "created_at": "01-10-2026 09:12:40",
      "indirizzo": "Via Roma 1"
    }
  ]
}
```

#### Passo 3 — Emetti il documento

Una vendita di due caffè pagata in contanti. `idempotency_key` è un UUID nuovo per ogni documento: se la chiamata va ripetuta (timeout, errore di rete), con la stessa chiave ricevi il documento già emesso invece di emetterne un secondo. Gli importi sono stringhe decimali con due decimali.

POST /api/pem/{serial\_number}/fiscale/invio/dc · cURL

```bash
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/invio/dc \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c",
    "elementi": [{
      "descrizione": "Caffè espresso",
      "bene_servizio": "B",
      "quantita": "2",
      "prezzo_unitario": "1.20",
      "iva": "10"
    }],
    "pagamenti": [{
      "metodo": "PC",
      "importo": "2.40"
    }]
  }' \
  -D headers.txt -o DC.pdf
```

POST /api/pem/{serial\_number}/fiscale/invio/dc · PHP

```php
$payload = [
    'idempotency_key' => $uuid, // salvato con la vendita prima della chiamata
    'elementi' => [[
        'descrizione'     => 'Caffè espresso',
        'bene_servizio'   => 'B',
        'quantita'        => '2',
        'prezzo_unitario' => '1.20',
        'iva'             => '10',
    ]],
    'pagamenti' => [['metodo' => 'PC', 'importo' => '2.40']],
];

$headers = [];
$ch = curl_init("https://pem-staging.billyssw.it/api/pem/{$serial}/fiscale/invio/dc");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer {$accessToken}",
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_HEADERFUNCTION => function ($ch, $riga) use (&$headers) {
        [$nome, $valore] = array_pad(explode(':', $riga, 2), 2, null);
        if ($valore !== null) {
            $headers[strtolower(trim($nome))] = trim($valore);
        }
        return strlen($riga);
    },
]);
$body   = curl_exec($ch); // timeout o errore di rete: ripeti con la STESSA $uuid
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($status === 200 && str_starts_with($headers['content-type'] ?? '', 'application/pdf')) {
    file_put_contents("DC_{$uuid}.pdf", $body);  // il documento da conservare e consegnare
    preg_match('/filename="DC_(.+)\.pdf"/', $headers['content-disposition'], $m);
    $progressivo = $m[1];                         // "0012-0007"
    $pdfPubblico = $headers['content-location'];  // URL pubblico del PDF
    $hash = rawurldecode(basename(dirname(parse_url($pdfPubblico, PHP_URL_PATH))));
} else {
    $errore = json_decode($body, true);           // gli errori sono JSON
}
```

POST /api/pem/{serial\_number}/fiscale/invio/dc · Node.js

```javascript
const res = await fetch(
  `https://pem-staging.billyssw.it/api/pem/${serial}/fiscale/invio/dc`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      idempotency_key: uuid, // salvato con la vendita prima della chiamata
      elementi: [{
        descrizione: 'Caffè espresso',
        bene_servizio: 'B',
        quantita: '2',
        prezzo_unitario: '1.20',
        iva: '10',
      }],
      pagamenti: [{ metodo: 'PC', importo: '2.40' }],
    }),
  },
);
// timeout o errore di rete: ripeti con la STESSA uuid

if (res.ok && res.headers.get('content-type')?.startsWith('application/pdf')) {
  const pdf = Buffer.from(await res.arrayBuffer()); // il documento da conservare e consegnare
  const progressivo = res.headers
    .get('content-disposition')
    .match(/filename="DC_(.+)\.pdf"/)[1];           // "0012-0007"
  const pdfPubblico = res.headers.get('content-location');
  const hash = decodeURIComponent(new URL(pdfPubblico).pathname.split('/').at(-2));
} else {
  const errore = await res.json();                  // gli errori sono JSON
}
```

La risposta è il **PDF firmato** (PAdES) del documento. I dati che servono al gestionale sono negli header:

200 OK

```http
Content-Type: application/pdf
Content-Disposition: inline; filename="DC_0012-0007.pdf"
Content-Location: https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf
```

-   il **progressivo** (`NNNN-PPPP`) è nel nome del file di `Content-Disposition`;
-   `Content-Location` è il link pubblico allo stesso PDF, senza token; il segmento prima di `/pdf` è l'**hash** del documento, URL-encoded, che serve per il reso;
-   il testo ESC/POS per la stampante termica è nei metadati XMP del PDF.

Successo

Come leggere l'ESC/POS e stampare, anche la copia di cortesia, è descritto in [Stampare un DC](https://app.billyssw.it/documentazione/stampa). L'XML firmato è già nel Journal del PEM e parte verso il PEL subito dopo la risposta.

---

<a id="esercente"></a>

## Registrare un esercente

Fonte: https://app.billyssw.it/documentazione/esercente · Markdown: https://app.billyssw.it/documentazione/esercente.md

### Registrare un esercente

#### Richiesta di registrazione

L'esercente chiede di aderire dal modulo web `https://pem-staging.billyssw.it/auth/register` oppure il tuo software invia la richiesta per lui con `POST /api/auth/register`. Affinché il cliente ti venga assegnato correttamente, aggiungi il tuo codice affiliato: al modulo con `?aff=AFF-XXXXXXXX`, all'API nel campo `affiliate_code`.

| Campo | Contenuto |
| --- | --- |
| nome, cognome, email, telefono | Il titolare. email è l'indirizzo dell'account Billy ed è anche il nome utente per l'accesso: deve essere unica. telefono di 9 o 10 cifre. |
| denominazione, partita\_iva, codice\_fiscale | Dati fiscali dell'attività. La partita IVA è verificata ed è unica. |
| indirizzo, civico, cap, comune, provincia, nazione | Sede legale. provincia e nazione di 2 caratteri. |
| regime\_fiscale | Obbligatorio: ordinario, semplificato o forfettario\_minimo. |
| pec, sdi | Facoltativi. sdi di 7 caratteri. |
| sede\_operativa | Facoltativa: indirizzo e referente del punto vendita. Se manca vale la sede legale. È la sede del primo PEM. |
| affiliate\_code | Facoltativo: il codice del partner che ha portato l'esercente. |
| privacy | Obbligatorio: accettazione dell'informativa (true). |

POST /api/auth/register

```bash
curl -X POST https://pem-staging.billyssw.it/api/auth/register \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "nome": "Mario",
    "cognome": "Rossi",
    "email": "mario.rossi@example.it",
    "telefono": "3331234567",
    "denominazione": "Bar Centrale di Mario Rossi",
    "partita_iva": "01234567897",
    "codice_fiscale": "RSSMRA80A01F205X",
    "indirizzo": "Via Roma",
    "civico": "1",
    "cap": "20121",
    "comune": "Milano",
    "provincia": "MI",
    "nazione": "IT",
    "regime_fiscale": "ordinario",
    "affiliate_code": "AFF-7K2M9QXA",
    "privacy": true
  }'
```

La risposta conferma la presa in carico: non crea ancora l'account. L'esercente riceve un'email di conferma della richiesta. Lo stato della richiesta non si legge via API: l'esito arriva all'esercente per email.

#### Approvazione e attivazione

Sender verifica i dati e approva la richiesta. Da lì il sistema procede da solo:

-   crea l'account dell'esercente e gli invia l'email _Completa la configurazione del tuo account Billy_, per scegliere la password e confermare l'indirizzo;
-   crea il primo **PEM** sulla sede operativa e avvia il **censimento** presso l'Agenzia delle Entrate;
-   al termine del censimento porta il PEM nello stato `In servizio` e lo comunica via email al referente della sede.

#### Collegare il gestionale

Con l'account attivo, l'esercente autorizza il tuo software dalla schermata di consenso del flusso OAuth: non ci sono chiavi API da generare o copiare. Il seriale dei suoi PEM lo ottieni con `GET /api/pem`.

PEM aggiuntivi

Altri punti di emissione (una seconda cassa, un altro punto vendita) si chiedono a Sender: vedi **Registrare un PEM**.

---

<a id="nuovopem"></a>

## Registrare un PEM

Fonte: https://app.billyssw.it/documentazione/nuovopem · Markdown: https://app.billyssw.it/documentazione/nuovopem.md

### Registrare un PEM

Come nasce un Punto di Emissione (PEM), come viene censito presso l'Agenzia delle Entrate e quando può emettere documenti commerciali.

Cloud PEM

Punto di Emissione

Il PEM è un'entità cloud che crea, firma e concatena i documenti commerciali per conto dell'esercente. Nessun hardware o certificato da installare.

1 esercente → N PEM

Più casse

Un esercente può avere più PEM. Ognuno ha un numero seriale univoco.

#### Creazione

Il primo PEM nasce con l'approvazione della registrazione dell'esercente, sulla sua sede operativa. I PEM successivi li crea Sender su richiesta dell'esercente: le API non permettono a un partner di crearne, in quanto questa è un'operazione di competenza dell'Erogatore certificato.

Il PEM riceve un **numero seriale** di undici caratteri (es. `ABCD-0012EF`): quattro che identificano l'erogatore, un trattino e sei di progressivo. È il parametro `serial_number` di tutte le chiamate sul PEM.

#### Censimento automatico

Appena creato, il PEM chiede il proprio certificato all'Agenzia delle Entrate (_censimento_) e passa nello stato `In attesa`. Billy verifica l'esito ogni 30 minuti; quando il certificato arriva configura il PEM, lo porta `In servizio` e lo comunica via email al referente della sede. Non serve alcun intervento. Puoi seguire l'avanzamento con `GET /api/pem` (campo `stato`) e con le richieste del PEM (`GET …/fiscale/richieste`).

#### Stati

| Stato | Significato |
| --- | --- |
| In attesa | Censimento in corso presso l'AdE. |
| In servizio | Il PEM emette documenti commerciali ed esegue la chiusura di cassa. |
| Fuori servizio | Il PEM non può emettere: guasto, impossibilità di trasmettere o scelta dell'esercente. Torna In servizio con PUT …/fiscale/stato. |
| Dismissione | Il PEM è definitivamente cessato. |

Lo stato si legge con `GET /api/pem/{serial_number}` e lo storico con `GET …/fiscale/stati`. Con lo scope `fiscale:manage` il tuo software può anche:

-   cambiare lo stato con `PUT …/fiscale/stato`: `"01"` Fuori servizio, con `motivazione` e `note` obbligatorie (vedi Modalità di emergenza per i codici), oppure `"03"` In servizio, senza motivazione;
-   dismettere il PEM con `POST …/fiscale/stato/dismissione` e il campo `note`, obbligatorio. L'operazione è definitiva ed è riservata all'esercente titolare, non a un operatore.

Pronto all'invio

Con il PEM `In servizio` e con l'access token del tuo client, puoi emettere documenti commerciali.

---

<a id="receipts"></a>

## Emettere DC

Fonte: https://app.billyssw.it/documentazione/receipts · Markdown: https://app.billyssw.it/documentazione/receipts.md

### Vendite, annulli, resi e chiusura

Come emettere i documenti commerciali e chiudere la giornata. Tutte le operazioni richiedono lo scope `fiscale:manage` e un PEM `In servizio`.

#### Vendita

Il PEM genera e firma l'XML del documento, lo aggiunge al Journal e ne accoda la trasmissione al PEL. La risposta è il PDF firmato del documento, con dentro il testo per la stampante termica; l'hash è nell'URL di `Content-Location`. Vedi [Stampare un DC](https://app.billyssw.it/documentazione/stampa).

POST …/fiscale/invio/dc

```bash
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/invio/dc \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c",
    "elementi": [
      { "descrizione": "Pizza margherita", "bene_servizio": "B", "quantita": "1", "prezzo_unitario": "8.00", "iva": "10" },
      { "descrizione": "Acqua", "bene_servizio": "B", "quantita": "1", "prezzo_unitario": "2.00", "iva": "10", "sconto": "0.50" }
    ],
    "pagamenti": [
      { "metodo": "PP", "importo": "9.50" }
    ]
  }'
```

##### Regole principali

| Campo | Regola |
| --- | --- |
| idempotency\_key | UUID, obbligatorio, uno per documento. Con una chiave già usata sul PEM la risposta è il documento già emesso, anche se il payload è diverso: vedi Idempotenza, sotto. |
| elementi\[\].iva | 4, 5, 10, 22; N1–N6 per le operazioni senza IVA; VI per la ventilazione. |
| elementi\[\].bene\_servizio | B (bene) o S (servizio). Le righe di servizio non sono rendibili. |
| elementi\[\].prezzo\_unitario, sconto | Importi in euro IVA inclusa, al massimo due decimali. sconto è un importo, non una percentuale. |
| pagamenti | Obbligatori se il totale da pagare è maggiore di zero, e devono coprirlo. I contanti possono eccederlo (il resto si stampa); gli altri metodi no. |
| pagamenti\[\].metodo | PC contanti, PP elettronico, TK buoni pasto (solo con l'opzione attiva), NRF, NRB, NRS, NRD non riscosso. NRF e NRD escludono altri pagamenti. |
| codice\_lotteria | Fino a 8 caratteri. Richiede almeno 1 euro pagato con un unico pagamento PP; alternativo a cessionario\_committente. |
| cessionario\_committente | Codice fiscale del cliente, fino a 16 caratteri. Alternativo a codice\_lotteria. |

Tutti i campi, compresi sconto sul subtotale, arrotondamento DL 50/2017 e dati della transazione POS, sono nel **riferimento REST**.

##### Importi e quantità

Invia importi e quantità come **stringhe decimali** con il punto (`"8.00"`).

`quantita` ha **al massimo due decimali** (`"1.5"`, `"0.35"`): il documento commerciale dell'Agenzia delle Entrate la registra con due decimali.

##### Idempotenza

Genera la `idempotency_key` e salvala con la vendita prima della chiamata: se la risposta non arriva (timeout, errore di rete), ripeti la stessa chiamata con la stessa chiave. Una chiave già usata sul PEM restituisce `200` con il PDF del documento già emesso, quindi lo stesso progressivo, e non emette nulla.

#### Annullo

L'annullo invalida una vendita dello stesso PEM: basta il suo `progressivo` (`NNNN-PPPP`), le righe si prendono dall'originale. Per un documento emesso da un altro dispositivo si indicano anche `matricola_annullo`, `dataOra` ed `elementi`.

POST …/fiscale/invio/annullo

```bash
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/invio/annullo \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "progressivo": "0012-0007"
  }'
```

#### Reso

Il reso restituisce una o più righe di una vendita, in tutto o in parte. Prima leggi le righe dell'originale, con il loro `id` e la quantità già resa, partendo dall'`hash` ricevuto all'emissione:

GET …/fiscale/invio/reso?hash=…

```bash
curl "https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/invio/reso?hash=YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D" \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json"
```

200 OK

```json
{
  "success": true,
  "dati": {
    "progressivo": "0012-0007",
    "data": "2026-10-08 10:15:42",
    "serial_number": "ASVS-241232",
    "elementi_contabili": [
      { "id": 5531, "descrizione": "Pizza margherita", "bene_servizio": "B", "quantita": "1.00", "prezzo_unitario": "8.00", "sconto": "0.00", "iva": "10", "reso": 0 }
    ]
  }
}
```

Poi emetti il reso con il progressivo dell'originale e, per ogni riga, l'`id` e la quantità resa:

POST …/fiscale/invio/reso

```bash
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/invio/reso \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "progressivo": "0012-0007",
    "elementi": [
      { "id": 5531, "quantita": "1" }
    ]
  }'
```

Risposta

Annullo e reso rispondono come la vendita: il PDF firmato del documento di annullo o di reso, con un proprio progressivo e un proprio hash negli header. Gli errori tipici sono `DC003` (già annullato), `DC006` (quantità oltre il rendibile) e `DC007` (riga di servizio).

#### Chiusura di cassa

La chiusura consolida il Journal della giornata e ne avvia la trasmissione al PEL, che invia i corrispettivi all'AdE. Non ha corpo e risponde subito; il resto avviene in background.

POST …/fiscale/invio/chiusura

```bash
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/invio/chiusura \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json"
```

-   `409 JN005` subito dopo un'emissione: il documento è ancora in invio al PEL, riprova dopo qualche istante.
-   `GET …/fiscale/chiusure/storico` mostra la giornata chiusa; `inviato_il` si valorizza quando il PEL l'ha ricevuta.
-   `GET …/fiscale/richieste?tipo=CORRISPETTIVI` elenca le trasmissioni all'AdE: con `richieste/stato/{id}` se ne aggiorna lo stato, con `richieste/esito/{id}` si scarica l'esito XML.
-   Il primo documento emesso dopo la chiusura apre la giornata successiva. Se non effettui la chiusura manualmente, il sistema chiude automaticamente cassa entro 24 ore dall'apertura; oltre le 24 ore l'emissione risponde `409 JN002`.

---

<a id="emergenza"></a>

## Modalità di emergenza

Fonte: https://app.billyssw.it/documentazione/emergenza · Markdown: https://app.billyssw.it/documentazione/emergenza.md

### Modalità di emergenza

Cosa fare quando il PEM non può emettere documenti commerciali: annotare le vendite sul registro di emergenza e trasmettere a fine giornata la chiusura di emergenza, con i riepiloghi IVA.

#### Quando si usa

Se il PEM entra nello stato di `Fuori servizio`, questo **non** può emettere documenti commerciali. Finché il PEM è fuori servizio, le chiamate `…/fiscale/invio/*` (vendita, annullo, reso, chiusura) rispondono `409`. L'esercente annota i documenti commerciali sul **registro di emergenza dei corrispettivi** fuori dal sistema. A fine giornata ne trasmette gli aggregati con la chiusura di emergenza.

La modalità di emergenza si applica anche quando il tuo gestionale non può connettersi alle API per mancata connessione internet. In questo caso, la comunicazione di `Fuori servizio` può avvenire successivamente al ripristino della connessione.

#### Entrare in emergenza

Il tuo software porta il PEM fuori servizio con `PUT …/fiscale/stato`, indicando `stato` `01`, una `motivazione` e le `note`, fino a 255 caratteri. Stato e motivazione sono stringhe di due cifre: `"01"`, non `1`.

Note sempre obbligatorie

Billy richiede le `note` con ogni Fuori servizio, qualunque sia la motivazione, e con la dismissione; senza, risponde `422`.

PUT …/fiscale/stato

```bash
curl -X PUT \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/stato \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "stato": "01", "motivazione": "01", "note": "Guasto alla rete del punto vendita" }'
```

| Motivazione | Significato |
| --- | --- |
| 01 | Problemi hardware. |
| 02 | Problemi software. |
| 03 | Problemi di comunicazione tra PEM e PEL. |
| 08 | Mancata integrità. |
| 09 | Messa in magazzino: periodo di inattività. |
| 10 | Altro. |

Gli altri codici non si inviano: `04` e `05` riguardano la dismissione, che ha un endpoint dedicato; `06` e `07` li attribuisce il sistema al passaggio In servizio. Al rientro si invia solo `{ "stato": "03" }`, senza motivazione.

Il PEM può anche finire fuori servizio da solo:

-   se un documento non raggiunge il PEL entro 12 ore, con motivazione `03`. Prima di cambiare stato il PEM chiude la giornata e conserva il Journal firmato, che trasmette al rientro in servizio;
-   se la catena degli hash risulta compromessa, con motivazione `08`.

#### Chiusura di emergenza

A fine giornata l'esercente riporta i totali del registro di emergenza: un riepilogo per aliquota, natura o ventilazione, più i totali dei pagamenti. È ammessa solo con il PEM `Fuori servizio` ed è riservata all'esercente titolare: un operatore riceve `403`.

POST …/fiscale/emergenza/chiusura

```bash
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/emergenza/chiusura \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c",
    "n_doc_commerciali": 12,
    "riepiloghi": [
      { "iva": "22", "importo_parziale": "100.00", "imposta": "22.00" },
      { "iva": "10", "importo_parziale": "50.50", "imposta": "5.05" }
    ],
    "totali": {
      "pagato_contanti": "100.00",
      "pagato_elettronico": "77.55",
      "sconto_a_pagare": "0.00"
    }
  }'
```

200 OK

```json
{
  "success": true,
  "dati": {
    "id_presa_in_carico": "12345",
    "n_doc_commerciali": 12
  }
}
```

##### Regole principali

| Campo | Regola |
| --- | --- |
| idempotency\_key | UUID, obbligatorio, uno per chiusura. Vedi sotto, Idempotenza. |
| n\_doc\_commerciali | Numero di documenti annotati sul registro nella giornata, almeno 1. |
| riepiloghi | Da 1 a 40 righe. Ognuna indica esattamente uno tra iva (4, 5, 10, 22), natura (N1–N6) e ventilazione\_iva (SI). La stessa aliquota o natura non si ripete, salvo con codice\_attivita diversi. |
| riepiloghi\[\].importo\_parziale | Imponibile, al netto dell'IVA (al lordo con ventilazione\_iva). È già al netto di resi, annulli, beni in sospeso e non riscossi, ma comprende gli omaggi. Può essere negativo. |
| riepiloghi\[\].imposta | Obbligatoria se e solo se la riga ha iva. Deve valere importo\_parziale × aliquota, con una tolleranza di ±0,01. |
| riepiloghi\[\].totale\_ammontare\_resi, totale\_ammontare\_annulli, beni\_in\_sospeso, non\_riscosso\_\* | Facoltativi, mai negativi. Li si indica a parte, senza toglierli una seconda volta dall'imponibile. |
| riepiloghi\[\].codice\_attivita | Facoltativo, deve essere uno dei codici Ateco configurati sul PEM. |
| totali | pagato\_contanti, pagato\_elettronico e sconto\_a\_pagare sono obbligatori. ticket (pagato\_ticket, numero\_ticket) è facoltativo: senza opzione buoni pasto è un normale pagamento; con l'opzione attiva nella giornata richiede un riepilogo al 10% il cui lordo copra i ticket, e il riferimento normativo TICKET lo attribuisce il sistema (rif\_normativo non si invia). |

Gli importi sono **stringhe decimali** con al massimo due decimali (`"100.00"`), mai numeri in virgola mobile. Se il registro riporta importi IVA inclusa, scorpora l'IVA prima dell'invio: `imponibile = lordo × 100 / (100 + aliquota)`, arrotondato al centesimo, e l'imposta per differenza. Così imponibile più imposta danno il lordo esatto: ad esempio 122,00 € al 22% diventano `100.00` + `22.00`. Le righe con natura o ventilazione non hanno imposta. L'elenco completo dei campi è nel **riferimento REST**.

Lo stesso calcolo in codice, a partire dalle vendite del registro con aliquota IVA, importi IVA inclusa in centesimi. Lavorare in centesimi interi evita gli errori della virgola mobile.

Riepiloghi per aliquota · PHP

```php
// $vendite: [['iva' => '22', 'lordo' => 12200], ...] — lordo in centesimi
$lordi = [];
foreach ($vendite as $v) {
    $lordi[$v['iva']] = ($lordi[$v['iva']] ?? 0) + $v['lordo'];
}

$euro = fn (int $c): string =>
    ($c < 0 ? '-' : '') . sprintf('%d.%02d', intdiv(abs($c), 100), abs($c) % 100);

$riepiloghi = [];
foreach ($lordi as $iva => $lordo) {
    $imponibile = (int) round($lordo * 100 / (100 + (int) $iva)); // al centesimo
    $riepiloghi[] = [
        'iva'              => (string) $iva,
        'importo_parziale' => $euro($imponibile),
        'imposta'          => $euro($lordo - $imponibile),          // per differenza
    ];
}
```

Riepiloghi per aliquota · Node.js

```javascript
// vendite: [{ iva: '22', lordo: 12200 }, ...] — lordo in centesimi
const lordi = {};
for (const v of vendite) lordi[v.iva] = (lordi[v.iva] ?? 0) + v.lordo;

const euro = (c) =>
  (c < 0 ? '-' : '') + `${Math.trunc(Math.abs(c) / 100)}.${String(Math.abs(c) % 100).padStart(2, '0')}`;
// arrotondamento al centesimo, metà lontano dallo zero anche per i negativi
const arrotonda = (x) => Math.sign(x) * Math.round(Math.abs(x));

const riepiloghi = Object.entries(lordi).map(([iva, lordo]) => {
  const imponibile = arrotonda((lordo * 100) / (100 + Number(iva)));
  return {
    iva,
    importo_parziale: euro(imponibile),
    imposta: euro(lordo - imponibile), // per differenza
  };
});
```

Le righe con natura (`N1`–`N6`) e quelle con ventilazione si costruiscono a parte: `importo_parziale` è il lordo e non c'è `imposta`.

##### Giornata e idempotenza

-   La chiusura vale sempre per la giornata in cui viene inviata, e non c'è un campo data. Inviala lo stesso giorno delle vendite che riepiloga: inviata dopo la mezzanotte, viene registrata sulla giornata nuova e non c'è modo di attribuirla a quella precedente.
-   Una giornata ammette più chiusure di emergenza: ogni `idempotency_key` nuova è una chiusura e una trasmissione distinte, anche con dati identici.
-   Ripetere la chiamata con la stessa chiave è sicuro. Se la chiusura è già trasmessa ricevi lo stesso `id_presa_in_carico` e non parte un nuovo invio. Se l'invio era fallito, il PEM lo ritenta con i dati salvati, compresa la giornata originale.
-   La stessa chiave con dati diversi viene rifiutata con `422` sul campo `idempotency_key`. Per dati nuovi, genera una chiave nuova.

#### Seguire la trasmissione

Ogni chiusura di emergenza trasmessa diventa una richiesta di tipo `CORRISPETTIVI`, come quella ordinaria. Si segue con lo scope `fiscale:read`:

-   `GET …/fiscale/richieste?tipo=CORRISPETTIVI` elenca le trasmissioni all'AdE;
-   con l'`id` di quell'elenco, non con l'`id_presa_in_carico`, `richieste/stato/{id}` ne aggiorna lo stato e `richieste/esito/{id}` scarica l'esito XML.

---

<a id="stampa"></a>

## Stampare un DC

Fonte: https://app.billyssw.it/documentazione/stampa · Markdown: https://app.billyssw.it/documentazione/stampa.md

### Stampare un DC

Come ottenere dal documento emesso il PDF da condividere e i byte ESC/POS per la stampante termica, compresa la copia di cortesia.

#### Cosa restituisce l'emissione

Come richiesto dalle specifiche AdE, vendita, annullo e reso hanno un unico output: il **PDF firmato** (PAdES) del documento commerciale. Anche il replay con la stessa `idempotency_key` restituisce lo stesso PDF. Gli errori restano JSON, quindi controlla `Content-Type` prima di leggere il corpo.

POST …/fiscale/invio/dc

```bash
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/invio/dc \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d @vendita.json \
  -D headers.txt -o DC.pdf
```

200 OK

```http
Content-Type: application/pdf
Content-Disposition: inline; filename="DC_0012-0007.pdf"
Content-Location: https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf
```

| Dove | Cosa contiene |
| --- | --- |
| corpo | I byte del PDF firmato, largo 120 mm. È il documento da mostrare, salvare o inviare al cliente. |
| metadati XMP del PDF | billy:escPosBase64: i comandi ESC/POS per la stampante termica, in base64. |
| Content-Location | L'URL pubblico dello stesso PDF. Il segmento prima di /pdf è l'hash del documento (URL-encoded), che serve per il reso e per l'invio email. |
| Content-Disposition | Il nome del file, DC\_<progressivo>.pdf. |

#### Stampa termica (ESC/POS)

Il testo per la stampante viaggia dentro il PDF, nel pacchetto XMP del catalogo, nell'elemento `billy:escPosBase64` (namespace `https://billy.it/ns/thermal/1.0/`). Leggi i metadati con una libreria PDF, decodifica il base64 e invia i byte alla stampante così come sono.

#### PDF pubblico

L'URL di `Content-Location` è pubblico, senza token, e non sta sotto `/api`: il percorso è `/pem/{serial_number}/fiscale/consultazione/dc/{hash}/pdf`, con l'hash URL-encoded. È limitato a 60 richieste al minuto per indirizzo IP. Restituisce lo stesso PDF firmato e accetta parametri di impaginazione per rigenerarlo a un'altra larghezza.

GET …/consultazione/dc/{hash}/pdf

```bash
curl -o DC_58mm.pdf \
  "https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf?width=58&font_size=8"
```

| Parametro | Regola |
| --- | --- |
| width | Larghezza in mm, da 58 a 120 (default 120). Valori fuori intervallo vengono riportati al limite. |
| font\_size | Corpo del testo, da 6 a 14 (default 10), con lo stesso limite. |
| regalo | 1 per la copia di cortesia; vedi sotto. |

L'altezza della pagina segue il contenuto. Ogni PDF, anche quello rigenerato, contiene i propri metadati ESC/POS, ma `width` e `font_size` cambiano solo il PDF: l'ESC/POS segue sempre le impostazioni di stampa dell'esercente (32 o 48 colonne).

#### Copia di cortesia

Per i DC di vendita puoi stampare anche una copia senza importi, da dare insieme a un regalo: stesse informazioni fiscali (data, progressivo, codice lotteria, QR, hash, DataMatrix, seriale, righe di cortesia, appendice), ma solo descrizioni e quantità. Si ottiene aggiungendo `?regalo=1` all'URL di `Content-Location`: il PDF restituito ha nei metadati l'ESC/POS della copia, sempre a 32 colonne.

GET …/pdf?regalo=1

```bash
curl -o DC_0012-0007_regalo.pdf \
  "https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf?regalo=1"
```

Annulli e resi

Su annullo e reso `regalo` viene ignorato e l'URL restituisce il documento completo. Il documento di annullo riporta le righe del DC annullato.

#### Invio al cliente

In alternativa alla stampa, consegna al cliente il PDF ricevuto all'emissione oppure il link di `Content-Location`, che si apre senza credenziali.

---

<a id="ambienti"></a>

## Base URL

Fonte: https://app.billyssw.it/documentazione/ambienti · Markdown: https://app.billyssw.it/documentazione/ambienti.md

### Base URL

Gli ambienti dell'API fiscale e l'host da anteporre a ogni percorso del riferimento REST.

#### Ambienti

| Ambiente | Base URL | Uso |
| --- | --- | --- |
| Staging | https://pem-staging.billyssw.it | Sviluppo e collaudo dell'integrazione. I documenti emessi non hanno valore fiscale. |
| Production | da definire | Esercenti reali: ogni documento emesso è trasmesso all'Agenzia delle Entrate. |

#### Comporre l'URL

I percorsi del riferimento REST sono relativi al base URL: l'endpoint `GET /api/pem` in staging è

GET /api/pem

```bash
curl https://pem-staging.billyssw.it/api/pem \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json"
```

Tieni il base URL in configurazione: il passaggio in produzione cambia solo quello, percorsi e payload restano identici. Gli esempi di questa documentazione usano l'host di staging.

Importante

Host e client OAuth di produzione sono da definire: li ricevi con l'abilitazione alla produzione, da chiedere a [api@sender.it](mailto:api@sender.it) a collaudo concluso.

---

<a id="api"></a>

## REST API

Fonte: https://app.billyssw.it/documentazione/api · Markdown: https://app.billyssw.it/documentazione/api.md

Tutte le operazioni pubblicate dell'API fiscale, generate dalla specifica OpenAPI (https://app.billyssw.it/documentazione/openapi.json).

I percorsi sono relativi al base URL dell'ambiente: in staging `https://pem-staging.billyssw.it` (vedi Base URL, https://app.billyssw.it/documentazione/ambienti). Ogni richiesta porta `Accept: application/json`.

### Autenticazione

Il bearer è un <b>access token</b> del realm Keycloak <code>billy</code>, ottenuto con Authorization Code + PKCE e il consenso dell'esercente. Per i client partner vale 10 minuti: alla scadenza (401 con <code>error: token_expired</code>) se ne chiede uno nuovo al token endpoint del realm con il <code>refresh_token</code>. Ogni endpoint dichiara lo scope richiesto ai client partner; quelli riservati all'app e al pannello Billy rispondono 403 a un client partner.

- Autorizzazione: `https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth`
- Token e rinnovo: `https://auth.billyssw.it/realms/billy/protocol/openid-connect/token`

| Scope | Descrizione |
| --- | --- |
| `fiscale:read` | Leggere lo stato dei PEM, consultare documenti commerciali e report |
| `fiscale:manage` | Emettere documenti commerciali, annulli, resi e documenti gestionali, eseguire la chiusura giornaliera e di emergenza, gestire il PEM (cambio stato, dismissione, cambio sede, periodi di inattività) |
| `catalogo:read` | Leggere reparti e codici ATECO |
| `catalogo:write` | Creare, modificare ed eliminare reparti, categorie e codici ATECO |
| `offline_access` | Refresh token di lunga durata (sessione offline) |

### Indice delle operazioni
- **Account - Registrazione**
  - [POST /api/auth/register](https://app.billyssw.it/documentazione/api#post-api-auth-register): Richiesta di registrazione esercente
- **Anagrafica - PEM**
  - [GET /api/pem](https://app.billyssw.it/documentazione/api#get-api-pem): Lista PEM
  - [GET /api/pem/{serial_number}](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number): Mostra PEM
  - [POST /api/pem/{serial_number}/cambio-sede](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-cambio-sede): Richiedi il cambio sede
  - [DELETE /api/pem/{serial_number}/cambio-sede/{cambio_sede}](https://app.billyssw.it/documentazione/api#delete-api-pem-serial_number-cambio-sede-cambio_sede): Annulla il cambio sede
- **Catalogo - ATECO**
  - [GET /api/pem/{serial_number}/dati/ateco](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-dati-ateco): Lista ATECO
  - [POST /api/pem/{serial_number}/dati/ateco](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-dati-ateco): Crea ATECO
  - [PUT /api/pem/{serial_number}/dati/ateco/{id}](https://app.billyssw.it/documentazione/api#put-api-pem-serial_number-dati-ateco-id): Aggiorna ATECO
  - [DELETE /api/pem/{serial_number}/dati/ateco/{id}](https://app.billyssw.it/documentazione/api#delete-api-pem-serial_number-dati-ateco-id): Elimina ATECO
- **Catalogo - Reparti**
  - [GET /api/pem/{serial_number}/dati/reparti](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-dati-reparti): Lista Reparti
  - [POST /api/pem/{serial_number}/dati/reparti](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-dati-reparti): Crea Reparto
  - [PUT /api/pem/{serial_number}/dati/reparti/{reparto_id}](https://app.billyssw.it/documentazione/api#put-api-pem-serial_number-dati-reparti-reparto_id): Aggiorna Reparto
  - [DELETE /api/pem/{serial_number}/dati/reparti/{reparto_id}](https://app.billyssw.it/documentazione/api#delete-api-pem-serial_number-dati-reparti-reparto_id): Elimina Reparto
- **Funzioni fiscali - Consultazione**
  - [GET /api/pem/{serial_number}/fiscale/chiusure/storico](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-chiusure-storico): Storico chiusure
  - [GET /api/pem/{serial_number}/fiscale/chiusure/{journalId}/documento](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-chiusure-journalid-documento): Dati di una chiusura
  - [POST /api/pem/{serial_number}/fiscale/consultazione/report](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-consultazione-report): Report
  - [GET /api/pem/{serial_number}/fiscale/consultazione/dc](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-consultazione-dc): Dettaglio di un DC
  - [GET /pem/{serial_number}/fiscale/consultazione/dc/{hash}/pdf](https://app.billyssw.it/documentazione/api#get-pem-serial_number-fiscale-consultazione-dc-hash-pdf): PDF pubblico di un DC
- **Funzioni fiscali - Controllo PEM**
  - [GET /api/pem/{serial_number}/controllo](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-controllo): Dati di controllo AdE del PEM
  - [POST /api/pem/{serial_number}/fiscale/gestionali/controllo](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-gestionali-controllo): Emetti Documento Gestionale di Controllo
- **Funzioni fiscali - Documenti gestionali**
  - [GET /api/pem/{serial_number}/fiscale/gestionali](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-gestionali): Archivio documenti gestionali
  - [GET /api/pem/{serial_number}/fiscale/gestionali/{id}/pdf](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-gestionali-id-pdf): Download di un documento gestionale archiviato
  - [POST /api/pem/{serial_number}/fiscale/gestionali/operazioni](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-gestionali-operazioni): Documento gestionale operazioni memorizzate
  - [POST /api/pem/{serial_number}/fiscale/gestionali/esiti-trasmissione](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-gestionali-esiti-trasmissione): Documento gestionale esiti trasmissioni corrispettivi
  - [POST /api/pem/{serial_number}/fiscale/gestionali/storico-buoni-pasto](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-gestionali-storico-buoni-pasto): Documento gestionale storico opzione buoni pasto
  - [POST /api/pem/{serial_number}/fiscale/gestionali/chiusura/{journalId}](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-gestionali-chiusura-journalid): Documento gestionale di chiusura
- **Funzioni fiscali - Emergenza**
  - [POST /api/pem/{serial_number}/fiscale/emergenza/chiusura](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-emergenza-chiusura): Chiusura di emergenza
- **Funzioni fiscali - Opzione buoni pasto**
  - [GET /api/pem/{serial_number}/fiscale/opzione-buoni-pasto](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-opzione-buoni-pasto): Stato opzione buoni pasto
  - [POST /api/pem/{serial_number}/fiscale/opzione-buoni-pasto](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-opzione-buoni-pasto): Attiva/disattiva opzione buoni pasto
  - [GET /api/pem/{serial_number}/fiscale/opzione-buoni-pasto/storico](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-opzione-buoni-pasto-storico): Storico opzione buoni pasto
- **Funzioni fiscali - Richieste PEM**
  - [GET /api/pem/{serial_number}/fiscale/richieste](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-richieste): Lista richieste
  - [GET /api/pem/{serial_number}/fiscale/richieste/stato/{idRichiesta}](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-richieste-stato-idrichiesta): Verifica stato richiesta
  - [GET /api/pem/{serial_number}/fiscale/richieste/esito/{idRichiesta}](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-richieste-esito-idrichiesta): Recupera esito richiesta
- **Funzioni fiscali - Segnalazioni**
  - [GET /api/pem/{serial_number}/fiscale/segnalazioni/inattivita](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-segnalazioni-inattivita): Periodi di inattività
  - [POST /api/pem/{serial_number}/fiscale/segnalazioni/inattivita](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-segnalazioni-inattivita): Segnala un periodo di inattività
- **Funzioni fiscali - Stato PEM**
  - [GET /api/pem/{serial_number}/fiscale/stati](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-stati): Lista Stati PEM
  - [PUT /api/pem/{serial_number}/fiscale/stato](https://app.billyssw.it/documentazione/api#put-api-pem-serial_number-fiscale-stato): Modifica lo stato del PEM
  - [POST /api/pem/{serial_number}/fiscale/stato/dismissione](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-stato-dismissione): Dismissione del PEM
- **Funzioni fiscali - Trasmissioni**
  - [POST /api/pem/{serial_number}/fiscale/invio/dc](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-invio-dc): Invio DC
  - [POST /api/pem/{serial_number}/fiscale/invio/annullo](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-invio-annullo): Annulla DC
  - [GET /api/pem/{serial_number}/fiscale/invio/reso](https://app.billyssw.it/documentazione/api#get-api-pem-serial_number-fiscale-invio-reso): Recupera dati reso DC
  - [POST /api/pem/{serial_number}/fiscale/invio/reso](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-invio-reso): Reso DC
  - [POST /api/pem/{serial_number}/fiscale/invio/chiusura](https://app.billyssw.it/documentazione/api#post-api-pem-serial_number-fiscale-invio-chiusura): Chiusura giornaliera

### Account - Registrazione

<a id="post-api-auth-register"></a>

#### Richiesta di registrazione esercente

`POST /api/auth/register`

Pubblica: nessun token.

Registra la richiesta di adesione di un esercente, senza credenziali: la
esamina l'erogatore e, all'approvazione, l'esercente riceve l'email per
scegliere la password. `affiliate_code` è il codice del partner che
l'ha portato (`AFF-…`), facoltativo.

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `nome` | `string` | sì | Nome del titolare. Al massimo 60 caratteri. Esempio: `Mario`. |
| `cognome` | `string` | sì | Cognome del titolare. Al massimo 60 caratteri. Esempio: `Rossi`. |
| `email` | `string` | sì | Email del titolare, unica: è l'indirizzo dell'account Billy ed è anche il nome utente dell'account. Non si può cambiare dopo l'approvazione. Deve essere un indirizzo email valido. Al massimo 80 caratteri. Esempio: `mario.rossi@example.it`. |
| `telefono` | `string` | sì | Telefono, da 6 a 11 cifre; il prefisso +39 o 0039, gli spazi e i separatori vengono tolti. Formato: <code>/^\d{6,11}$/</code>. Esempio: `3331234567`. |
| `partita_iva` | `string` | sì | Partita IVA dell'attività, verificata e unica. Va inviata come stringa: un numero JSON perderebbe gli zeri iniziali ed è rifiutato. Esempio: `01234567897`. |
| `codice_fiscale` | `string` | sì | Codice fiscale dell'attività o del titolare: 16 caratteri con carattere di controllo valido (omocodie ammesse, convertito in maiuscolo), oppure 11 cifre per le società, di solito uguali alla partita IVA. Esempio: `RSSMRA80A01F205X`. |
| `denominazione` | `string` | sì | Denominazione dell'attività. Al massimo 80 caratteri. Esempio: `Bar Centrale di Mario Rossi`. |
| `indirizzo` | `string` | sì | Indirizzo della sede legale. Al massimo 60 caratteri. Esempio: `Via Roma`. |
| `civico` | `string` | sì | Numero civico della sede legale. Al massimo 8 caratteri. Esempio: `1`. |
| `cap` | `string` | sì | CAP della sede legale, 5 cifre. Deve essere composto da 5 cifre. Esempio: `20121`. |
| `comune` | `string` | sì | Comune della sede legale. Al massimo 60 caratteri. Esempio: `Milano`. |
| `provincia` | `string` | sì | Sigla della provincia (elenco ISTAT; `EE` per l'estero). Deve essere lungo 2 caratteri. Valori: `AG`, `AL`, `AN`, `AO`, `AP`, `AQ`, `AR`, `AT`, `AV`, `BA`, `BG`, `BI`, `BL`, `BN`, `BO`, `BR`, `BS`, `BT`, `BZ`, `CA`, `CB`, `CE`, `CH`, `CL`, `CN`, `CO`, `CR`, `CS`, `CT`, `CZ`, `EN`, `FC`, `FE`, `FG`, `FI`, `FM`, `FR`, `GE`, `GO`, `GR`, `IM`, `IS`, `KR`, `LC`, `LE`, `LI`, `LO`, `LT`, `LU`, `MB`, `MC`, `ME`, `MI`, `MN`, `MO`, `MS`, `MT`, `NA`, `NO`, `NU`, `OR`, `PA`, `PC`, `PD`, `PE`, `PG`, `PI`, `PN`, `PO`, `PR`, `PT`, `PU`, `PV`, `PZ`, `RA`, `RC`, `RE`, `RG`, `RI`, `RM`, `RN`, `RO`, `SA`, `SI`, `SO`, `SP`, `SR`, `SS`, `SU`, `SV`, `TA`, `TE`, `TN`, `TO`, `TP`, `TR`, `TS`, `TV`, `UD`, `VA`, `VB`, `VC`, `VE`, `VI`, `VR`, `VT`, `VV`, `EE`. Esempio: `MI`. |
| `nazione` | `string` | sì | Codice della nazione, 2 caratteri. Deve essere lungo 2 caratteri. Esempio: `IT`. |
| `pec` | `string` | no | PEC dell'attività, facoltativa. Deve essere un indirizzo email valido. Al massimo 80 caratteri. Può essere `null`. Esempio: `barcentrale@pec.it`. |
| `sdi` | `string` | no | Codice destinatario SDI, 7 lettere o cifre (convertito in maiuscolo), facoltativo. Formato: <code>/^[A-Z0-9]{7}$/</code>. Deve essere lungo 7 caratteri. Può essere `null`. Esempio: `M5UXCR1`. |
| `regime_fiscale` | `string` | sì | Regime fiscale dell'attività. Valori: `ordinario`, `semplificato`, `forfettario_minimo`. Esempio: `ordinario`. |
| `sede_operativa` | `object` | no | Facoltativa: sede e referente del punto vendita, sede del primo PEM. Se manca, è `null` o è vuota vale la sede legale con i dati del titolare. Può essere `null`. |
| `sede_operativa.nome_referente` | `string` | sì | Al massimo 60 caratteri. Esempio: `Mario`. |
| `sede_operativa.cognome_referente` | `string` | sì | Al massimo 60 caratteri. Esempio: `Rossi`. |
| `sede_operativa.email_referente` | `string` | sì | Deve essere un indirizzo email valido. Al massimo 80 caratteri. Esempio: `mario.rossi@example.it`. |
| `sede_operativa.telefono_referente` | `string` | sì | Formato: <code>/^\d{6,11}$/</code>. Esempio: `3331234567`. |
| `sede_operativa.indirizzo` | `string` | sì | Al massimo 60 caratteri. Esempio: `Corso Buenos Aires`. |
| `sede_operativa.civico` | `string` | sì | Al massimo 8 caratteri. Esempio: `10`. |
| `sede_operativa.cap` | `string` | sì | Deve essere composto da 5 cifre. Esempio: `20124`. |
| `sede_operativa.comune` | `string` | sì | Al massimo 60 caratteri. Esempio: `Milano`. |
| `sede_operativa.provincia` | `string` | sì | Deve essere lungo 2 caratteri. Valori: `AG`, `AL`, `AN`, `AO`, `AP`, `AQ`, `AR`, `AT`, `AV`, `BA`, `BG`, `BI`, `BL`, `BN`, `BO`, `BR`, `BS`, `BT`, `BZ`, `CA`, `CB`, `CE`, `CH`, `CL`, `CN`, `CO`, `CR`, `CS`, `CT`, `CZ`, `EN`, `FC`, `FE`, `FG`, `FI`, `FM`, `FR`, `GE`, `GO`, `GR`, `IM`, `IS`, `KR`, `LC`, `LE`, `LI`, `LO`, `LT`, `LU`, `MB`, `MC`, `ME`, `MI`, `MN`, `MO`, `MS`, `MT`, `NA`, `NO`, `NU`, `OR`, `PA`, `PC`, `PD`, `PE`, `PG`, `PI`, `PN`, `PO`, `PR`, `PT`, `PU`, `PV`, `PZ`, `RA`, `RC`, `RE`, `RG`, `RI`, `RM`, `RN`, `RO`, `SA`, `SI`, `SO`, `SP`, `SR`, `SS`, `SU`, `SV`, `TA`, `TE`, `TN`, `TO`, `TP`, `TR`, `TS`, `TV`, `UD`, `VA`, `VB`, `VC`, `VE`, `VI`, `VR`, `VT`, `VV`, `EE`. Esempio: `MI`. |
| `sede_operativa.nazione` | `string` | sì | Deve essere lungo 2 caratteri. Esempio: `IT`. |
| `affiliate_code` | `string` | no | Codice del partner affiliato che ha portato l'esercente, facoltativo. Al massimo 32 caratteri. Può essere `null`. Esempio: `AFF-7K2M9QXA`. |
| `privacy` | `boolean` | sì | Accettazione dell'informativa privacy. Deve valere true. Esempio: `true`. |

Esempio:

```json
{
    "nome": "Mario",
    "cognome": "Rossi",
    "email": "mario.rossi@example.it",
    "telefono": "3331234567",
    "partita_iva": "01234567897",
    "codice_fiscale": "RSSMRA80A01F205X",
    "denominazione": "Bar Centrale di Mario Rossi",
    "indirizzo": "Via Roma",
    "civico": "1",
    "cap": "20121",
    "comune": "Milano",
    "provincia": "MI",
    "nazione": "IT",
    "regime_fiscale": "ordinario",
    "privacy": true
}
```

**Risposte**

- `200` — Richiesta registrata

```json
{
    "success": true,
    "message": "Registrazione avvenuta con successo"
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

- `429` — Troppe richieste

```json
{
    "success": false,
    "message": "Troppe richieste di registrazione: riprova tra qualche minuto",
    "error": "too_many_requests"
}
```

### Anagrafica - PEM

<a id="get-api-pem"></a>

#### Lista PEM

`GET /api/pem`

Scope richiesto ai client partner: `fiscale:read`.

Mostra la lista dei PEM associati all'utente autenticato.

**Risposte**

- `200` — success

```json
{
    "success": true,
    "pem": [
        {
            "id": 1,
            "serial_number": "ASVS-241232",
            "stato": "In servizio",
            "created_at": "01-01-2025 13:00:00",
            "indirizzo": "Via Roma 1"
        }
    ]
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

<a id="get-api-pem-serial_number"></a>

#### Mostra PEM

`GET /api/pem/{serial_number}`

Scope richiesto ai client partner: `fiscale:read`.

Mostra i dettagli del PEM indicato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "pem": {
        "id": 1,
        "serial_number": "ASVS-241232",
        "stato": "In servizio",
        "created_at": "01-01-2025 13:00:00",
        "indirizzo": "Via Roma 1"
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="post-api-pem-serial_number-cambio-sede"></a>

#### Richiedi il cambio sede

`POST /api/pem/{serial_number}/cambio-sede`

Scope richiesto ai client partner: `fiscale:manage`.

Chiede di spostare il PEM a un nuovo indirizzo. Se serve l'approvazione
dell'erogatore la richiesta resta in attesa; una volta approvata l'emissione
è sospesa fino alla chiusura di giornata e la nuova sede vale dalla prima
emissione successiva.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `indirizzo` | `string` | sì | Al massimo 60 caratteri. Esempio: `Via Garibaldi`. |
| `civico` | `string` | sì | Al massimo 8 caratteri. Esempio: `12`. |
| `cap` | `string` | sì | Deve essere composto da 5 cifre. Esempio: `20121`. |
| `comune` | `string` | sì | Al massimo 60 caratteri. Esempio: `Milano`. |
| `provincia` | `string` | sì | Deve essere lungo 2 caratteri. Esempio: `MI`. |
| `nazione` | `string` | sì | Al massimo 2 caratteri. Esempio: `IT`. |
| `sede_legale` | `boolean` | sì | Esempio: `false`. |

Esempio:

```json
{
    "indirizzo": "Via Garibaldi",
    "civico": "12",
    "cap": "20121",
    "comune": "Milano",
    "provincia": "MI",
    "nazione": "IT",
    "sede_legale": false
}
```

**Risposte**

- `200` — Richiesta registrata

```json
{
    "success": true,
    "cambio_sede": {
        "id": 3,
        "stato": "richiesta",
        "indirizzo": "Via Garibaldi",
        "civico": "12",
        "cap": "20121",
        "comune": "Milano",
        "provincia": "MI",
        "nazione": "IT",
        "sede_legale": false,
        "motivazione_rifiuto": null,
        "created_at": "08-10-2026 10:15:42",
        "approvato_il": null
    },
    "message": "Richiesta inviata: dopo l'approvazione dell'erogatore l'emissione sarà sospesa fino alla chiusura di giornata, poi la nuova sede sarà applicata automaticamente alla prima emissione"
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

<a id="delete-api-pem-serial_number-cambio-sede-cambio_sede"></a>

#### Annulla il cambio sede

`DELETE /api/pem/{serial_number}/cambio-sede/{cambio_sede}`

Scope richiesto ai client partner: `fiscale:manage`.

Ritira una richiesta di cambio sede non ancora applicata (409 se non è più annullabile).

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `cambio_sede` | `integer` | sì | L'id della richiesta di cambio sede. Esempio: `4`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Richiesta di cambio sede annullata"
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

### Catalogo - ATECO

<a id="get-api-pem-serial_number-dati-ateco"></a>

#### Lista ATECO

`GET /api/pem/{serial_number}/dati/ateco`

Scope richiesto ai client partner: `catalogo:read`.

Restituisce i codici ATECO associati al PEM indicato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "ateco": [
        {
            "id": 1,
            "codice": "112233",
            "descrizione": "TEST ATECO"
        }
    ]
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="post-api-pem-serial_number-dati-ateco"></a>

#### Crea ATECO

`POST /api/pem/{serial_number}/dati/ateco`

Scope richiesto ai client partner: `catalogo:write`.

Crea un nuovo codice ATECO associato al PEM indicato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `ateco` | `object` | no |  |
| `ateco.descrizione` | `string` | sì | La descrizione dell'ATECO. Al massimo 100 caratteri. Esempio: `Commercio al dettaglio`. |
| `ateco.codice` | `string` | sì | Il codice ATECO nel formato XX.XX.XX. Unico sul PEM. Formato: <code>/^\d{2}\.\d{2}\.\d{2}$/</code>. Esempio: `47.11.00`. |

Esempio:

```json
{
    "ateco": {
        "descrizione": "Commercio al dettaglio",
        "codice": "47.11.00"
    }
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "ATECO creato con successo",
    "ateco": {
        "id": 1,
        "codice": "112233",
        "descrizione": "TEST ATECO"
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Validazione fallita

```json
{
    "success": false,
    "message": "Il campo codice è obbligatorio",
    "errors": {
        "ateco.codice": [
            "Il campo codice è obbligatorio"
        ]
    }
}
```

<a id="put-api-pem-serial_number-dati-ateco-id"></a>

#### Aggiorna ATECO

`PUT /api/pem/{serial_number}/dati/ateco/{id}`

Scope richiesto ai client partner: `catalogo:write`.

Aggiorna un codice ATECO esistente associato al PEM indicato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `id` | `integer` | sì | L'ID dell'ATECO. Esempio: `23`. |

**Corpo della richiesta** (`application/json`)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `ateco` | `object` | no |  |
| `ateco.descrizione` | `string` | no | La descrizione dell'ATECO. Al massimo 100 caratteri. Esempio: `Commercio al dettaglio`. |
| `ateco.codice` | `string` | no | Il codice ATECO nel formato XX.XX.XX. Unico sul PEM. Formato: <code>/^\d{2}\.\d{2}\.\d{2}$/</code>. Esempio: `47.11.00`. |

Esempio:

```json
{
    "ateco": {
        "descrizione": "Commercio al dettaglio",
        "codice": "47.11.00"
    }
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "ATECO aggiornato con successo",
    "ateco": {
        "id": 1,
        "codice": "112233",
        "descrizione": "TEST ATECO"
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — ATECO non trovato

```json
{
    "success": false,
    "message": "La risorsa richiesta non è stata trovata."
}
```

- `422` — Validazione fallita

```json
{
    "success": false,
    "message": "Il campo codice è obbligatorio",
    "errors": {
        "ateco.codice": [
            "Il campo codice è obbligatorio"
        ]
    }
}
```

<a id="delete-api-pem-serial_number-dati-ateco-id"></a>

#### Elimina ATECO

`DELETE /api/pem/{serial_number}/dati/ateco/{id}`

Scope richiesto ai client partner: `catalogo:write`.

Elimina un codice ATECO associato al PEM indicato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `id` | `integer` | sì | L'ID dell'ATECO. Esempio: `23`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "ATECO eliminato con successo"
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — ATECO non trovato

```json
{
    "success": false,
    "message": "La risorsa richiesta non è stata trovata."
}
```

### Catalogo - Reparti

<a id="get-api-pem-serial_number-dati-reparti"></a>

#### Lista Reparti

`GET /api/pem/{serial_number}/dati/reparti`

Scope richiesto ai client partner: `catalogo:read`.

Mostra la lista dei reparti associati al PEM corrente.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "reparti": [
        {
            "id": 1,
            "sigla": "R1",
            "descrizione": "Reparto 1",
            "importo": 100,
            "iva": "22",
            "colore": "#FFFFFF",
            "bene_servizio": "B",
            "id_categoria": 1,
            "id_ateco": 1
        }
    ]
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="post-api-pem-serial_number-dati-reparti"></a>

#### Crea Reparto

`POST /api/pem/{serial_number}/dati/reparti`

Scope richiesto ai client partner: `catalogo:write`.

Memorizza un nuovo reparto associato al PEM corrente.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `reparto` | `object` | no |  |
| `reparto.sigla` | `string` | sì | La sigla del reparto. Al massimo 20 caratteri. Esempio: `A`. |
| `reparto.codice` | `string` | no | Il codice del reparto. Al massimo 50 caratteri. Può essere `null`. Esempio: `REP-001`. |
| `reparto.descrizione` | `string` | sì | La descrizione del reparto. Al massimo 100 caratteri. Esempio: `Caffetteria`. |
| `reparto.importo` | `number` | sì | L'importo del reparto. Esempio: `1.2`. |
| `reparto.iva` | `string` | sì | L'aliquota IVA del reparto. Valori: `4`, `5`, `10`, `22`, `N1`, `N2`, `N3`, `N4`, `N5`, `N6`, `VI`. Esempio: `10`. |
| `reparto.colore` | `string` | no | Il colore del reparto. Formato: <code>/^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$/</code>. Esempio: `#FFFFFF`. |
| `reparto.bene_servizio` | `string` | sì | Il tipo di bene/servizio del reparto. Valori: `B`, `S`. Esempio: `B`. |
| `reparto.id_categoria` | `number` | no | L'ID della categoria del reparto. Può essere `null`. Esempio: `1`. |
| `reparto.id_ateco` | `number` | no | L'ID dell'ATECO del reparto. Può essere `null`. Esempio: `1`. |

Esempio:

```json
{
    "reparto": {
        "sigla": "A",
        "descrizione": "Caffetteria",
        "importo": 1.2,
        "iva": "10",
        "bene_servizio": "B"
    }
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Reparto creato con successo",
    "reparto": {
        "id": 1,
        "sigla": "R1",
        "descrizione": "Reparto 1",
        "importo": 100,
        "iva": "22",
        "colore": "#FFFFFF",
        "bene_servizio": "B",
        "id_categoria": 1,
        "id_ateco": 1
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Validazione fallita

```json
{
    "success": false,
    "message": "Il campo iva è obbligatorio",
    "errors": {
        "reparto.iva": [
            "Il campo iva è obbligatorio"
        ]
    }
}
```

<a id="put-api-pem-serial_number-dati-reparti-reparto_id"></a>

#### Aggiorna Reparto

`PUT /api/pem/{serial_number}/dati/reparti/{reparto_id}`

Scope richiesto ai client partner: `catalogo:write`.

Aggiorna un reparto esistente associato al PEM indicato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `reparto_id` | `integer` | sì | L'ID del reparto. Esempio: `23`. |

**Corpo della richiesta** (`application/json`)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `reparto` | `object` | no |  |
| `reparto.sigla` | `string` | no | La sigla del Reparto. Al massimo 20 caratteri. Esempio: `A`. |
| `reparto.codice` | `string` | no | Il codice del Reparto. Al massimo 50 caratteri. Può essere `null`. Esempio: `REP-001`. |
| `reparto.descrizione` | `string` | no | La descrizione del Reparto. Al massimo 100 caratteri. Esempio: `Caffetteria`. |
| `reparto.importo` | `number` | no | L'importo del Reparto. Esempio: `1.2`. |
| `reparto.iva` | `string` | no | L'IVA del Reparto. Valori: `4`, `5`, `10`, `22`, `N1`, `N2`, `N3`, `N4`, `N5`, `N6`, `VI`. Esempio: `10`. |
| `reparto.colore` | `string` | no | Il colore del Reparto in esadecimale. Formato: <code>/^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$/</code>. Esempio: `#FFFFFF`. |
| `reparto.ordine` | `number` | no | L'ordine del Reparto. Esempio: `1`. |
| `reparto.bene_servizio` | `string` | no | Il tipo di Bene/Servizio del Reparto. Valori: `B`, `S`. Esempio: `B`. |
| `reparto.preferito` | `boolean` | no | Indica se il Reparto è preferito. Esempio: `true`. |
| `reparto.id_categoria` | `number` | no | L'ID della Categoria del Reparto. Può essere `null`. Esempio: `1`. |
| `reparto.id_ateco` | `number` | no | L'ID dell'ATECO del Reparto. Può essere `null`. Esempio: `1`. |

Esempio:

```json
{
    "reparto": {
        "sigla": "A",
        "descrizione": "Caffetteria",
        "importo": 1.2,
        "iva": "10",
        "bene_servizio": "B"
    }
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Reparto aggiornato con successo",
    "reparto": {
        "id": 1,
        "sigla": "R1",
        "descrizione": "Reparto 1",
        "importo": 100,
        "iva": "22",
        "colore": "#FFFFFF",
        "bene_servizio": "B",
        "id_categoria": 1,
        "id_ateco": 1
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Reparto non trovato

```json
{
    "success": false,
    "message": "La risorsa richiesta non è stata trovata."
}
```

- `422` — Validazione fallita

```json
{
    "success": false,
    "message": "Aliquota non valida",
    "errors": {
        "reparto.iva": [
            "Aliquota non valida"
        ]
    }
}
```

<a id="delete-api-pem-serial_number-dati-reparti-reparto_id"></a>

#### Elimina Reparto

`DELETE /api/pem/{serial_number}/dati/reparti/{reparto_id}`

Scope richiesto ai client partner: `catalogo:write`.

Elimina un reparto associato al PEM indicato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `reparto_id` | `integer` | sì | L'ID del reparto. Esempio: `23`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Reparto eliminato con successo"
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Reparto non trovato

```json
{
    "success": false,
    "message": "La risorsa richiesta non è stata trovata."
}
```

### Funzioni fiscali - Consultazione

<a id="get-api-pem-serial_number-fiscale-chiusure-storico"></a>

#### Storico chiusure

`GET /api/pem/{serial_number}/fiscale/chiusure/storico`

Scope richiesto ai client partner: `fiscale:read`.

Elenca le giornate chiuse del PEM, dalla più recente, con il numero di DC
e la data di trasmissione al PEL (`inviato_il` nullo = chiusura non ancora
trasmessa). `journal_aperto` è la giornata in corso, nullo se la cassa è chiusa.
`data_chiusura` è in ora locale; `inviato_il` e `aperto_il` in UTC.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "chiusure": [
        {
            "id": 12,
            "progressivo": 12,
            "data_chiusura": "2026-10-07T21:30:00",
            "numero_dc": 48,
            "numero_dc_journal": 48,
            "inviato_il": "2026-10-07T19:30:04.000000Z"
        }
    ],
    "journal_aperto": {
        "id": 13,
        "progressivo": 13,
        "aperto_il": "2026-10-08T06:02:11.000000Z"
    }
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="get-api-pem-serial_number-fiscale-chiusure-journalid-documento"></a>

#### Dati di una chiusura

`GET /api/pem/{serial_number}/fiscale/chiusure/{journalId}/documento`

Scope richiesto ai client partner: `fiscale:read`.

Restituisce i dati della giornata fiscale chiusa (`journalId` da *Storico
chiusure*): dati locali della chiusura uniti ai totali del PEL, unica fonte
fiscale. Finché la chiusura non è stata trasmessa all'AdE il PEL non ha i
totali e la risposta è 409.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `journalId` | `integer` | sì | L'id della chiusura (campo `id` dello storico). Esempio: `12`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "documento": {
        "pem": {
            "serial_number": "ASVS-241232",
            "indirizzo": "Via Roma 1",
            "comune": "Milano",
            "cap": "20100",
            "provincia": "MI"
        },
        "esercente": {
            "denominazione": "Bar Centrale srl",
            "partita_iva": "01234567890"
        },
        "journal": {
            "progressivo": 12,
            "data_chiusura": "2026-10-07T21:30:00",
            "numero_dc": 48,
            "numero_dc_journal": 48
        },
        "riepilogo": [],
        "pagamenti": [],
        "riepiloghi": [],
        "chiusure": [],
        "buoni_pasto": {
            "attiva": false,
            "aggiornato_il": null
        }
    }
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — Corrispettivi non trasmessi

```json
{
    "success": false,
    "message": "Chiusura non ancora trasmessa all'AdE"
}
```

<a id="post-api-pem-serial_number-fiscale-consultazione-report"></a>

#### Report

`POST /api/pem/{serial_number}/fiscale/consultazione/report`

Scope richiesto ai client partner: `fiscale:read`.

Elabora il report delle vendite del PEM indicato

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `data_inizio` | `string` | no | Data di inizio del periodo di report. Deve essere una data valida. Esempio: `2026-10-01`. |
| `data_fine` | `string` | no | Data di fine del periodo di report. Deve essere una data valida. Esempio: `2026-10-31`. |
| `metodi_pagamento` | `array<string>` | no | Metodo di pagamento da filtrare, con i codici di emissione (PC, PP, TK, NRF, NRB, NRS, NRD). Esempio: `["PP"]`. |
| `utenti` | `array<integer>` | no | ID utente specifico da filtrare. Esempio: `[121]`. |
| `tipo_documenti` | `array<string>` | no | Tipo di documento da filtrare: DC (vendita), ANNULLO o RESO. Esempio: `["DC"]`. |
| `ricerca` | `string` | no | Testo libero per filtrare per progressivo DC (es. '0089-0001') o per descrizione di un elemento contabile. Al massimo 255 caratteri. Può essere `null`. Esempio: `0089`. |

Esempio:

```json
{
    "data_inizio": "2026-10-01",
    "data_fine": "2026-10-31"
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "scontrini": [
        {
            "id": 102,
            "user": "EsercenteTEST",
            "tipo": "DC",
            "progressivo": "0089-0001",
            "data": "2025-07-22 14:58:57",
            "inviato_il": "2025-07-22 14:59:02",
            "importo": 377.5,
            "pagamenti_metodo": "PC",
            "progressivo_riferimento": null,
            "annullato": false,
            "reso_fatto": false,
            "hash": "NmNhMzI5MDc3NjY2YzI5OTk2NDBiODg5YmFhMjEyMDI3MzZlZmNiNTMzMjEzNDQxNDE1YzQ1MmZlYWMyMjYyZTlmMTljMWVkYzFmN2UzMGU2ODYxMGIxMzVjZmJiMDBjNDAwMDI5NmQ5YTk1ODk4ZDI4NWQ3NmU1MjI5ZTkwMzg="
        }
    ],
    "totali": {
        "totaleLordo": 377.5,
        "totaleAnnulli": 0,
        "totaleResi": 0,
        "pagamenti": {
            "contanti": 377,
            "elettronico": 0,
            "ticket": 0,
            "nonRiscossoBeni": 0,
            "nonRiscossoServizi": 0,
            "nonRiscossoFattura": 0,
            "scontoAPagare": 0
        },
        "iva": {
            "10": {
                "imponibile": 70.45454545,
                "importo": 7.04545455
            },
            "N2": {
                "imponibile": 100,
                "importo": 0
            },
            "N4": {
                "imponibile": 200,
                "importo": 0
            }
        },
        "reparto": {
            "Nessuno": 377.5
        },
        "totaleOmaggi": 0
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

<a id="get-api-pem-serial_number-fiscale-consultazione-dc"></a>

#### Dettaglio di un DC

`GET /api/pem/{serial_number}/fiscale/consultazione/dc`

Scope richiesto ai client partner: `fiscale:read`.

Restituisce un documento commerciale del PEM nello stesso formato della
risposta di emissione: con `id` quello indicato, senza l'ultimo emesso
dall'utente del token.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Parametri della query**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `id` | `integer` | no | L'id del documento commerciale (dal report). Esempio: `1532`. |

**Risposte**

- `200` — Il documento commerciale (abbreviato)

```json
{
    "success": true,
    "dati": {
        "tipo": "DC",
        "progressivo": "0012-0007",
        "data": "2026-10-08 10:15:42",
        "hash": "YjQ0ZDJmMGE5YzFl...MmEzYjQ=",
        "codice_lotteria": null,
        "cessionario_committente": null,
        "elementi_contabili": [
            {
                "id": 5531,
                "descrizione": "Caffè espresso",
                "bene_servizio": "B",
                "quantita": "2.00",
                "prezzo_unitario": "1.20",
                "sconto": "0.00",
                "iva": "10",
                "acconto_versato_beni_non_consegnati": false,
                "omaggio": false
            }
        ],
        "pagamenti_dc": [
            {
                "importo": "2.40",
                "metodo": "PC"
            }
        ],
        "condivisione": {
            "testo_stampa": "G0AbYQFCQVIgQ0VOVFJBTEUg...",
            "pdf_url": "https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf"
        }
    }
}
```

- `401` — 

```json
{
    "success": false,
    "message": "Il token di accesso non è valido.",
    "error": "token_invalid"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Documento inesistente

```json
{
    "success": false,
    "errore": "Documento commerciale non trovato"
}
```

<a id="get-pem-serial_number-fiscale-consultazione-dc-hash-pdf"></a>

#### PDF pubblico di un DC

`GET /pem/{serial_number}/fiscale/consultazione/dc/{hash}/pdf`

Pubblica: nessun token.

Il PDF firmato del documento, senza token: è l'URL di Content-Location della risposta di emissione. Sta fuori da /api. Limite di 60 richieste al minuto per indirizzo IP. width e font_size cambiano solo il PDF: il testo ESC/POS nei metadati segue le impostazioni di stampa dell'esercente.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `hash` | `string` | sì | L'hash del documento, URL-encoded. Esempio: `YjQ0ZDJmMGE5YzFl...MmEzYjQ=`. |

**Parametri della query**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `width` | `integer` | no | Larghezza in mm, da 58 a 120 (default 120); i valori fuori intervallo vengono riportati al limite. Esempio: `58`. |
| `font_size` | `integer` | no | Corpo del testo, da 6 a 14 (default 10), con lo stesso limite. Esempio: `8`. |
| `regalo` | `integer` | no | Con 1, la copia di cortesia senza importi (solo per i DC di vendita). Esempio: `1`. |

**Risposte**

- `200` — Il PDF firmato del documento.

Corpo: `application/pdf`, binario.

- `429` — Più di 60 richieste al minuto dallo stesso indirizzo IP.

```json
{
    "message": "Too Many Attempts."
}
```

### Funzioni fiscali - Controllo PEM

<a id="get-api-pem-serial_number-controllo"></a>

#### Dati di controllo AdE del PEM

`GET /api/pem/{serial_number}/controllo`

Scope richiesto ai client partner: `fiscale:read`.

Restituisce i dati identificativi, di stato e di versione richiesti per il controllo del PEM.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "data": {
        "matricola": "ASVS-000001",
        "stato": "In servizio",
        "data_cambio_stato": "01-01-2025 10:00:00",
        "esercente": {
            "partita_iva": "12345678901",
            "codice_fiscale": "RSSMRA80A01H501Z",
            "denominazione": "Esercente Srl"
        },
        "erogatore": {
            "partita_iva": "12345678901",
            "codice_fiscale": "RSSMRA80A01H501Z",
            "denominazione": "Erogatore Srl"
        },
        "soluzione_software": {
            "sigla": "123e4567-e89b-12d3-a456-426614174000",
            "id_approvazione": "0000",
            "data_approvazione": null
        },
        "versione_installata": {
            "versione": 1,
            "sotto_versione": 0,
            "patch": "0",
            "data_ora": "01-01-2025 10:00:00",
            "swid": null
        },
        "versione_disponibile": {
            "versione": 1,
            "sotto_versione": 0,
            "patch": "0",
            "data_creazione": "01-01-2025 10:00:00",
            "data_approvazione": null,
            "swid": null
        },
        "dc_non_trasmessi": {
            "count": 3,
            "importo_totale": 125.5
        }
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa."
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — PEM non trovato

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="post-api-pem-serial_number-fiscale-gestionali-controllo"></a>

#### Emetti Documento Gestionale di Controllo

`POST /api/pem/{serial_number}/fiscale/gestionali/controllo`

Scope richiesto ai client partner: `fiscale:manage`.

Genera il PDF dei dati di controllo del PEM lato backend e lo archivia
come Documento Gestionale (tipo `controllo`), restituendolo in download.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — Il PDF firmato del documento gestionale.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento gestionale. | `attachment; filename="documento_gestionale_0012-0001.pdf"` |

Corpo: `application/pdf`, binario.

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa."
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — PEM non trovato

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — Nessun Journal aperto

```json
{
    "success": false,
    "message": "Nessun Journal aperto disponibile"
}
```

### Funzioni fiscali - Documenti gestionali

<a id="get-api-pem-serial_number-fiscale-gestionali"></a>

#### Archivio documenti gestionali

`GET /api/pem/{serial_number}/fiscale/gestionali`

Scope richiesto ai client partner: `fiscale:read`.

Elenca i documenti gestionali del PEM, di qualunque tipo, filtrabili per
data di elaborazione e per tipo. Senza `Accept: application/json` rende la
pagina Inertia dell'archivio.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Parametri della query**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `data` | `string` | no | Filtra per data di elaborazione. Esempio: `2026-08-20`. |
| `tipo` | `string` | no | Filtra per tipo di documento gestionale. Valori: `chiusura`, `controllo`, `operazioni`, `esiti_trasmissione`, `storico_buoni_pasto`. Esempio: `operazioni`. |

**Risposte**

- `200` — I documenti gestionali del PEM, dal più recente

```json
{
    "success": true,
    "gestionali": [
        {
            "id": 5,
            "progressivo": "0012-0001",
            "tipo": "operazioni",
            "data_elaborazione": "2026-10-08T10:15:42+02:00",
            "parametri": {
                "selezione": "date",
                "data_inizio": "2026-10-01",
                "data_fine": "2026-10-08"
            }
        }
    ]
}
```

- `401` — 

```json
{
    "success": false,
    "message": "Il token di accesso non è valido.",
    "error": "token_invalid"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="get-api-pem-serial_number-fiscale-gestionali-id-pdf"></a>

#### Download di un documento gestionale archiviato

`GET /api/pem/{serial_number}/fiscale/gestionali/{id}/pdf`

Scope richiesto ai client partner: `fiscale:read`.

Restituisce il PDF firmato già emesso, qualunque ne sia il tipo.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `id` | `integer` | sì | L'id del documento gestionale (dall'archivio). Esempio: `5`. |

**Risposte**

- `200` — Il PDF firmato del documento gestionale.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento gestionale. | `attachment; filename="documento_gestionale_0012-0001.pdf"` |

Corpo: `application/pdf`, binario.

- `401` — 

```json
{
    "success": false,
    "message": "Il token di accesso non è valido.",
    "error": "token_invalid"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Non trovato

```json
{
    "success": false,
    "message": "Documento gestionale non trovato"
}
```

<a id="post-api-pem-serial_number-fiscale-gestionali-operazioni"></a>

#### Documento gestionale operazioni memorizzate

`POST /api/pem/{serial_number}/fiscale/gestionali/operazioni`

Scope richiesto ai client partner: `fiscale:manage`.

Emette e restituisce il documento gestionale con le operazioni memorizzate
dal PEM secondo la chiave di selezione indicata (ST v1.4 §3.4.2, punti 1-4).
La selezione `integrale` produce il solo riepilogo; le selezioni delimitate
includono anche il dettaglio per documento e gli aggregati giornalieri.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `selezione` | `string` | sì | Chiave di selezione: `integrale` (tutto l'archivio, solo riepilogo), `date` (tra due date), `documento` (per numero di documento), `intervallo` (tra due numeri di documento dello stesso journal). Valori: `integrale`, `date`, `documento`, `intervallo`. Esempio: `date`. |
| `data_inizio` | `string` | no | Data di inizio del periodo. Obbligatoria con `selezione=date`. Deve essere una data valida. Esempio: `2026-10-01`. |
| `data_fine` | `string` | no | Data di fine del periodo. Obbligatoria con `selezione=date`. Deve essere una data valida. Deve essere una data uguale o successiva a <code>data_inizio</code>. Esempio: `2026-10-08`. |
| `progressivo` | `string` | no | Numero del documento in forma `NNNN-PPPP`. Obbligatorio con `selezione=documento`. Al massimo 32 caratteri. Esempio: `0089-0001`. |
| `journal` | `integer` | no | Progressivo del journal (`NNNN`). Obbligatorio con `selezione=intervallo`. Almeno 1. Esempio: `89`. |
| `progressivo_da` | `integer` | no | Progressivo iniziale del documento (`PPPP`). Obbligatorio con `selezione=intervallo`. Almeno 1. Al massimo 9999. Esempio: `1`. |
| `progressivo_a` | `integer` | no | Progressivo finale del documento (`PPPP`). Obbligatorio con `selezione=intervallo`. Al massimo 9999. Esempio: `50`. |
| `metodi_pagamento` | `array<string>` | no | Metodo di pagamento specifico da filtrare (es. "PC" per contanti, "PP" per pagamento elettronico). Esempio: `["PP"]`. |
| `utenti` | `array<integer>` | no | ID utente specifico da filtrare. Esempio: `[121]`. |
| `tipo_documenti` | `array<string>` | no | Tipo di documento specifico da filtrare (es. "DC", "A" per annullo, "R" per reso). Esempio: `["DC"]`. |
| `ateco` | `array<string>` | no | Codice ATECO specifico da filtrare. Esempio: `["011101"]`. |
| `ricerca` | `string` | no | Testo libero su progressivo DC (es. '0089-0001') o descrizione di un elemento contabile. Al massimo 255 caratteri. Può essere `null`. Esempio: `0089`. |

Esempio:

```json
{
    "selezione": "date",
    "data_inizio": "2026-10-01",
    "data_fine": "2026-10-08"
}
```

**Risposte**

- `200` — Il PDF firmato del documento gestionale.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento gestionale. | `attachment; filename="documento_gestionale_0012-0001.pdf"` |

Corpo: `application/pdf`, binario.

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — Cassa chiusa

```json
{
    "success": false,
    "message": "Nessun Journal aperto disponibile"
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

<a id="post-api-pem-serial_number-fiscale-gestionali-esiti-trasmissione"></a>

#### Documento gestionale esiti trasmissioni corrispettivi

`POST /api/pem/{serial_number}/fiscale/gestionali/esiti-trasmissione`

Scope richiesto ai client partner: `fiscale:manage`.

Emette e restituisce il documento gestionale con gli esiti delle
trasmissioni dei corrispettivi comprese tra due date (ST v1.4 §3.4.2,
punto 5). I dati provengono da `richieste_pem`: nessuna chiamata al PEL.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `data_inizio` | `string` | sì | Data di inizio dell'intervallo di trasmissioni. Deve essere una data valida. Esempio: `2026-01-01`. |
| `data_fine` | `string` | sì | Data di fine dell'intervallo di trasmissioni. Deve essere una data valida. Deve essere una data uguale o successiva a <code>data_inizio</code>. Esempio: `2026-08-20`. |

Esempio:

```json
{
    "data_inizio": "2026-01-01",
    "data_fine": "2026-08-20"
}
```

**Risposte**

- `200` — Il PDF firmato del documento gestionale.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento gestionale. | `attachment; filename="documento_gestionale_0012-0001.pdf"` |

Corpo: `application/pdf`, binario.

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — Cassa chiusa

```json
{
    "success": false,
    "message": "Nessun Journal aperto disponibile"
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

<a id="post-api-pem-serial_number-fiscale-gestionali-storico-buoni-pasto"></a>

#### Documento gestionale storico opzione buoni pasto

`POST /api/pem/{serial_number}/fiscale/gestionali/storico-buoni-pasto`

Scope richiesto ai client partner: `fiscale:manage`.

Emette e restituisce il documento gestionale con lo storico delle
attivazioni e disattivazioni dell'opzione buoni pasto (ST v1.4 §3.4.2,
punto 8). Nessun parametro di selezione.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — Il PDF firmato del documento gestionale.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento gestionale. | `attachment; filename="documento_gestionale_0012-0001.pdf"` |

Corpo: `application/pdf`, binario.

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — Cassa chiusa

```json
{
    "success": false,
    "message": "Nessun Journal aperto disponibile"
}
```

<a id="post-api-pem-serial_number-fiscale-gestionali-chiusura-journalid"></a>

#### Documento gestionale di chiusura

`POST /api/pem/{serial_number}/fiscale/gestionali/chiusura/{journalId}`

Scope richiesto ai client partner: `fiscale:manage`.

Emette il Documento Gestionale PDF firmato della giornata fiscale chiusa.
I totali provengono dal PEL, unica fonte fiscale. Come per il report vendite
ogni richiesta emette un nuovo documento, numerato sul journal corrente; le
copie già emesse restano scaricabili dall'archivio gestionali.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `journalId` | `integer` | sì | L'id della chiusura (campo `id` dello storico chiusure). Esempio: `12`. |

**Risposte**

- `200` — Il PDF firmato del documento gestionale.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento gestionale. | `attachment; filename="documento_gestionale_0012-0001.pdf"` |

Corpo: `application/pdf`, binario.

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — Corrispettivi non trasmessi

```json
{
    "success": false,
    "message": "Chiusura non ancora trasmessa all'AdE"
}
```

### Funzioni fiscali - Emergenza

<a id="post-api-pem-serial_number-fiscale-emergenza-chiusura"></a>

#### Chiusura di emergenza

`POST /api/pem/{serial_number}/fiscale/emergenza/chiusura`

Scope richiesto ai client partner: `fiscale:manage`.

Trasmette al PEL gli aggregati della giornata annotati sul registro di
emergenza dei corrispettivi: riepiloghi IVA e totali dei pagamenti,
dichiarati dall'esercente. Ammessa solo a PEM `Fuori servizio`; la
chiusura riguarda la giornata in cui è inviata.

Una giornata ammette più chiusure, anche con dati diversi: ogni
`idempotency_key` (UUID scelto dal client) è una chiusura e un invio
distinto al PEL. Ripetere la chiamata con la stessa chiave è sicuro: se
la chiusura è già trasmessa si riceve il suo esito senza un nuovo invio,
se l'invio era fallito lo si ritenta. La stessa chiave con dati diversi,
o già usata su un altro PEM, è rifiutata (422).

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `idempotency_key` | `string` | sì | UUID scelto dal client, uno per chiusura e propagato al PEL. Ripetendo la chiamata con la stessa chiave non si crea una seconda chiusura: si riceve l'esito di quella già trasmessa, o si ritenta l'invio fallito. La stessa chiave con dati diversi, o già usata su un altro PEM, è rifiutata (422). Deve essere un UUID valido. Esempio: `3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c`. |
| `n_doc_commerciali` | `integer` | sì | Numero di documenti commerciali annotati sul registro di emergenza nella giornata. Almeno 1. Al massimo 999999999. Esempio: `12`. |
| `riepiloghi` | `array<object>` | sì |  |
| `riepiloghi[].iva` | `string` | no | Aliquota IVA (4, 5, 10, 22). Richiede `imposta`. Può essere `null`. Esempio: `22`. |
| `riepiloghi[].natura` | `string` | no | Codice natura (N1–N6), in alternativa a `iva`. Valori: `N1`, `N2`, `N3`, `N4`, `N5`, `N6`. Può essere `null`. |
| `riepiloghi[].ventilazione_iva` | `string` | no | Ventilazione IVA (`SI`), in alternativa a `iva` e `natura`. Valori: `SI`. Può essere `null`. |
| `riepiloghi[].importo_parziale` | `string` | sì | Imponibile della riga, stringa decimale con al massimo 2 decimali (può essere negativo). Al netto dell'IVA (al lordo con `ventilazione_iva`), al netto di resi, annulli, beni in sospeso e non riscossi, ma comprensivo degli omaggi. Formato: <code>/^-?\d{1,11}(\.\d{1,2})?$/</code>. Esempio: `100.00`. |
| `riepiloghi[].imposta` | `string` | no | Imposta della riga: obbligatoria se e solo se è indicata `iva`, coerente con `importo_parziale × aliquota` entro ±0,01. Formato: <code>/^-?\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. Esempio: `22.00`. |
| `riepiloghi[].codice_attivita` | `string` | no | Codice Ateco della riga (facoltativo), tra quelli configurati sul PEM. Valori: `null`. Può essere `null`. |
| `riepiloghi[].totale_ammontare_resi` | `string` | no | Ammontare dei resi (facoltativo, ≥ 0, al netto dell'IVA; al lordo con ventilazione IVA). Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. |
| `riepiloghi[].totale_ammontare_annulli` | `string` | no | Ammontare degli annulli (facoltativo, ≥ 0). Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. |
| `riepiloghi[].beni_in_sospeso` | `string` | no | Beni in sospeso (facoltativo, ≥ 0). Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. |
| `riepiloghi[].non_riscosso_servizi` | `string` | no | Non riscosso servizi (facoltativo, ≥ 0). Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. |
| `riepiloghi[].non_riscosso_fatture` | `string` | no | Non riscosso fatture (facoltativo, ≥ 0). Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. |
| `riepiloghi[].non_riscosso_dcra_ssn` | `string` | no | Non riscosso DCR a SSN (facoltativo, ≥ 0). Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. |
| `riepiloghi[].non_riscosso_omaggio` | `string` | no | Non riscosso omaggio (facoltativo, ≥ 0). Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Può essere `null`. |
| `totali` | `object` | sì |  |
| `totali.pagato_contanti` | `string` | sì | Totale pagato in contanti. Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Esempio: `100.00`. |
| `totali.pagato_elettronico` | `string` | sì | Totale pagato con strumenti elettronici. Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Esempio: `77.55`. |
| `totali.sconto_a_pagare` | `string` | sì | Totale degli sconti a pagare. Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. Esempio: `0.00`. |
| `totali.ticket` | `object` | no | Buoni pasto incassati (facoltativo). Senza opzione buoni pasto sono un normale pagamento. Con l'opzione attiva nella giornata richiedono un riepilogo al 10% il cui lordo (`(importo_parziale + non_riscosso_servizi) × 1,10`) li copra, e il sistema attribuisce il riferimento normativo `TICKET` ai riepiloghi al 10%: il client non invia `rif_normativo`. Può essere `null`. |
| `totali.ticket.pagato_ticket` | `string` | no | Importo pagato con buoni pasto. Obbligatorio se è presente <code>totali.ticket</code>. Formato: <code>/^\d{1,11}(\.\d{1,2})?$/</code>. |
| `totali.ticket.numero_ticket` | `integer` | no | Numero di buoni pasto (≥ 1). Obbligatorio se è presente <code>totali.ticket</code>. Almeno 1. Al massimo 999999999. |

Esempio:

```json
{
    "idempotency_key": "3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c",
    "n_doc_commerciali": 12,
    "riepiloghi": [
        {
            "iva": "22",
            "importo_parziale": "100.00",
            "imposta": "22.00"
        },
        {
            "iva": "10",
            "importo_parziale": "50.50",
            "imposta": "5.05"
        }
    ],
    "totali": {
        "pagato_contanti": "100.00",
        "pagato_elettronico": "77.55",
        "sconto_a_pagare": "0.00"
    }
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "dati": {
        "id_presa_in_carico": "12345",
        "n_doc_commerciali": 12
    }
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — 

### Funzioni fiscali - Opzione buoni pasto

<a id="get-api-pem-serial_number-fiscale-opzione-buoni-pasto"></a>

#### Stato opzione buoni pasto

`GET /api/pem/{serial_number}/fiscale/opzione-buoni-pasto`

Scope richiesto ai client partner: `fiscale:read`.

Restituisce lo stato *efficace* dell'opzione (quello che governa
l'emissione) e, se una scelta è stata registrata ma decorre dal primo
giorno del mese successivo (§5.3), anche la scelta in sospeso.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "attiva": true,
    "opzione": {
        "attiva": true,
        "aggiornato_il": "12-08-2026 10:00:00",
        "decorre_dal": "12-08-2026 10:00:00"
    },
    "scelta_pendente": null,
    "modificabile": true
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="post-api-pem-serial_number-fiscale-opzione-buoni-pasto"></a>

#### Attiva/disattiva opzione buoni pasto

`POST /api/pem/{serial_number}/fiscale/opzione-buoni-pasto`

Scope richiesto ai client partner: `fiscale:manage`.

Registra l'attivazione o disattivazione dell'opzione per il trattamento dei
buoni pasto (ST v1.4 §5.3). Ammessa solo a cassa chiusa. Ha effetto dal
momento della scelta, salvo che nel mese corrente sia già stato emesso
almeno un DC con buoni pasto accettati: in tal caso decorre dal primo
giorno del mese successivo (campo `decorre_dal` della risposta).

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `attiva` | `boolean` | sì | true per attivare l'opzione di trattamento dei buoni pasto, false per disattivarla. Il cambio è ammesso solo a cassa chiusa. Esempio: `true`. |

Esempio:

```json
{
    "attiva": true
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Opzione buoni pasto aggiornata",
    "opzione": {
        "attiva": true,
        "aggiornato_il": "12-08-2026 10:00:00",
        "decorre_dal": "01-09-2026 00:00:00"
    }
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Ruolo non ammesso

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Cassa aperta

```json
{
    "error_code": "BP001",
    "message": "Impossibile modificare opzione buoni pasto. Effettua chiusura di cassa e riprova."
}
```

<a id="get-api-pem-serial_number-fiscale-opzione-buoni-pasto-storico"></a>

#### Storico opzione buoni pasto

`GET /api/pem/{serial_number}/fiscale/opzione-buoni-pasto/storico`

Scope richiesto ai client partner: `fiscale:read`.

Restituisce lo storico delle attivazioni/disattivazioni dell'opzione,
come richiesto dalle ST v1.4 (§3.4.2, lettura e stampa).

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "storico": [
        {
            "attiva": true,
            "aggiornato_il": "12-08-2026 10:00:00",
            "utente": "Mario Rossi"
        }
    ]
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

### Funzioni fiscali - Richieste PEM

<a id="get-api-pem-serial_number-fiscale-richieste"></a>

#### Lista richieste

`GET /api/pem/{serial_number}/fiscale/richieste`

Scope richiesto ai client partner: `fiscale:read`.

Recupera la lista delle richieste effettuate dal PEM specificato.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Parametri della query**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `tipo` | `string` | no | Filtra per tipo di richiesta (CENSIMENTO, CORRISPETTIVI). Valori: `CENSIMENTO`, `CORRISPETTIVI`. Esempio: `CENSIMENTO`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Richieste PEM recuperate",
    "richieste": [
        {
            "id": 1,
            "id_presa_in_carico": "ABC123",
            "stato": "PRONTA",
            "data": "2025-07-01 10:30:00",
            "tipo": "CENSIMENTO"
        }
    ]
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

<a id="get-api-pem-serial_number-fiscale-richieste-stato-idrichiesta"></a>

#### Verifica stato richiesta

`GET /api/pem/{serial_number}/fiscale/richieste/stato/{idRichiesta}`

Scope richiesto ai client partner: `fiscale:read`.

Controlla lo stato attuale di una richiesta PEM specifica.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `idRichiesta` | `integer` | sì | L'id della richiesta (campo `id` della lista). Esempio: `1`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Stato richiesta: COMPLETED",
    "stato": "COMPLETED"
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="get-api-pem-serial_number-fiscale-richieste-esito-idrichiesta"></a>

#### Recupera esito richiesta

`GET /api/pem/{serial_number}/fiscale/richieste/esito/{idRichiesta}`

Scope richiesto ai client partner: `fiscale:read`.

Recupera l'esito finale di una richiesta pronta.
Se l'esito è già presente nel database, lo restituisce direttamente
senza effettuare una nuova chiamata al PEL.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |
| `idRichiesta` | `integer` | sì | L'id della richiesta (campo `id` della lista). Esempio: `1`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Esito richiesta recuperato",
    "esito": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4..."
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

### Funzioni fiscali - Segnalazioni

<a id="get-api-pem-serial_number-fiscale-segnalazioni-inattivita"></a>

#### Periodi di inattività

`GET /api/pem/{serial_number}/fiscale/segnalazioni/inattivita`

Scope richiesto ai client partner: `fiscale:read`.

Elenca i periodi di inattività segnalati per il PEM, dal più recente, con
lo stato dell'inoltro al PEL.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "status": "success",
    "data": [
        {
            "id": 3,
            "serial_number": "ASVS-241232",
            "dal": "2026-12-24 00:00:00",
            "al": "2027-01-06 23:59:59",
            "stato": "PENDING"
        }
    ]
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="post-api-pem-serial_number-fiscale-segnalazioni-inattivita"></a>

#### Segnala un periodo di inattività

`POST /api/pem/{serial_number}/fiscale/segnalazioni/inattivita`

Scope richiesto ai client partner: `fiscale:manage`.

Comunica all'AdE, tramite il PEL, un periodo futuro in cui il PEM non
emetterà documenti (ferie, chiusura stagionale). La segnalazione è
accodata e inoltrata in modo asincrono: la risposta è 202. Le date sono
nel formato `Y-m-d H:i:s`, `al` successiva a `dal` e nel futuro.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `dal` | `string` | sì | Data nel formato <code>Y-m-d H:i:s</code>. Esempio: `2026-11-01 00:00:00`. |
| `al` | `string` | sì | Data nel formato <code>Y-m-d H:i:s</code>. Deve essere una data successiva a <code>dal</code>. Esempio: `2026-11-30 23:59:59`. |

Esempio:

```json
{
    "dal": "2026-11-01 00:00:00",
    "al": "2026-11-30 23:59:59"
}
```

**Risposte**

- `202` — success

```json
{
    "status": "success",
    "message": "Periodo inattivo registrato"
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

### Funzioni fiscali - Stato PEM

<a id="get-api-pem-serial_number-fiscale-stati"></a>

#### Lista Stati PEM

`GET /api/pem/{serial_number}/fiscale/stati`

Scope richiesto ai client partner: `fiscale:read`.

Restituisce la lista degli stati del PEM corrente.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "data": [
        {
            "id": 1,
            "stato": "In servizio"
        },
        {
            "id": 2,
            "stato": "Dismesso"
        }
    ]
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

<a id="put-api-pem-serial_number-fiscale-stato"></a>

#### Modifica lo stato del PEM

`PUT /api/pem/{serial_number}/fiscale/stato`

Scope richiesto ai client partner: `fiscale:manage`.

Comunica al PEL il cambio di stato del PEM.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `stato` | `string` | sì | Stato del PEM (01: Fuori servizio, 03: In servizio). La dismissione (02) deve usare endpoint dedicato. Formato: <code>/^\d{2}$/D</code>. Valori: `01`, `03`. Esempio: `01`. |
| `motivazione` | `string` | no | Motivazione del Fuori servizio (01), obbligatoria con quello stato (01: Problemi hardware PEM, 02: Problemi software PEM, 03: Problemi di comunicazione PEM-PEL, 08: Mancata integrità, 09: Messa in magazzino - periodo di inattività, 10: Altro). Da non inviare con stato In servizio (03): il sistema trasmette 06 (Inizio ciclo di vita PEM) al primo passaggio in servizio e 07 (Ripristino da un fuori servizio) altrimenti. Formato: <code>/^\d{2}$/D</code>. Valori: `01`, `02`, `03`, `08`, `09`, `10`. Può essere `null`. Esempio: `01`. |
| `note` | `string` | no | Note sul cambiamento di stato, obbligatorie con stato Fuori servizio (01) qualunque sia la motivazione (AdE rifiuta altrimenti con CS009); facoltative con stato In servizio (03). Massimo 255 caratteri. Al massimo 255 caratteri. Può essere `null`. Esempio: `Guasto alla rete del punto vendita`. |

Esempio:

```json
{
    "stato": "01",
    "motivazione": "01",
    "note": "Guasto alla rete del punto vendita"
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Stato PEM aggiornato con successo",
    "data": {
        "id": 1,
        "stato": "In servizio"
    }
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Validazione fallita

```json
{
    "success": false,
    "message": "La motivazione è obbligatoria",
    "errors": {
        "motivazione": [
            "Il campo motivazione è obbligatorio"
        ]
    }
}
```

<a id="post-api-pem-serial_number-fiscale-stato-dismissione"></a>

#### Dismissione del PEM

`POST /api/pem/{serial_number}/fiscale/stato/dismissione`

Scope richiesto ai client partner: `fiscale:manage`.

Porta il PEM nello stato definitivo *Dismesso* e lo comunica al PEL. Riservata
all'esercente titolare; lo stato e la motivazione non si scelgono (campi rifiutati).

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `note` | `string` | sì | Note sulla dismissione del PEM, obbligatorie (AdE rifiuta altrimenti con CS009). Massimo 255 caratteri. Al massimo 255 caratteri. Esempio: `Chiusura definitiva del ciclo di vita PEM`. |

Esempio:

```json
{
    "note": "Chiusura definitiva del ciclo di vita PEM"
}
```

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Dismissione PEM completata con successo",
    "data": {
        "id": 7,
        "stato": "Dismesso"
    }
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

### Funzioni fiscali - Trasmissioni

<a id="post-api-pem-serial_number-fiscale-invio-dc"></a>

#### Invio DC

`POST /api/pem/{serial_number}/fiscale/invio/dc`

Scope richiesto ai client partner: `fiscale:manage`.

Emette un documento commerciale di vendita: genera e firma l'XML, lo aggiunge
al Journal e ne accoda la trasmissione al PEL (la risposta non attende il PEL).
Ammesso solo su un PEM In servizio.

`idempotency_key` (UUID) rende sicuro ripetere la chiamata: con una chiave già
usata sul PEM la risposta è il documento già emesso, senza emetterne un altro.
Come richiesto dalle specifiche AdE, l'unico output è lo stream di byte del PDF
firmato (PAdES, `application/pdf`): il testo ESC/POS per la stampante termica è
nei metadati XMP del PDF (`billy:escPosBase64`) e `Content-Location` è l'URL
pubblico dello stesso PDF (con `?regalo=1` la copia di cortesia). Gli errori
restano JSON. Il primo DC dopo una chiusura apre la nuova giornata.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `idempotency_key` | `string` | sì | UUID scelto dal client, uno per documento: ripetendo la chiamata con la stessa chiave si riceve il documento già emesso. Deve essere un UUID valido. Al massimo 36 caratteri. Esempio: `3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c`. |
| `sconto_a_pagare` | `number` | no | Lo sconto a pagare sul documento commerciale. Può essere `null`. Esempio: `2`. |
| `arrotondamento_dl50_difetto` | `boolean` | no | Indica che lo sconto a pagare rappresenta l'arrotondamento DL 50/2017 per difetto. Ammesso solo con pagamento integralmente in contanti e importo massimo 0,02 euro. Può essere `null`. Esempio: `false`. |
| `arrotondamento_dl50_eccesso` | `number` | no | Importo dell'arrotondamento DL 50/2017 per eccesso. Ammesso solo con pagamento integralmente in contanti, mutuamente esclusivo con arrotondamento_dl50_difetto e importo massimo 0,02 euro. Almeno 0. Può essere `null`. Esempio: `0.01`. |
| `sconto_subtotale` | `number` | no | Sconto applicato all'intero subtotale (importo assoluto in euro, dopo gli sconti di linea). Viene ripartito sulle singole linee ai soli fini fiscali e non può superare il subtotale al netto degli sconti di linea. Almeno 0. Può essere `null`. Esempio: `1.8`. |
| `codice_lotteria` | `string` | no | Codice lotteria del cliente (massimo 8 caratteri). Richiede un totale di almeno 1 euro pagato con un unico pagamento PP; alternativo a cessionario_committente. Al massimo 8 caratteri. Può essere `null`. Esempio: `ABC12345`. |
| `cessionario_committente` | `string` | no | Codice fiscale del cliente (massimo 16 caratteri), per il documento commerciale con codice fiscale. Alternativo a codice_lotteria. Al massimo 16 caratteri. Può essere `null`. Esempio: `RSSMRA85M01H501Z`. |
| `ateco` | `string` | no | Codice ATECO dell'operazione (massimo 6 caratteri). Oggi è accettato ma non riportato nel documento. Al massimo 6 caratteri. Può essere `null`. Esempio: `561011`. |
| `appendice` | `array<string>` | no | Una riga di appendice (massimo 255 caratteri). Al massimo 255 caratteri. Esempio: `["Grazie e arrivederci"]`. |
| `elementi` | `array<object>` | sì | Le righe (elementi contabili) del documento commerciale, al massimo 200 e con al massimo 40 aliquote distinte. Al massimo 200 elementi. |
| `elementi[].descrizione` | `string` | sì | La descrizione dell'elemento. Al massimo 255 caratteri. Esempio: `Caffè espresso`. |
| `elementi[].bene_servizio` | `string` | sì | Indica se l'elemento è un bene (B) o un servizio (S). Valori: `B`, `S`. Esempio: `B`. |
| `elementi[].quantita` | `number` | sì | La quantità dell'elemento (due decimali). Almeno 0. Esempio: `2`. |
| `elementi[].prezzo_unitario` | `number` | sì | Il prezzo unitario dell'elemento, IVA inclusa, in euro (due decimali). Almeno 0. Esempio: `1.20`. |
| `elementi[].iva` | `string` | sì | Aliquota IVA (4, 5, 10, 22), natura dell'operazione (N1-N6) o VI per la ventilazione. Valori: `4`, `5`, `10`, `22`, `N1`, `N2`, `N3`, `N4`, `N5`, `N6`, `VI`. Esempio: `10`. |
| `elementi[].sconto` | `number` | no | Sconto sulla riga, importo assoluto in euro. Almeno 0. Può essere `null`. Esempio: `0.2`. |
| `elementi[].maggiorazione` | `number` | no | La maggiorazione applicata all'elemento come importo assoluto. Almeno 0. Può essere `null`. Esempio: `1.5`. |
| `elementi[].acconto_versato_beni_non_consegnati` | `boolean` | no | Indica se l'elemento è un acconto versato per beni non consegnati. Può essere `null`. Esempio: `false`. |
| `elementi[].omaggio` | `boolean` | no | Indica se l'elemento è un omaggio. Può essere `null`. Esempio: `false`. |
| `elementi[].id_reparto` | `integer` | no | L'ID del reparto associato all'elemento. Può essere `null`. Esempio: `1`. |
| `pagamenti` | `array<object>` | no | I pagamenti. Obbligatori se il totale da pagare è maggiore di zero; devono coprirlo (i contanti possono eccederlo: la differenza è il resto). |
| `pagamenti[].importo` | `number` | sì | L'importo del pagamento in euro. Almeno 0. Esempio: `2.40`. |
| `pagamenti[].metodo` | `string` | sì | PC contanti, PP elettronico, TK buoni pasto (con l'opzione buoni pasto attiva solo su operazioni al 10%), NRF/NRB/NRS/NRD non riscosso (fattura, beni, servizi, DCR a SSN). NRF e NRD escludono altri pagamenti. Valori: `PC`, `PP`, `TK`, `NRF`, `NRB`, `NRS`, `NRD`. Esempio: `PC`. |
| `pagamenti[].numero_ticket` | `integer` | no | Il numero del ticket per i pagamenti con metodo TK. Obbligatorio se <code>pagamenti.*.metodo</code> vale <code>TK</code>. Almeno 0. Esempio: `123456`. |
| `pagamenti[].pos` | `object` | no | Facoltativo, per i pagamenti PP: dati della transazione sul terminale. |
| `pagamenti[].pos.pos` | `string` | no | Circuito o fornitore del terminale (massimo 32 caratteri). Obbligatorio se è presente <code>pagamenti.*.pos</code>. Esempio: `Nexi`. |
| `pagamenti[].pos.uuid` | `string` | no | Identificativo univoco della transazione. Obbligatorio se è presente <code>pagamenti.*.pos</code>. Al massimo 50 caratteri. Esempio: `9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f`. |
| `pagamenti[].pos.data_ora` | `string` | no | Data e ora della transazione. Obbligatorio se è presente <code>pagamenti.*.pos</code>. Deve essere una data valida. Esempio: `2026-10-08T10:15:00+02:00`. |
| `pagamenti[].pos.tid` | `string` | no | Terminal ID. Al massimo 36 caratteri. Può essere `null`. Esempio: `12345678`. |
| `pagamenti[].pos.auth_code` | `string` | no | Codice di autorizzazione (massimo 20 caratteri). Al massimo 255 caratteri. Può essere `null`. Esempio: `A1B2C3`. |

Esempio:

```json
{
    "idempotency_key": "3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c",
    "elementi": [
        {
            "descrizione": "Caffè espresso",
            "bene_servizio": "B",
            "quantita": "2",
            "prezzo_unitario": "1.20",
            "iva": "10"
        }
    ],
    "pagamenti": [
        {
            "metodo": "PC",
            "importo": "2.40"
        }
    ]
}
```

**Risposte**

- `200` — Il PDF firmato (PAdES) del documento emesso.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento (NNNN-PPPP). | `inline; filename="DC_0012-0007.pdf"` |
| `Content-Location` | L'URL pubblico dello stesso PDF, senza token: con `?regalo=1` la copia di cortesia (solo DC di vendita). Il segmento prima di /pdf è l'hash del documento, URL-encoded. | `https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf` |

Corpo: `application/pdf`, binario.

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — Giornata aperta da più di 24 ore

```json
{
    "error_code": "JN002",
    "message": "Apertura effettuata da più di 24 ore (ID: 42, Data: 2026-10-07 08:00:00)"
}
```

- `422` — Validazione fallita

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

- `502` — Il PEL non ha aperto la nuova giornata (JN004): il documento non è emesso, ripetere con la stessa idempotency_key.

```json
{
    "error_code": "JN004",
    "message": "Errore apertura journal PEL: …"
}
```

<a id="post-api-pem-serial_number-fiscale-invio-annullo"></a>

#### Annulla DC

`POST /api/pem/{serial_number}/fiscale/invio/annullo`

Scope richiesto ai client partner: `fiscale:manage`.

Emette il documento commerciale di annullo di una vendita e ne accoda la
trasmissione al PEL. Per una vendita dello stesso PEM basta il suo
`progressivo` (`NNNN-PPPP`): righe e codice lotteria si prendono dall'originale.
Per un documento di un altro dispositivo si indicano `matricola_annullo`,
`progressivo`, `dataOra` ed `elementi`; con matricola `POS`, `VR` o `ND` il
progressivo non serve. La risposta, come per *Invio DC*, è il PDF firmato del documento.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `idempotency_key` | `string` | sì | Chiave UUID usata per rendere idempotente la richiesta di annullo. Deve essere un UUID valido. Al massimo 36 caratteri. Esempio: `9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d`. |
| `progressivo` | `string` | no | Il progressivo (NNNN-PPPP) del documento da annullare. Obbligatorio salvo matricola_annullo POS, VR o ND. Al massimo 11 caratteri. Esempio: `0012-0007`. |
| `matricola_annullo` | `string` | no | Solo per un documento emesso da un altro dispositivo: la sua matricola, oppure POS, VR o ND. Vuoto per un documento di questo PEM. Al massimo 11 caratteri. Esempio: `99MEX012345`. |
| `dataOra` | `string` | no | Data e ora del documento da annullare. Obbligatoria con matricola_annullo. Deve essere una data valida. Esempio: `2026-01-15 18:30:00`. |
| `codice_lotteria` | `string` | no | Codice lotteria associato al documento. Questo campo è opzionale e va fornito se il documento da annullare è stato emesso da un altro dispositivo. Al massimo 8 caratteri. Può essere `null`. Esempio: `ABC12345`. |
| `elementi` | `array<object>` | no | L'array di elementi del documento di annullo. Questo campo è obbligatorio se il documento da annullare è stato emesso da un altro dispositivo. |
| `elementi[].descrizione` | `string` | sì | La descrizione dell'elemento. Al massimo 255 caratteri. Esempio: `Prodotto A`. |
| `elementi[].bene_servizio` | `string` | sì | Indica se l'elemento è un bene (B) o un servizio (S). Valori: `B`, `S`. Esempio: `B`. |
| `elementi[].quantita` | `number` | sì | La quantità dell'elemento. Esempio: `2`. |
| `elementi[].prezzo_unitario` | `number` | sì | Il prezzo unitario dell'elemento. Esempio: `100`. |
| `elementi[].iva` | `string` | no | Il codice IVA applicato all'elemento. Obbligatorio se manca <code>elementi.*.id</code>. Valori: `4`, `5`, `10`, `22`, `N1`, `N2`, `N3`, `N4`, `N5`, `N6`, `VI`. |
| `elementi[].sconto` | `number` | no | Lo sconto applicato all'elemento. Può essere `null`. Esempio: `10`. |
| `elementi[].acconto_versato_beni_non_consegnati` | `boolean` | no | Indica se l'elemento è un acconto versato per beni non consegnati. Può essere `null`. Esempio: `false`. |
| `elementi[].omaggio` | `boolean` | no | Indica se l'elemento è un omaggio. Può essere `null`. Esempio: `false`. |
| `elementi[].ateco` | `string` | no | Al massimo 6 caratteri. Può essere `null`. Esempio: `bngz`. |

Esempio:

```json
{
    "idempotency_key": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "progressivo": "0012-0007"
}
```

**Risposte**

- `200` — Il PDF firmato (PAdES) del documento emesso.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento (NNNN-PPPP). | `inline; filename="DC_0012-0007.pdf"` |
| `Content-Location` | L'URL pubblico dello stesso PDF, senza token: con `?regalo=1` la copia di cortesia (solo DC di vendita). Il segmento prima di /pdf è l'hash del documento, URL-encoded. | `https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf` |

Corpo: `application/pdf`, binario.

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Documento inesistente

```json
{
    "error_code": "DC001",
    "message": "Documento commerciale non trovato"
}
```

- `409` — Già annullato

```json
{
    "error_code": "DC003",
    "message": "Il documento commerciale è già stato annullato"
}
```

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

- `502` — Il PEL non ha aperto la nuova giornata (JN004): il documento non è emesso, ripetere con la stessa idempotency_key.

```json
{
    "error_code": "JN004",
    "message": "Errore apertura journal PEL: …"
}
```

<a id="get-api-pem-serial_number-fiscale-invio-reso"></a>

#### Recupera dati reso DC

`GET /api/pem/{serial_number}/fiscale/invio/reso`

Scope richiesto ai client partner: `fiscale:manage`.

Restituisce le righe di un documento commerciale, con il loro `id` e la
quantità già resa (`reso`), per comporre la richiesta di reso.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Parametri della query**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `hash` | `string` | sì | L'hash del documento commerciale (campo `hash` della risposta di emissione). Esempio: `YjQ0ZDJmMGE5YzFlNGI3ZDhmMDAxMmFiMzRjZDU2ZWY3ODkwYTFiMmMzZDRlNWY2YTdiOGM5ZDBlMWYyYTNiNA==`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "dati": {
        "progressivo": "0012-0007",
        "data": "2026-10-08 10:15:42",
        "serial_number": "ASVS-241232",
        "elementi_contabili": [
            {
                "id": 5531,
                "descrizione": "Caffè espresso",
                "bene_servizio": "B",
                "quantita": "2.00",
                "prezzo_unitario": "1.20",
                "sconto": "0.00",
                "iva": "10",
                "acconto_versato_beni_non_consegnati": false,
                "omaggio": false,
                "reso": 0
            }
        ]
    }
}
```

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Documento inesistente

```json
{
    "success": false,
    "message": "Documento commerciale non trovato"
}
```

<a id="post-api-pem-serial_number-fiscale-invio-reso"></a>

#### Reso DC

`POST /api/pem/{serial_number}/fiscale/invio/reso`

Scope richiesto ai client partner: `fiscale:manage`.

Emette il documento commerciale di reso, totale o parziale, e ne accoda la
trasmissione al PEL. Per una vendita dello stesso PEM si indicano il suo
`progressivo` e, per ogni riga resa, l'`id` della riga originale (da *Recupera
dati reso DC*) con la `quantita`: il resto si prende dall'originale. Le righe di
servizio non sono rendibili. Per un documento di un altro dispositivo si
indicano `matricola_reso`, `progressivo`, `dataOra` e le righe complete.
La risposta, come per *Invio DC*, è il PDF firmato del documento.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Corpo della richiesta** (`application/json`, obbligatorio)

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `idempotency_key` | `string` | sì | Chiave UUID usata per rendere idempotente la richiesta di reso. Deve essere un UUID valido. Al massimo 36 caratteri. Esempio: `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`. |
| `progressivo` | `string` | no | Il progressivo (NNNN-PPPP) del documento di vendita da rendere. Obbligatorio salvo matricola_reso POS, VR o ND. Al massimo 11 caratteri. Esempio: `0012-0007`. |
| `matricola_reso` | `string` | no | Solo per un documento emesso da un altro dispositivo: la sua matricola, oppure POS, VR o ND. Vuoto per un documento di questo PEM. Al massimo 11 caratteri. Esempio: `99MEX012345`. |
| `dataOra` | `string` | no | Data e ora del documento da rendere. Obbligatoria con matricola_reso. Deve essere una data valida. Esempio: `2026-01-15 18:30:00`. |
| `codice_lotteria` | `string` | no | Il codice lotteria associato al reso. Questo campo è opzionale e va fornito se il documento di cui si vuole fare il reso è stato emesso da un altro dispositivo. Al massimo 8 caratteri. Può essere `null`. Esempio: `ABC12345`. |
| `elementi` | `array<object>` | sì | L'array di elementi contabili del documento commerciale. |
| `elementi[].quantita` | `number` | sì | La quantità dell'elemento da rendere. Esempio: `1`. |
| `elementi[].id` | `integer` | no | L'id della riga originale da rendere (da Recupera dati reso DC). Obbligatorio se il documento è stato emesso dallo stesso PEM. Esempio: `5531`. |
| `elementi[].descrizione` | `string` | no | La descrizione dell'elemento. Questo campo è obbligatorio se il documento di cui si vuole fare il reso è stato emesso da un altro dispositivo. Al massimo 255 caratteri. Esempio: `Prodotto A`. |
| `elementi[].bene_servizio` | `string` | no | Indica se l'elemento è un bene (B). I servizi (S) non sono ammessi al reso. Questo campo è obbligatorio se il documento di cui si vuole fare il reso è stato emesso da un altro dispositivo. Valori: `B`. Esempio: `B`. |
| `elementi[].prezzo_unitario` | `number` | no | Il prezzo unitario dell'elemento da rendere. Questo campo è obbligatorio se il documento di cui si vuole fare il reso è stato emesso da un altro dispositivo. Esempio: `10.5`. |
| `elementi[].iva` | `string` | no | Il codice IVA applicato all'elemento da rendere. Questo campo è obbligatorio se il documento di cui si vuole fare il reso è stato emesso da un altro dispositivo. Valori: `4`, `5`, `10`, `22`, `N1`, `N2`, `N3`, `N4`, `N5`, `N6`, `VI`. |
| `elementi[].sconto` | `number` | no | Lo sconto applicato all'elemento da rendere. Può essere `null`. Esempio: `1`. |
| `elementi[].acconto_versato_beni_non_consegnati` | `boolean` | no | Indica se l'elemento è un acconto versato per beni non consegnati. Può essere `null`. Esempio: `false`. |
| `elementi[].omaggio` | `boolean` | no | Indica se l'elemento è un omaggio. Può essere `null`. Esempio: `false`. |
| `elementi[].ateco` | `string` | no | Il codice ATECO associato all'elemento. Al massimo 6 caratteri. Può essere `null`. Esempio: `123456`. |

Esempio:

```json
{
    "idempotency_key": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "progressivo": "0012-0007",
    "elementi": [
        {
            "id": 5531,
            "quantita": "1"
        }
    ]
}
```

**Risposte**

- `200` — Il PDF firmato (PAdES) del documento emesso.

| Header | Descrizione | Esempio |
| --- | --- | --- |
| `Content-Disposition` | Il nome del file contiene il progressivo del documento (NNNN-PPPP). | `inline; filename="DC_0012-0007.pdf"` |
| `Content-Location` | L'URL pubblico dello stesso PDF, senza token: con `?regalo=1` la copia di cortesia (solo DC di vendita). Il segmento prima di /pdf è l'hash del documento, URL-encoded. | `https://pem-staging.billyssw.it/pem/ASVS-241232/fiscale/consultazione/dc/YjQ0ZDJmMGE5YzFl...MmEzYjQ%3D/pdf` |

Corpo: `application/pdf`, binario.

- `401` — Token assente (error: unauthenticated), scaduto (error: token_expired) o non valido (error: token_invalid).

```json
{
    "success": false,
    "message": "Il token di accesso è scaduto.",
    "error": "token_expired"
}
```

- `403` — Scope non concesso al client partner o ruolo non ammesso per l'operazione.

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — 

- `422` — Dati non validi: errors riporta i messaggi per campo. Richiede Accept: application/json.

```json
{
    "success": false,
    "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
    "errors": {
        "pagamenti": [
            "La somma degli importi dei pagamenti è inferiore al totale da pagare"
        ]
    }
}
```

- `502` — Il PEL non ha aperto la nuova giornata (JN004): il documento non è emesso, ripetere con la stessa idempotency_key.

```json
{
    "error_code": "JN004",
    "message": "Errore apertura journal PEL: …"
}
```

<a id="post-api-pem-serial_number-fiscale-invio-chiusura"></a>

#### Chiusura giornaliera

`POST /api/pem/{serial_number}/fiscale/invio/chiusura`

Scope richiesto ai client partner: `fiscale:manage`.

Chiude la giornata fiscale: scrive il blocco di chiusura del Journal e accoda
l'invio firmato al PEL, che trasmette i corrispettivi all'AdE. La risposta
arriva subito: l'esito si segue con *Storico chiusure* (`inviato_il`) e con le
richieste di tipo CORRISPETTIVI. Se ci sono DC appena emessi ancora in invio al
PEL la risposta è 409 `JN005`: riprovare dopo qualche istante. Una giornata
aperta da 23 ore viene chiusa in automatico.

**Parametri di percorso**

| Nome | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `serial_number` | `string` | sì | Il seriale del PEM. Esempio: `ASVS-241232`. |

**Risposte**

- `200` — success

```json
{
    "success": true,
    "message": "Chiusura in elaborazione"
}
```

- `401` — Non autenticato

```json
{
    "message": "Non hai i permessi per accedere a questa risorsa.",
    "error": "unauthenticated"
}
```

- `403` — Non autorizzato

```json
{
    "success": false,
    "message": "Richiesta non autorizzata"
}
```

- `404` — Seriale inesistente o PEM non accessibile all'utente.

```json
{
    "success": false,
    "message": "Il PEM specificato non è stato trovato."
}
```

- `409` — 


---

<a id="errors"></a>

## Codici di errore

Fonte: https://app.billyssw.it/documentazione/errors · Markdown: https://app.billyssw.it/documentazione/errors.md

### Riferimento errori

I codici HTTP, il corpo JSON di ciascun tipo di errore e i codici di dominio restituiti dall'API fiscale.

Accept: application/json

Invia sempre l'header `Accept: application/json`.

#### Codici HTTP

| Codice | Quando si verifica |
| --- | --- |
| 200 | Richiesta completata. |
| 202 | Richiesta accettata ed elaborata in modo asincrono (es. segnalazione di inattività). |
| 400 | Richiesta non elaborabile per un motivo diverso dalla validazione. |
| 401 | Access token assente, scaduto o non valido, oppure utente non abilitato su Billy. |
| 403 | Token valido ma operazione non consentita: scope non concesso o ruolo dell'utente insufficiente. |
| 404 | Risorsa inesistente. Anche un PEM che esiste ma non appartiene all'esercente del token risponde 404. |
| 409 | Operazione incompatibile con lo stato attuale: PEM non nello stato richiesto, Journal o documento in uno stato che non la consente. |
| 422 | Payload non valido (validazione) o dato rifiutato dalle regole fiscali. |
| 429 | Troppe richieste dallo stesso indirizzo IP: sul link pubblico al PDF del documento oltre 60 al minuto, sulla richiesta di registrazione dell'esercente oltre 5 al minuto o 20 all'ora. |
| 500 | Errore interno, non dipendente dalla richiesta. |
| 502 | Un servizio a valle (Punto di Elaborazione o identity provider) non ha risposto come atteso. |

#### Autenticazione (401)

Il campo `error` dice cosa fare: rinnovare il token o ripetere l'autorizzazione dell'esercente.

401 · token scaduto

```json
{
  "success": false,
  "message": "Il token di accesso è scaduto.",
  "error": "token_expired"
}
```

| error | Significato | Azione |
| --- | --- | --- |
| token\_expired | Access token scaduto. | Rinnovarlo con il refresh token e ripetere la richiesta. |
| token\_invalid | Firma, emittente, audience o claim obbligatori non validi. | Ottenere un nuovo token con il flusso di autorizzazione. |
| unauthenticated | Header Authorization assente, oppure l'utente del token non è abilitato su Billy. Questo corpo non ha il campo success. | Verificare l'header; se persiste, contattare il supporto Sender. |

#### Accesso e stato del PEM (403, 404, 409)

| HTTP | Corpo | Quando |
| --- | --- | --- |
| 403 | {"success": false, "message": "Richiesta non autorizzata"} | Scope mancante, token di un utente che non è esercente né operatore, ruolo insufficiente per l'operazione. |
| 404 | {"success": false, "message": "Il PEM specificato non è stato trovato."} | Seriale inesistente o PEM di un altro esercente (per un operatore: un PEM diverso dal suo). |
| 404 | {"success": false, "message": "La risorsa richiesta non è stata trovata."} | Altra risorsa inesistente (richiesta, documento gestionale, rotta). |
| 409 | {"message": "Questa richiesta può essere elaborata solo da un PEM In servizio"} | Emissione, chiusura o cambio dell'opzione buoni pasto su un PEM non In servizio. |

#### Validazione (422)

`message` riporta il primo errore, `errors` tutti, per campo. Le regole con un testo dedicato rispondono in italiano, le altre con il messaggio predefinito in inglese: basa la logica del client sulle chiavi di `errors`, non sul testo.

422 · validazione

```json
{
  "success": false,
  "message": "La somma degli importi dei pagamenti è inferiore al totale da pagare",
  "errors": {
    "pagamenti": [
      "La somma degli importi dei pagamenti è inferiore al totale da pagare"
    ]
  }
}
```

#### Codici di dominio

Gli errori del dominio fiscale portano un codice stabile in `error_code`, senza il campo `success`. La tabella elenca quelli che un client può ricevere.

409 · codice di dominio

```json
{
  "error_code": "JN002",
  "message": "Apertura effettuata da più di 24 ore (ID: 42, Data: 2026-10-07 08:00:00)"
}
```

| Codice | HTTP | Significato e azione |
| --- | --- | --- |
| JN001 | 409 | Catena hash del Journal non valida. Contattare il supporto. |
| JN002 | 409 | Giornata aperta da più di 24 ore: eseguire la chiusura di cassa prima di emettere. |
| JN003 | 404 | Nessun Journal da chiudere. |
| JN004 | 502 | Il PEL non ha aperto la nuova giornata durante l'emissione. Il documento non è emesso: riprovare con la stessa idempotency\_key. |
| JN005 | 409 | Documenti commerciali ancora in attesa di invio al PEL: riprovare la chiusura dopo qualche istante. |
| JN006 | 409 | Chiusura già effettuata. |
| JN007 · JN008 | 409 | Stato del Journal incoerente con l'operazione. Contattare il supporto. |
| JN009 · JN010 | 422 | Journal non ancora chiuso, oppure chiuso ma non ancora inviato al PEL. |
| JN011 | 409 | PEM già dismesso. |
| JN013 | 409 | Numerazione dei DC esaurita: la giornata è stata chiusa d'ufficio. Ripetere l'emissione, che riparte da una nuova serie. |
| JN014 | 409 | Numerazione dei Journal esaurita: il PEM è dismesso. |
| JN015 | 409 | Cambio sede approvato: l'emissione è sospesa fino alla chiusura di cassa. |
| PL001 | come il PEL | Il Punto di Elaborazione ha rifiutato la richiesta; message ne riporta il motivo. |
| XM001 | 422 | Documento non conforme allo schema XML dell'AdE. |
| AE002 | 422 | L'AdE ha restituito errori sulla richiesta; message riporta i codici della Code List AdE. |
| DC001 | 404 | Documento commerciale, o riga del documento, non trovato. |
| DC002 | 409 | Il documento indicato non è una vendita: non si può annullare. |
| DC003 | 409 | Documento già annullato. |
| DC004 | 409 | Annullo o reso di un documento di un altro dispositivo datato dopo l'attivazione del PEM. |
| DC005 | 409 | Documento non valido per il reso. |
| DC006 | 409 | Quantità resa superiore a quella ancora rendibile. |
| DC007 | 409 | Le righe di servizio non sono ammesse al reso. |
| BP001 | 422 | L'opzione buoni pasto si cambia solo a cassa chiusa: eseguire la chiusura e riprovare. |
