BillySSW · Documentazione API

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
Authorization: Bearer <access_token>
Accept: application/json

Passo 2 — Trova il PEM

GET /api/pem
curl https://pem-staging.billyssw.it/api/pem \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json"
200 OK
{
  "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
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
$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
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
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. L'XML firmato è già nel Journal del PEM e parte verso il PEL subito dopo la risposta.