Download di file
Alcuni endpoint delle API non restituiscono un oggetto JSON con i campi error, message, data e meta, ma restituiscono direttamente un file da scaricare.
È il caso dei download di fatture e note di credito in formato PDF o XML.
Come riconoscere un endpoint di download
Negli endpoint di download, una risposta 200 OK restituisce direttamente il contenuto binario del file.
In pratica:
- se la richiesta va a buon fine, il body della risposta contiene il file;
- se invece si verifica un errore, la risposta torna nel formato JSON standard delle API, descritto in Convenzioni comuni delle risposte.
Questo significa che il tuo client deve gestire in modo diverso i casi di successo e di errore:
- successo: salva il file;
- errore: leggi il JSON di risposta e gestiscilo come una normale risposta API.
Endpoint disponibili e permessi
| Endpoint | Contenuto |
|---|---|
GET /api/v3/invoices/{invoice_id}/pdf | PDF della fattura |
GET /api/v3/invoices/{invoice_id}/xml | XML della fattura, oppure il file firmato .p7m |
GET /api/v3/credit-notes/{credit_note_id}/pdf | PDF della nota di credito |
GET /api/v3/credit-notes/{credit_note_id}/xml | XML della nota di credito, oppure il file firmato .p7m |
Tutti questi endpoint richiedono lo scope billing:read. Gli ID di fatture e note di credito si ottengono dagli elenchi GET /api/v3/invoices e GET /api/v3/credit-notes.
Fatture e note di credito non sono disponibili per i sub-account, come nel pannello di controllo: per loro questi endpoint rispondono 403 Forbidden.
Header utili nella risposta
Gli endpoint di download restituiscono alcuni header molto utili:
Content-Type: indica il tipo di file restituito;Content-Disposition: indica che il contenuto è un allegato e include il nome file;Content-Length: indica la dimensione del file in byte;Last-Modified: indica la data di ultima modifica del file;Cache-Control: contiene eventuali indicazioni di cache;X-Content-Type-Options: header di protezione del contenuto.
Quando possibile, è buona pratica leggere Content-Disposition per determinare il nome del file da salvare.
Content-Type da aspettarsi
Il tipo di file restituito dipende dall'endpoint:
- per i PDF, il content type è
application/pdf; - per i file XML, il content type è
text/xml; - per i documenti firmati può essere restituito
application/x-pkcs7-mime, cioè il file firmato in formato.p7m.
Download PDF
Per i documenti PDF il comportamento è diretto: l'endpoint restituisce il file PDF.
Esempi:
GET /api/v3/invoices/{invoice_id}/pdfGET /api/v3/credit-notes/{credit_note_id}/pdf
In questi casi, se la richiesta va a buon fine, puoi salvare direttamente il body della risposta come file PDF.
Download XML e file firmati
Per i documenti XML esistono endpoint dedicati:
GET /api/v3/invoices/{invoice_id}/xmlGET /api/v3/credit-notes/{credit_note_id}/xml
Questi endpoint possono restituire:
- il file XML;
- oppure, se il documento è firmato, il file originale in formato
.p7m.
Per questi endpoint è disponibile il parametro query string prefer_xml.
Se imposti prefer_xml=true, forzi il download della versione XML anche quando il documento firmato originale è disponibile.
Esempio
Questo esempio scarica il PDF di una fattura e lo salva su disco.
curl --request GET \
--url 'https://api.shellrent.com/api/v3/invoices/123456/pdf' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--output fattura-123456.pdf
In questo caso:
- se la richiesta ha esito positivo, il file viene salvato come
fattura-123456.pdf; - se la richiesta fallisce, la risposta non sarà un PDF ma un JSON di errore. Con
curl --failpuoi fare in modo che il comando termini con errore invece di salvare il JSON nel file.
Esempio in PHP
Questo esempio mostra come scaricare un file PDF con Guzzle e salvarlo localmente.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://api.shellrent.com',
'http_errors' => false,
]);
$response = $client->request('GET', '/api/v3/invoices/123456/pdf', [
'headers' => [
'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
'Accept' => 'application/pdf',
],
]);
$statusCode = $response->getStatusCode();
$contentType = $response->getHeaderLine('Content-Type');
if ($statusCode === 200 && str_contains($contentType, 'application/pdf')) {
file_put_contents(__DIR__ . '/fattura-123456.pdf', $response->getBody()->getContents());
echo "File salvato correttamente.\n";
exit;
}
$data = json_decode((string) $response->getBody(), true);
echo "Errore API ({$statusCode}): " . ($data['message'] ?? 'risposta non valida') . "\n";
Esempio con prefer_xml
Questo esempio richiede il download XML di una fattura, forzando la versione XML anche se il documento è disponibile in formato firmato.
curl --request GET \
--url 'https://api.shellrent.com/api/v3/invoices/123456/xml?prefer_xml=true' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--output fattura-123456.xml
Se non imposti prefer_xml=true, il server potrebbe restituire il file firmato originale invece del file XML: in quel caso il Content-Type è application/x-pkcs7-mime e conviene salvare il file con estensione .p7m.
Esempio con gli SDK
Con l'SDK PHP, i metodi di download restituiscono un \SplFileObject che punta a un file temporaneo: spostalo o eliminalo quando hai finito.
$file = $client->billing()->downloadInvoicePdf(123456);
rename($file->getPathname(), __DIR__ . '/fattura-123456.pdf');
Il nome del file proposto dal server si legge dall'header Content-Disposition, restituito dalla variante downloadInvoicePdfWithHttpInfo().
Con l'SDK Python, i metodi di download restituiscono un File con il contenuto in payload:
from pathlib import Path
from shellrent_sdk import unwrap
from shellrent_sdk.client.api.billing import download_invoice_pdf
response = download_invoice_pdf.sync_detailed(123456, client=client)
Path("fattura-123456.pdf").write_bytes(unwrap(response).payload.read())
In entrambi gli SDK, una risposta di errore diventa un'eccezione, quindi non rischi di salvare un JSON di errore al posto del file.
Il comando shellrent installato con l'SDK Python scrive i file così come arrivano:
shellrent api GET /api/v3/invoices/123456/pdf > fattura-123456.pdf
Come gestire correttamente la risposta
Quando lavori con endpoint di download, ti conviene seguire sempre questo flusso:
- invia la richiesta autenticata;
- controlla lo status code;
- verifica il
Content-Type; - se la risposta è un file, salva il body;
- se la risposta non è un file, interpreta il body come JSON di errore.
Questo approccio evita errori comuni, ad esempio tentare di decodificare come JSON una risposta che in realtà contiene un PDF.
Buone pratiche
Se disponibile, usa Content-Disposition per recuperare il nome file proposto dal server invece di costruirlo manualmente.
Quando scarichi XML da endpoint che possono restituire anche file firmati, controlla sempre il Content-Type ricevuto prima di decidere l'estensione del file.
Una risposta 200 OK su un endpoint di download non contiene il wrapper JSON standard: contiene direttamente il file.
Gli errori continuano invece a usare il formato JSON standard delle API, quindi il tuo codice deve saper distinguere chiaramente i due casi.