stabilitas: Beta
Render faktur secara menyeluruh melalui REST
Sekilas pandang
Bagian berjudul “Sekilas pandang”Ubah satu faktur dari JSON menjadi PDF yang terverifikasi secara lokal
melalui permukaan Representational State Transfer (REST) NextPDF Connect,
satu pertukaran wire pada satu waktu. Resep ini mengirim job render ke
POST /api/v1/jobs, mengulang pengiriman dengan Idempotency-Key yang
sama untuk menunjukkan jalur yang aman terhadap duplikat, melakukan polling
GET /api/v1/jobs/{id}, mengunduh PDF dari
GET /api/v1/jobs/{id}/result, memeriksa byte dengan qpdf --check, dan
menghapus job yang telah selesai.
Setiap respons di bawah adalah tangkapan verbatim dari deployment Connect
core-tier yang nyata (nextpdf/server di bawah RoadRunner, terikat ke
http://localhost:8080). Satu-satunya penggantian adalah kunci API, yang
ditampilkan sebagai variabel lingkungan $NEXTPDF_CONNECT_TOKEN;
identifier job, identifier permintaan, stempel waktu, header, dan byte
body persis seperti yang dikembalikan server. Nilai header seperti Date
dan X-Request-Id tentu akan berbeda pada deployment Anda.
Resep ini memproses satu dokumen agar Anda dapat membaca setiap
pertukaran secara lengkap. Untuk banyak dokumen, konkurensi terbatas, dan
loop polling yang digerakkan oleh Retry-After, lihat
Buat PDF secara batch dengan pelacakan progres,
yang menggunakan permukaan job yang sama.
Pemasangan
Bagian berjudul “Pemasangan”Sisi server adalah distribusi Connect standar:
composer require nextpdf/serverSisi klien dari resep ini adalah curl ditambah qpdf, sehingga Anda
dapat memindahkannya ke klien HTTP mana pun. Ekspor nilai deployment Anda
terlebih dahulu:
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:Ikhtisar konseptual
Bagian berjudul “Ikhtisar konseptual”Permukaan async-job memisahkan pengiriman dari pengambilan: Anda mengirim
permintaan render, menerima rekaman job, dan mengambil hasilnya ketika job
mencapai completed. Permintaan render itu sendiri adalah array
operations yang berurutan — tipe operasi yang sama (set_font,
add_text, add_table, add_image, add_page) yang mendukung
pemanggilan tool Connect pada setiap transport — ditambah field
tingkat-dokumen (page_size, orientation, title, author).
Dua detail kontrak membentuk transkrip yang akan Anda baca:
- Pengiriman idempoten. Pengiriman yang diberi kunci
Idempotency-Keymengembalikan201 Createdpada kali pertama dan200 OKdengan rekaman job yang sama ketika diulang, sehingga percobaan ulang jaringan tidak pernah merender dua kali. - Pengiriman mungkin sudah terminal. Rilis saat ini memproses job
secara inline sebelum menjawab
POST, sehingga respons pengiriman bisa saja sudah membawastatus: "completed"— seperti yang terjadi di bawah. Kontrak poll-hingga-terminal adalah bentuk API yang stabil: tulis loop polling, dan terima status terminal pada percobaan mana pun, termasuk yang pertama.
Anda dapat memastikan apa yang diekspos deployment Anda sebelum mengirim
apa pun: GET /api/v1/capabilities mengembalikan katalog operasi yang
dapat dijangkau oleh tier kunci API Anda. Pada deployment core-tier yang
ditangkap di sini, katalog itu hanya mencantumkan operasi core; katalog
resmi selalu merupakan respons server yang sedang berjalan itu sendiri,
bukan halaman ini.
Permukaan API
Bagian berjudul “Permukaan API”| Pertukaran | Metode dan path | Status yang ditangkap |
|---|---|---|
| Kirim job render | POST /api/v1/jobs | 201 Created |
| Ulangi pengiriman yang sama | POST /api/v1/jobs (Idempotency-Key yang sama) | 200 OK |
| Polling rekaman job | GET /api/v1/jobs/{id} | 200 OK |
| Unduh PDF | GET /api/v1/jobs/{id}/result | 200 OK, application/pdf |
| Hapus job yang telah selesai | DELETE /api/v1/jobs/{id} | 204 No Content |
Autentikasi berupa bearer token pada setiap permintaan /api/v1/*:
Authorization: Bearer npk_live_{kid}_{secret}. Respons JSON yang berhasil
menggunakan envelope { "data": ..., "meta": ... } yang sama; field yang Anda gunakan
berada di bawah data.
Permintaan faktur
Bagian berjudul “Permintaan faktur”Tulis permintaan render ke invoice.json. Ini adalah daftar operasi yang
sederhana dan deterministik — sebuah baris header tebal, baris penerbitan,
dan tabel item baris:
{ "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>" } ]}Field faktur di sini adalah data contoh. Bentuk argumen otoritatif untuk
setiap operasi adalah yang dilaporkan oleh deployment Anda — melalui MCP,
tools/list mengembalikan skema input lengkap untuk setiap tipe operasi
yang digunakan permintaan ini.
Transkrip end-to-end
Bagian berjudul “Transkrip end-to-end”1. Kirim job render
Bagian berjudul “1. Kirim job render”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.jsonServer menjawab 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" }}Job sudah terminal dalam tangkapan ini — status bernilai "completed"
dan result_url ada — karena rilis saat ini merender secara inline sebelum
menjawab. Jangan bergantung pada hal itu: perlakukan respons pengiriman
sebagai hasil polling pertama dan bercabang berdasarkan data.status
seperti polling lainnya.
2. Ulangi pengiriman (jalur idempoten)
Bagian berjudul “2. Ulangi pengiriman (jalur idempoten)”Ulangi persis perintah yang sama — Idempotency-Key yang sama, body yang
sama:
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.jsonServer mengembalikan 200 OK — bukan 201 — dengan job_id yang sama,
dan tidak ada render kedua yang terjadi (bandingkan meta.duration_ms
dengan respons pertama):
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. Polling rekaman job
Bagian berjudul “3. Polling rekaman 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" }}Polling ini menunjukkan rekaman terminal, sehingga tidak ada header
Retry-After dan tidak ada field poll_url. Selama job masih pending
atau running, server menetapkan Retry-After (interval 2 detik) pada
setiap polling — patuhi itu alih-alih melakukan polling dalam loop yang
ketat.
4. Unduh PDF
Bagian berjudul “4. Unduh 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 GMTBody-nya adalah biner PDF — 3.663 byte dalam tangkapan ini, cocok dengan
header Content-Length — dan disingkat di sini. Berkas itu ditulis ke
invoice-inv-2026-0042.pdf.
5. Verifikasi byte yang diunduh secara lokal
Bagian berjudul “5. Verifikasi byte yang diunduh secara lokal”Sebuah 200 dengan Content-Type: application/pdf bukanlah, dengan
sendirinya, bukti bahwa body adalah PDF yang well-formed. Jalankan
pemeriksaan struktural dengan qpdf:
qpdf --check invoice-inv-2026-0042.pdfKeluaran yang ditangkap untuk berkas yang diunduh di atas:
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 detectKata-kata qpdf sendiri adalah batas yang jujur: ini adalah pemeriksaan sintaks dan stream, bukan penentuan konformansi terhadap standar apa pun.
6. Hapus job yang telah selesai
Bagian berjudul “6. Hapus job yang telah selesai”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 GMTSetelah penghapusan, rekaman job dan hasil tersimpannya hilang; GET
berikutnya pada job mengembalikan 404.
Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”- Bercabang berdasarkan
data.status, bukan hanya berdasarkan status HTTP. Pengiriman, pengulangan, dan polling semuanya mengembalikan2xxdengan rekaman job; status siklus hidup berada didata.status(pending,running,completed,failed,cancelled). - Kunci yang diulang dengan body berbeda menghasilkan
409 Conflict. Pengulangan idempoten200hanya terjadi ketika body cocok dengan pengiriman asli. Jangan pernah menggunakan kembali kunci untuk konten yang berbeda. /resultsebelum selesai menghasilkan409. Unduh hanya setelah polling menunjukkancompleted.409adalah respons normal untuk diperiksa, bukan kegagalan transport — pemisahan transport-versus-status yang sama yang diikuti setiap resep Connect (lihat Konvensi resep).- Job bersifat owner-scoped. Job yang dikirim dengan satu kunci API
tidak terlihat oleh kunci lain:
GETlintas-pemilik mengembalikan404, bukan403. Lakukan polling dengan kredensial yang Anda gunakan untuk mengirim. progressmungkin tidak ada. Rekaman yang ditangkap tidak membawa fieldprogresskarena job sudah terminal. Ketika server melacak progres untuk job yang belum terminal,data.progressadalah integer dari 0 hingga 100; perlakukan field yang hilang sebagai tidak diketahui, bukan nol.- Job yang
failedmembawadata.error. Catat itu; jangan mengirim ulang secara membabi buta.
Performa
Bagian berjudul “Performa”Satu job render membutuhkan satu pengiriman, paling banyak beberapa
polling, dan satu unduhan. Nilai meta.duration_ms yang ditangkap
menceritakan keseluruhan kisahnya: 63,31 ms untuk merender faktur saat
pengiriman, 1,03 ms untuk pengulangan idempoten yang tidak melakukan
pekerjaan apa pun, dan pembacaan status di bawah satu milidetik. Lakukan
polling sesuai irama Retry-After server alih-alih loop yang ketat;
pembacaan status memang murah tetapi tidak gratis, dan pembatas laju
menganggarkannya (perhatikan X-Ratelimit-Remaining menurun dalam header
yang ditangkap). Untuk batch, batasi job yang sedang berjalan alih-alih
mengirim semuanya sekaligus — resep batch
mengimplementasikan loop tersebut.
Catatan keamanan
Bagian berjudul “Catatan keamanan”- Simpan bearer token hanya di header
Authorization. Jangan pernah di query string, baris log, atau berkas yang di-commit. Transkrip di atas menggantinya dengan variabel lingkungan justru karena alasan itu. - Validasi byte yang diunduh sebelum mempercayainya. Langkah 5 adalah
bagian dari alur, bukan tambahan opsional: periksa bahwa respons adalah
PDF (minimal header
%PDF,qpdf --checkuntuk struktur) sebelum mengarsipkan atau meneruskannya. - Hapus job selesai yang tidak lagi Anda perlukan. Langkah 6 menghapus hasil tersimpan dari server; jika tidak, job yang telah selesai tetap dapat diunduh hingga garbage collection job pada server menghapusnya.
- Gunakan kunci dengan hak istimewa paling kecil. Alur ini hanya membutuhkan kunci render core-tier, tidak lebih.
Konformansi
Bagian berjudul “Konformansi”Resep ini tidak membuat klaim standar normatif. Resep ini menjalankan
endpoint REST async-job Connect dan membaca field rekaman-job yang
didefinisikan server. Langkah qpdf --check hanya mengonfirmasi integritas
struktural — “the file may still contain errors that qpdf cannot detect”
adalah peringatan qpdf sendiri, dikutip verbatim di atas. Menentukan
konformansi terhadap suatu standar (PDF/A-4, PDF/UA) adalah tugas validator
independen, dan permukaan yang berbeda — lihat
Jalankan pemeriksaan standar bernama
untuk batas tersebut.
Lihat juga
Bagian berjudul “Lihat juga”- Buat PDF secara batch dengan pelacakan progres — permukaan job yang sama dijalankan sebagai batch dengan konkurensi terbatas.
- Buat PDF pertama Anda — render Connect terkecil.
- Jalankan sesi dokumen agen melalui MCP — mesin yang sama, tool demi tool, melalui transport stdio MCP.
- Konvensi resep Connect — kontrak transport, tier, dan konformansi yang diikuti setiap resep Connect.
- Penanganan kesalahan yang sadar-eksepsi melalui Connect — cara memisahkan kegagalan transport dari status non-sukses.