stabilità: Beta
Eseguire il rendering di una fattura end-to-end tramite REST
In sintesi
Sezione intitolata “In sintesi”Portare una fattura da JSON a un PDF verificato localmente attraverso la
superficie Representational State Transfer (REST) di NextPDF Connect, uno
scambio sul canale alla volta. Questa ricetta invia un job di rendering a
POST /api/v1/jobs, ripete l’invio con la stessa Idempotency-Key per
mostrare il percorso a prova di duplicati, interroga GET /api/v1/jobs/{id},
scarica il PDF da GET /api/v1/jobs/{id}/result, controlla i byte con
qpdf --check ed elimina il job completato.
Ogni risposta qui sotto è una cattura letterale da un deployment Connect
di tier core reale (nextpdf/server sotto RoadRunner, associato a
http://localhost:8080). L’unica sostituzione è la chiave API, mostrata
come la variabile d’ambiente $NEXTPDF_CONNECT_TOKEN; identificatori di
job, identificatori di richiesta, timestamp, header e byte del corpo sono
esattamente ciò che il server ha restituito. Valori di header come Date
e X-Request-Id differiranno, ovviamente, sul proprio deployment.
Questa ricetta gestisce un singolo documento, così da poter leggere ogni
scambio per intero. Per molti documenti, concorrenza limitata e cicli di
polling guidati da Retry-After, vedere
Generare PDF in batch con monitoraggio dell’avanzamento,
che usa la stessa superficie di job.
Installazione
Sezione intitolata “Installazione”Il lato server è la distribuzione Connect standard:
composer require nextpdf/serverIl lato client di questa ricetta è curl più qpdf, così da poterla
portare su qualsiasi client HTTP. Esportare prima i valori del proprio
deployment:
export NEXTPDF_CONNECT_URL="http://localhost:8080"export NEXTPDF_CONNECT_TOKEN="npk_live_{kid}_{secret}" # your real key# Key provisioning and server startup live in the quickstart:Panoramica concettuale
Sezione intitolata “Panoramica concettuale”La superficie dei job asincroni separa l’invio dal recupero: si invia una
richiesta di rendering, si riceve un record del job e si recupera il
risultato quando il job raggiunge lo stato completed. La richiesta di
rendering stessa è un array operations ordinato — gli stessi tipi di
operazione (set_font, add_text, add_table, add_image, add_page)
che sostengono le chiamate agli strumenti Connect su ogni trasporto —
insieme a campi a livello di documento (page_size, orientation,
title, author).
Due dettagli del contratto danno forma alla trascrizione che si sta per leggere:
- Invio idempotente. Un invio con chiave
Idempotency-Keyrestituisce201 Createdla prima volta e200 OKcon lo stesso record del job quando viene ripetuto, così un retry di rete non esegue mai il rendering due volte. - L’invio può essere già terminale. La release corrente elabora il job
in modo sincrono prima di rispondere al
POST, quindi la risposta all’invio può già riportarestatus: "completed"— come avviene qui sotto. Il contratto poll-until-terminal è la forma stabile dell’API: scrivere il ciclo di polling e accettare uno stato terminale a qualsiasi tentativo, incluso il primo.
Si può confermare ciò che il proprio deployment espone prima di inviare
qualsiasi cosa: GET /api/v1/capabilities restituisce il catalogo delle
operazioni raggiungibili dal tier della propria chiave API. Il deployment
di tier core catturato qui elencava solo le operazioni core; il catalogo
di riferimento è sempre la risposta del server in esecuzione, non questa
pagina.
Superficie API
Sezione intitolata “Superficie API”| Scambio | Metodo e percorso | Stato catturato |
|---|---|---|
| Inviare il job di rendering | POST /api/v1/jobs | 201 Created |
| Ripetere lo stesso invio | POST /api/v1/jobs (stessa Idempotency-Key) | 200 OK |
| Interrogare il record del job | GET /api/v1/jobs/{id} | 200 OK |
| Scaricare il PDF | GET /api/v1/jobs/{id}/result | 200 OK, application/pdf |
| Eliminare il job completato | DELETE /api/v1/jobs/{id} | 204 No Content |
L’autenticazione è un bearer token su ogni richiesta /api/v1/*:
Authorization: Bearer npk_live_{kid}_{secret}. Le risposte JSON di
successo condividono l’envelope { "data": ..., "meta": ... }; i campi su
cui si agisce risiedono sotto data.
La richiesta della fattura
Sezione intitolata “La richiesta della fattura”Scrivere la richiesta di rendering in invoice.json. È un semplice elenco
deterministico di operazioni — una riga di intestazione in grassetto, una
riga di emissione e una tabella di voci:
{ "page_size": "A4", "orientation": "portrait", "title": "Invoice INV-2026-0042", "author": "Aurora Fixtures Ltd.", "operations": [ { "type": "set_font", "family": "helvetica", "style": "B", "size": 16 }, { "type": "add_text", "text": "Invoice INV-2026-0042" }, { "type": "set_font", "family": "helvetica", "style": "", "size": 10 }, { "type": "add_text", "text": "Issued 2026-07-08 by Aurora Fixtures Ltd. Payment is due within 30 days.", "width": 0, "line_height": 5 }, { "type": "add_table", "html": "<table><tr><th>Item</th><th>Qty</th><th>Unit price</th><th>Amount</th></tr><tr><td>Cable tray, 300 mm</td><td>12</td><td>18.40</td><td>220.80</td></tr><tr><td>Mounting kit</td><td>4</td><td>9.75</td><td>39.00</td></tr><tr><td>Site delivery</td><td>1</td><td>25.00</td><td>25.00</td></tr><tr><td>Total (EUR)</td><td></td><td></td><td>284.80</td></tr></table>" } ]}I campi della fattura qui sono dati di esempio. La forma autorevole degli
argomenti per ciascuna operazione è quella riportata dal proprio
deployment — su MCP, tools/list restituisce lo schema di input completo
per ogni tipo di operazione usato da questa richiesta.
Trascrizione end-to-end
Sezione intitolata “Trascrizione end-to-end”1. Inviare il job di rendering
Sezione intitolata “1. Inviare il job di rendering”curl -sS -i -X POST "$NEXTPDF_CONNECT_URL/api/v1/jobs" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: inv-2026-0042" \ --data-binary @invoice.jsonIl server risponde 201 Created:
HTTP/1.1 201 CreatedCache-Control: no-storeContent-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'Content-Type: application/json; charset=utf-8Referrer-Policy: no-referrerVary: Accept-EncodingX-Content-Type-Options: nosniffX-Frame-Options: DENYX-Powered-By: NextPDF ConnectX-Ratelimit-Remaining: 99X-Request-Id: 019f3fd6-c6ed-727c-958e-2fa4370ba97eDate: Wed, 08 Jul 2026 03:47:48 GMTTransfer-Encoding: chunked{ "data": { "job_id": "job_9bd0808960f10eb568484acc", "status": "completed", "created_at": "2026-07-08T03:47:48+00:00", "started_at": "2026-07-08T03:47:48+00:00", "completed_at": "2026-07-08T03:47:48+00:00", "result_url": "/api/v1/jobs/job_9bd0808960f10eb568484acc/result" }, "meta": { "request_id": "019f3fd6-c6ed-727c-958e-2fa4370ba97e", "timestamp": "2026-07-08T03:47:48+00:00", "duration_ms": 63.31, "api_version": "v1" }}Il job è già terminale in questa cattura — status è "completed" e
result_url è presente — perché la release corrente esegue il rendering
in modo sincrono prima di rispondere. Non farci affidamento: trattare la risposta
all’invio come il primo risultato di polling e diramare in base a data.status
come per qualsiasi altro polling.
2. Ripetere l’invio (percorso idempotente)
Sezione intitolata “2. Ripetere l’invio (percorso idempotente)”Ripetere esattamente lo stesso comando — stessa Idempotency-Key, stesso
corpo:
curl -sS -i -X POST "$NEXTPDF_CONNECT_URL/api/v1/jobs" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: inv-2026-0042" \ --data-binary @invoice.jsonIl server restituisce 200 OK — non 201 — con lo stesso job_id, e non
avviene un secondo rendering (confrontare meta.duration_ms con la prima
risposta):
HTTP/1.1 200 OKCache-Control: no-storeContent-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'Content-Type: application/json; charset=utf-8Referrer-Policy: no-referrerVary: Accept-EncodingX-Content-Type-Options: nosniffX-Frame-Options: DENYX-Powered-By: NextPDF ConnectX-Ratelimit-Remaining: 99X-Request-Id: 019f3fd6-c756-7226-bc85-b255d75bd449Date: Wed, 08 Jul 2026 03:47:48 GMTTransfer-Encoding: chunked{ "data": { "job_id": "job_9bd0808960f10eb568484acc", "status": "completed", "created_at": "2026-07-08T03:47:48+00:00", "started_at": "2026-07-08T03:47:48+00:00", "completed_at": "2026-07-08T03:47:48+00:00", "result_url": "/api/v1/jobs/job_9bd0808960f10eb568484acc/result" }, "meta": { "request_id": "019f3fd6-c756-7226-bc85-b255d75bd449", "timestamp": "2026-07-08T03:47:48+00:00", "duration_ms": 1.03, "api_version": "v1" }}3. Interrogare il record del job
Sezione intitolata “3. Interrogare il record del job”curl -sS -i "$NEXTPDF_CONNECT_URL/api/v1/jobs/job_9bd0808960f10eb568484acc" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN"HTTP/1.1 200 OKCache-Control: no-storeContent-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'Content-Type: application/json; charset=utf-8Referrer-Policy: no-referrerVary: Accept-EncodingX-Content-Type-Options: nosniffX-Frame-Options: DENYX-Powered-By: NextPDF ConnectX-Ratelimit-Remaining: 98X-Request-Id: 019f3fd7-0038-7131-b71e-4c69e637233bDate: Wed, 08 Jul 2026 03:48:02 GMTTransfer-Encoding: chunked{ "data": { "job_id": "job_9bd0808960f10eb568484acc", "status": "completed", "created_at": "2026-07-08T03:47:48+00:00", "started_at": "2026-07-08T03:47:48+00:00", "completed_at": "2026-07-08T03:47:48+00:00", "result_url": "/api/v1/jobs/job_9bd0808960f10eb568484acc/result" }, "meta": { "request_id": "019f3fd7-0038-7131-b71e-4c69e637233b", "timestamp": "2026-07-08T03:48:02+00:00", "duration_ms": 0.24, "api_version": "v1" }}Questo polling mostra un record terminale, quindi non c’è alcun header
Retry-After né il campo poll_url. Mentre un job è ancora pending o
running, il server imposta Retry-After (un intervallo di 2 secondi) su
ogni polling — rispettarlo invece di eseguire il polling in un ciclo
serrato.
4. Scaricare il PDF
Sezione intitolata “4. Scaricare il PDF”curl -sS -D result-headers.txt \ "$NEXTPDF_CONNECT_URL/api/v1/jobs/job_9bd0808960f10eb568484acc/result" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN" \ -o invoice-inv-2026-0042.pdfHTTP/1.1 200 OKCache-Control: no-storeContent-Disposition: attachment; filename="job-job_9bd0808960f10eb568484acc.pdf"Content-Length: 3663Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'Content-Type: application/pdfReferrer-Policy: no-referrerVary: Accept-EncodingX-Content-Type-Options: nosniffX-Frame-Options: DENYX-Powered-By: NextPDF ConnectX-Ratelimit-Remaining: 98X-Request-Id: 019f3fd7-0056-711e-acad-9f7ac5f21ba7Date: Wed, 08 Jul 2026 03:48:02 GMTIl corpo è il binario del PDF — 3.663 byte in questa cattura,
corrispondenti all’header Content-Length — ed è omesso qui. Viene
scritto in invoice-inv-2026-0042.pdf.
5. Verificare localmente i byte scaricati
Sezione intitolata “5. Verificare localmente i byte scaricati”Un 200 con Content-Type: application/pdf non è, di per sé, la prova
che il corpo sia un PDF ben formato. Eseguire un controllo strutturale con
qpdf:
qpdf --check invoice-inv-2026-0042.pdfOutput catturato per il file scaricato sopra:
checking invoice-inv-2026-0042.pdfPDF Version: 2.0File is not encryptedFile is not linearizedNo syntax or stream encoding errors found; the file may still containerrors that qpdf cannot detectLe parole stesse di qpdf sono il confine onesto: questo è un controllo di sintassi e di stream, non una determinazione di conformità rispetto ad alcuno standard.
6. Eliminare il job completato
Sezione intitolata “6. Eliminare il job completato”curl -sS -i -X DELETE "$NEXTPDF_CONNECT_URL/api/v1/jobs/job_9bd0808960f10eb568484acc" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN"HTTP/1.1 204 No ContentCache-Control: no-storeContent-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'Content-Type: application/json; charset=utf-8Referrer-Policy: no-referrerVary: Accept-EncodingX-Content-Type-Options: nosniffX-Frame-Options: DENYX-Powered-By: NextPDF ConnectX-Ratelimit-Remaining: 97X-Request-Id: 019f3fd7-0084-722f-bf54-3dabead8aeeaDate: Wed, 08 Jul 2026 03:48:02 GMTDopo l’eliminazione, il record del job e il suo risultato memorizzato non
esistono più; un GET successivo sul job restituisce 404.
Casi limite e insidie
Sezione intitolata “Casi limite e insidie”- Diramare in base a
data.status, non al solo stato HTTP. Invio, ripetizione e polling restituiscono tutti2xxcon un record del job; lo stato del ciclo di vita risiede indata.status(pending,running,completed,failed,cancelled). - Una chiave ripetuta con un corpo diverso è un
409 Conflict. La ripetizione idempotente200avviene solo quando il corpo corrisponde all’invio originale. Non riutilizzare mai una chiave per contenuti diversi. /resultprima del completamento è un409. Scaricare solo dopo che un polling mostracompleted. Il409è una risposta normale da esaminare, non un guasto del trasporto — la stessa separazione tra trasporto e stato che ogni ricetta Connect segue (vedere Convenzioni delle ricette).- I job sono associati al proprietario. Un job inviato con una chiave
API è invisibile a un’altra chiave: un
GETcross-owner restituisce404, non403. Eseguire il polling con la credenziale con cui si è effettuato l’invio. progresspuò essere assente. Il record catturato non contiene alcun campoprogressperché il job era già terminale. Quando il server traccia l’avanzamento per un job non terminale,data.progressè un intero da 0 a 100; trattare un campo mancante come sconosciuto, non come zero.- Un job
failedcontienedata.error. Registrarlo; non reinviare alla cieca.
Prestazioni
Sezione intitolata “Prestazioni”Un job di rendering costa un invio, al massimo una manciata di polling e un
download. I valori meta.duration_ms catturati parlano chiaro: 63,31
ms per eseguire il rendering della fattura all’invio, 1,03 ms per la
ripetizione idempotente che non ha svolto alcun lavoro e letture di stato
sotto il millisecondo. Eseguire il polling alla cadenza Retry-After del
server invece che in un ciclo serrato; la lettura di stato è economica ma
non gratuita, e il rate limiter la conteggia nel budget (osservare
X-Ratelimit-Remaining diminuire negli header catturati). Per i batch,
limitare i job in corso invece di inviarli tutti in una volta — la
ricetta batch
implementa quel ciclo.
Note di sicurezza
Sezione intitolata “Note di sicurezza”- Mantenere il bearer token solo nell’header
Authorization. Mai in una query string, in una riga di log o in un file committato. La trascrizione sopra sostituisce una variabile d’ambiente esattamente per questo motivo. - Validare i byte scaricati prima di considerarli attendibili. Il
passaggio 5 fa parte del flusso, non è un extra facoltativo: verificare
che la risposta sia un PDF (almeno l’header
%PDF,qpdf --checkper la struttura) prima di archiviarlo o inoltrarlo. - Eliminare i job completati che non servono più. Il passaggio 6 rimuove dal server il risultato memorizzato; altrimenti un job completato rimane scaricabile fino a quando la garbage collection dei job del server lo rimuove.
- Usare una chiave con privilegi minimi. Questo flusso richiede una chiave di rendering di tier core e nient’altro.
Conformità
Sezione intitolata “Conformità”Questa ricetta non avanza alcuna affermazione normativa sugli standard.
Esercita gli endpoint REST dei job asincroni di Connect e legge i campi del
record del job che il server definisce. Il passaggio qpdf --check conferma
solo l’integrità strutturale — “the file may still contain errors that qpdf
cannot detect” è la stessa avvertenza di qpdf, citata letteralmente sopra.
Determinare la conformità a uno standard (PDF/A-4, PDF/UA) è compito di un
validatore indipendente, ed è una superficie diversa — vedere
Eseguire un controllo di standard nominato
per quel confine.
Vedi anche
Sezione intitolata “Vedi anche”- Generare PDF in batch con monitoraggio dell’avanzamento — la stessa superficie di job gestita come batch a concorrenza limitata.
- Generare il primo PDF — il più piccolo rendering Connect.
- Guidare una sessione di documenti con un agente su MCP — lo stesso engine, strumento per strumento, sul trasporto MCP stdio.
- Convenzioni delle ricette Connect — il contratto di trasporto, tier e conformità che ogni ricetta Connect segue.
- Gestione degli errori consapevole delle eccezioni su Connect — come separare i guasti del trasporto dagli stati di non successo.