Invia il tuo primo documento
Prerequisiti
- Richiesta di affiliazione approvata.
- Il
client_iddel 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.
Authorization: Bearer <access_token>
Accept: application/jsonPasso 2 — Trova il PEM
curl https://pem-staging.billyssw.it/api/pem \
-H "Authorization: Bearer <access_token>" \
-H "Accept: application/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.
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$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
}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:
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 diContent-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. L'XML firmato è già nel Journal del PEM e parte verso il PEL subito dopo la risposta.