stabiliteit: Bèta
Een factuur end-to-end renderen via REST
In het kort
Sectie met titel “In het kort”Breng één factuur van JSON naar een lokaal geverifieerde PDF via het
Representational State Transfer (REST)-oppervlak van NextPDF Connect, één
netwerkuitwisseling tegelijk. Dit recipe dient een renderopdracht in bij
POST /api/v1/jobs, herhaalt de indiening met dezelfde
Idempotency-Key om de duplicaat-veilige route te tonen, pollt
GET /api/v1/jobs/{id}, downloadt de PDF van
GET /api/v1/jobs/{id}/result, controleert de bytes met qpdf --check
en verwijdert de voltooide job.
Elke respons hieronder is een letterlijke vastlegging van een echte
Connect-deployment op Core-niveau (nextpdf/server onder RoadRunner,
gebonden aan http://localhost:8080). Het enige wat is vervangen, is de
API key, weergegeven als de omgevingsvariabele $NEXTPDF_CONNECT_TOKEN;
job-identifiers, verzoek-identifiers, tijdstempels, headers en
body-bytes zijn precies wat de server retourneerde. Headerwaarden zoals
Date en X-Request-Id verschillen uiteraard op jouw deployment.
Dit recipe verwerkt één enkel document zodat je elke uitwisseling
volledig kunt lezen. Voor veel documenten, begrensde gelijktijdigheid en
Retry-After-gestuurde poll-lussen, zie
Batchgewijs PDF’s genereren met voortgangstracking,
dat hetzelfde job-oppervlak gebruikt.
Installeren
Sectie met titel “Installeren”De serverkant is de standaard Connect-distributie:
composer require nextpdf/serverDe clientkant van dit recipe is curl plus qpdf, zodat je het naar
elke HTTP-client kunt overzetten. Exporteer eerst de waarden van jouw
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:Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”Het async-job-oppervlak scheidt indiening van ophalen: je dient een
renderverzoek in, ontvangt een job-record en haalt het resultaat op
wanneer de job completed bereikt. Het renderverzoek zelf is een
geordende operations-array — dezelfde operatietypes (set_font,
add_text, add_table, add_image, add_page) die de
Connect-toolaanroepen op elk transport ondersteunen — plus velden op
documentniveau (page_size, orientation, title, author).
Twee contractdetails bepalen het transcript dat je zo gaat lezen:
- Idempotente indiening. Een indiening met een
Idempotency-Keyretourneert de eerste keer201 Createden bij herhaling200 OKmet hetzelfde job-record, zodat een netwerk-retry nooit twee keer rendert. - Een indiening kan al terminaal zijn. De huidige release verwerkt de
job inline voordat hij de
POSTbeantwoordt, dus de indieningsrespons kan alstatus: "completed"bevatten — zoals hieronder het geval is. Het poll-until-terminal-contract is de stabiele API-vorm: schrijf de poll-lus en accepteer een terminale status bij elke poging, inclusief de eerste.
Je kunt bevestigen wat jouw deployment blootstelt voordat je iets
indient: GET /api/v1/capabilities retourneert de operatiecatalogus die
het niveau van jouw API key kan bereiken. Op de hier vastgelegde
deployment op Core-niveau vermeldde die alleen de Core-operaties; de
gezaghebbende catalogus is altijd de eigen respons van de draaiende server,
niet deze pagina.
API-oppervlak
Sectie met titel “API-oppervlak”| Uitwisseling | Methode en pad | Vastgelegde status |
|---|---|---|
| Renderopdracht indienen | POST /api/v1/jobs | 201 Created |
| Dezelfde indiening herhalen | POST /api/v1/jobs (zelfde Idempotency-Key) | 200 OK |
| Job-record pollen | GET /api/v1/jobs/{id} | 200 OK |
| De PDF downloaden | GET /api/v1/jobs/{id}/result | 200 OK, application/pdf |
| De voltooide job verwijderen | DELETE /api/v1/jobs/{id} | 204 No Content |
Authenticatie is een bearer-token bij elk /api/v1/*-verzoek:
Authorization: Bearer npk_live_{kid}_{secret}. Succesvolle
JSON-responses delen de envelop { "data": ..., "meta": ... }; de velden
waarmee je werkt bevinden zich onder data.
Het factuurverzoek
Sectie met titel “Het factuurverzoek”Schrijf het renderverzoek naar invoice.json. Het is een eenvoudige,
deterministische operatielijst — een vetgedrukte kopregel, een
uitgifteregel en een tabel met regelitems:
{ "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>" } ]}De factuurvelden hier zijn voorbeelddata. De gezaghebbende argumentvorm
voor elke operatie is wat jouw deployment rapporteert — via MCP
retourneert tools/list het volledige input-schema voor elk
operatietype dat dit verzoek gebruikt.
End-to-end-transcript
Sectie met titel “End-to-end-transcript”1. Renderopdracht indienen
Sectie met titel “1. Renderopdracht indienen”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.jsonDe server antwoordt met 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" }}De job is in deze vastlegging al terminaal — status is "completed" en
result_url is aanwezig — omdat de huidige release inline rendert
voordat hij antwoordt. Vertrouw daar niet op: behandel de
indieningsrespons als het eerste poll-resultaat en vertak op basis van
data.status zoals bij elke andere poll.
2. De indiening herhalen (idempotente route)
Sectie met titel “2. De indiening herhalen (idempotente route)”Herhaal exact hetzelfde commando — zelfde Idempotency-Key, zelfde body:
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.jsonDe server retourneert 200 OK — niet 201 — met hetzelfde job_id, en
er vindt geen tweede render plaats (vergelijk meta.duration_ms met de
eerste respons):
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. Het job-record pollen
Sectie met titel “3. Het job-record pollen”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" }}Deze poll toont een terminaal record, dus er is geen Retry-After-header
en geen poll_url-veld. Zolang een job nog pending of running is,
stelt de server bij elke poll Retry-After in (een interval van 2
seconden) — respecteer die in plaats van in een strakke lus te pollen.
4. De PDF downloaden
Sectie met titel “4. De PDF downloaden”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 GMTDe body is de PDF-binary — 3.663 bytes in deze vastlegging,
overeenkomend met de Content-Length-header — en is hier weggelaten.
Hij wordt weggeschreven naar invoice-inv-2026-0042.pdf.
5. De gedownloade bytes lokaal verifiëren
Sectie met titel “5. De gedownloade bytes lokaal verifiëren”Een 200 met Content-Type: application/pdf is op zichzelf geen bewijs
dat de body een goedgevormde PDF is. Voer een structurele controle uit
met qpdf:
qpdf --check invoice-inv-2026-0042.pdfVastgelegde uitvoer voor het hierboven gedownloade bestand:
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 detectqpdf’s eigen formulering is de eerlijke grens: dit is een syntax- en streamcontrole, geen conformiteitsbepaling ten opzichte van enige standaard.
6. De voltooide job verwijderen
Sectie met titel “6. De voltooide job verwijderen”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 GMTNa het verwijderen zijn het job-record en het opgeslagen resultaat weg;
een volgende GET op de job retourneert 404.
Randgevallen & valkuilen
Sectie met titel “Randgevallen & valkuilen”- Vertak op basis van
data.status, niet op de HTTP-status alleen. Indienen, herhalen en pollen retourneren allemaal2xxmet een job-record; de levenscyclusstatus zit indata.status(pending,running,completed,failed,cancelled). - Een herhaalde key met een andere body levert een
409 Conflictop. De idempotente200-herhaling gebeurt alleen wanneer de body overeenkomt met de oorspronkelijke indiening. Hergebruik een key nooit voor andere inhoud. /resultvóór voltooiing levert een409op. Download pas nadat een pollcompletedtoont. De409is een normale respons om te inspecteren, geen transportstoring — dezelfde scheiding tussen transport en status die elk Connect-recipe volgt (zie Recipe-conventies).- Jobs zijn eigenaargebonden. Een job die onder één API key is
ingediend, is onzichtbaar voor een andere key: een cross-owner-
GETretourneert404, niet403. Poll met de credential waarmee je hebt ingediend. progresskan ontbreken. Het vastgelegde record bevat geenprogress-veld omdat de job al terminaal was. Wanneer de server de voortgang van een niet-terminale job bijhoudt, isdata.progresseen geheel getal van 0 tot 100; behandel een ontbrekend veld als onbekend, niet als nul.- Een
failedjob bevatdata.error. Registreer die; dien niet blindelings opnieuw in.
Prestaties
Sectie met titel “Prestaties”Eén renderopdracht kost één indiening, hooguit een handvol polls en één
download. De vastgelegde meta.duration_ms-waarden vertellen het
verhaal: 63,31 ms om de factuur te renderen bij indiening, 1,03 ms voor
de idempotente herhaling die geen werk deed, en status-leesbewerkingen
onder de milliseconde. Poll op de Retry-After-cadans van de server in
plaats van in een strakke lus; de status-leesbewerking is goedkoop maar
niet gratis, en de rate limiter budgetteert die (zie
X-Ratelimit-Remaining aftellen in de vastgelegde headers). Begrens voor
batches de lopende jobs in plaats van alles tegelijk in te dienen — het
batch-recipe
implementeert die lus.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”- Houd het bearer-token uitsluitend in de
Authorization-header. Nooit in een query string, een logregel of een gecommit bestand. Het bovenstaande transcript vervangt het door een omgevingsvariabele om precies die reden. - Valideer gedownloade bytes voordat je ze vertrouwt. Stap 5 hoort
bij de flow, geen optionele extra: controleer of de respons een
PDF is (minimaal de
%PDF-header,qpdf --checkvoor de structuur) voordat je die archiveert of doorstuurt. - Verwijder voltooide jobs die je niet meer nodig hebt. Stap 6 verwijdert het opgeslagen resultaat van de server; een voltooide job blijft anders downloadbaar totdat de job garbage collection van de server hem opruimt.
- Gebruik een key met minimale rechten. Deze flow heeft een render-key op Core-niveau nodig en niets meer.
Conformiteit
Sectie met titel “Conformiteit”Dit recipe doet geen normatieve standaardenclaim. Het spreekt de Connect
async-job REST-endpoints aan en leest de job-recordvelden die de server
definieert. De stap qpdf --check bevestigt alleen de structurele
integriteit — “the file may still contain errors that qpdf cannot
detect” is qpdf’s eigen voorbehoud, hierboven letterlijk geciteerd. Het
bepalen van conformiteit aan een standaard (PDF/A-4, PDF/UA) is de taak
van een onafhankelijke validator, en een ander oppervlak — zie
Een controle op een benoemde standaard uitvoeren
voor die grens.
Zie ook
Sectie met titel “Zie ook”- Batchgewijs PDF’s genereren met voortgangstracking — hetzelfde job-oppervlak aangedreven als een batch met begrensde gelijktijdigheid.
- Genereer je eerste PDF — de kleinste Connect-render.
- Een agent-documentsessie aansturen via MCP — dezelfde engine, tool voor tool, via het MCP stdio-transport.
- Connect-recipe-conventies — het transport-, niveau- en conformiteitscontract dat elk Connect-recipe volgt.
- Exception-bewuste foutafhandeling via Connect — hoe je transportstoringen scheidt van niet-succesvolle statussen.