穩定性: 測試版
透過 REST 端對端轉譯一份發票
透過 NextPDF Connect 的 Representational State Transfer(REST)介面,一次一筆連線交換,將一份發票從 JSON 轉為經本機驗證的 PDF。本範例將一項轉譯工作提交至
POST /api/v1/jobs、在相同的 Idempotency-Key 下重播該提交以展示重複安全的路徑、輪詢 GET /api/v1/jobs/{id}、從 GET /api/v1/jobs/{id}/result 下載
PDF、以 qpdf --check 檢查位元組,並刪除已完成的工作。
以下每一筆回應皆是從真實的 core 層級 Connect 部署(在 RoadRunner 下執行的
nextpdf/server,綁定至 http://localhost:8080)擷取的逐字內容。唯一的替換是 API 金鑰,以 $NEXTPDF_CONNECT_TOKEN 環境變數表示;工作識別碼、請求識別碼、時間戳記、標頭與主體位元組全都正是伺服器所回傳的內容。諸如 Date 與
X-Request-Id 之類的標頭值,在您的部署上當然會有所不同。
本範例僅驅動單一文件,讓您能完整閱讀每一次交換。若要處理多份文件、有界並行,
以及由 Retry-After 驅動的輪詢迴圈,請參閱
批次產生 PDF 並追蹤進度,
其使用相同的工作介面。
伺服器端是標準的 Connect 發行版本:
composer require nextpdf/server本範例的用戶端是 curl 加上 qpdf,因此您可以將其移植到任何 HTTP 用戶端。
請先匯出您部署環境的值:
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:概念總覽
標題為「概念總覽」的區段非同步工作介面將提交與擷取分離:您提交一項轉譯請求、收到一筆工作記錄,並在工作到達 completed 時擷取結果。轉譯請求本身是一個有序的 operations 陣列——在每一種傳輸上支撐 Connect 工具呼叫的相同操作類型(set_font、add_text、add_table、
add_image、add_page)——再加上文件層級的欄位(page_size、orientation、
title、author)。
有兩項合約細節形塑了您即將閱讀的逐字紀錄:
- 等冪提交。 以
Idempotency-Key標記的提交第一次會回傳201 Created,而在重播時則回傳200 OK並帶有相同的工作記錄,因此網路重試絕不會轉譯兩次。 - 提交可能已處於終端狀態。 目前的版本會在回應
POST之前就地處理該工作, 因此提交回應可能已帶有status: "completed"——如下方所示。輪詢直到終端狀態的合約才是穩定的 API 形態:撰寫輪詢迴圈,並在任何一次嘗試(包括第一次)接受終端狀態。
在提交任何內容之前,您可以確認您的部署所公開的內容:GET /api/v1/capabilities
會回傳您 API 金鑰的層級所能觸及的操作目錄。在此處擷取的 core 層級部署上,它僅列出
core 操作;準據目錄始終是執行中伺服器本身的回應,而非本頁面。
API 介面
標題為「API 介面」的區段| 交換 | 方法與路徑 | 擷取的狀態 |
|---|---|---|
| 提交轉譯工作 | POST /api/v1/jobs | 201 Created |
| 重播相同的提交 | POST /api/v1/jobs(相同的 Idempotency-Key) | 200 OK |
| 輪詢工作記錄 | GET /api/v1/jobs/{id} | 200 OK |
| 下載 PDF | GET /api/v1/jobs/{id}/result | 200 OK、application/pdf |
| 刪除已完成的工作 | DELETE /api/v1/jobs/{id} | 204 No Content |
每一筆 /api/v1/* 請求都以 bearer 權杖進行驗證:
Authorization: Bearer npk_live_{kid}_{secret}。成功的 JSON 回應共用
{ "data": ..., "meta": ... } 封套;您所要操作的欄位位於 data 之下。
發票請求
標題為「發票請求」的區段將轉譯請求寫入 invoice.json。它是一份單純、具決定性的操作清單——一行粗體標題、
一行開立資訊,以及一張明細項目表格:
{ "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>" } ]}此處的發票欄位為範例資料。每一項操作的準據引數形態,以您的部署所回報的為準——透過
MCP,tools/list 會為此請求所使用的每一種操作類型回傳完整的輸入結構描述。
端對端逐字紀錄
標題為「端對端逐字紀錄」的區段1. 提交轉譯工作
標題為「1. 提交轉譯工作」的區段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.json伺服器回應 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" }}在此擷取中,工作已處於終端狀態——status 為 "completed" 且 result_url 存在——
因為目前的版本會在回應之前就地轉譯。請勿依賴這一點:將提交回應視為第一筆輪詢結果,並如同任何其他輪詢一樣依 data.status 進行分支。
2. 重播提交(等冪路徑)
標題為「2. 重播提交(等冪路徑)」的區段重試完全相同的命令——相同的 Idempotency-Key、相同的主體:
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.json伺服器回傳 200 OK——而非 201——並帶有相同的 job_id,且不會發生第二次轉譯(請將 meta.duration_ms 與第一筆回應比較):
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. 輪詢工作記錄
標題為「3. 輪詢工作記錄」的區段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" }}這筆輪詢顯示一筆終端記錄,因此沒有 Retry-After 標頭,也沒有 poll_url 欄位。
當工作仍為 pending 或 running 時,伺服器會在每一次輪詢時設定 Retry-After
(2 秒的間隔)——請遵循它,而非以緊密迴圈輪詢。
4. 下載 PDF
標題為「4. 下載 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 GMT主體是 PDF 二進位內容——在此擷取中為 3,663 個位元組,與 Content-Length 標頭相符——並在此處省略。它會被寫入 invoice-inv-2026-0042.pdf。
5. 於本機驗證下載的位元組
標題為「5. 於本機驗證下載的位元組」的區段一個帶有 Content-Type: application/pdf 的 200,本身並不能證明主體是格式正確的 PDF。請以 qpdf 執行結構檢查:
qpdf --check invoice-inv-2026-0042.pdf上方所下載檔案的擷取輸出:
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 自身的用詞是誠實的界線:這是語法與串流檢查,而非針對任何標準的符合性判定。
6. 刪除已完成的工作
標題為「6. 刪除已完成的工作」的區段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 GMT刪除之後,工作記錄及其儲存的結果都會消失;後續對該工作進行的 GET 會回傳
404。
邊界情況與陷阱
標題為「邊界情況與陷阱」的區段- 依
data.status分支,而非僅依 HTTP 狀態。 提交、重播與輪詢全都會回傳帶有工作記錄的2xx;生命週期狀態位於data.status(pending、running、completed、failed、cancelled)。 - 以不同主體重播同一組金鑰,會得到
409 Conflict。 只有在主體與原始提交相符時, 才會發生等冪的200重播。切勿為不同的內容重用金鑰。 - 在完成之前存取
/result會是409。 只有在輪詢顯示completed之後才下載。409是需要檢視的一般回應,並非傳輸失敗——這正是每個 Connect 範例都遵循的「傳輸與狀態」的區分(請參閱 範例慣例)。 - 工作以擁有者為範圍。 在某一 API 金鑰下提交的工作,對另一組金鑰而言是不可見的:跨擁有者的
GET會回傳404,而非403。請以您提交時所用的憑證進行輪詢。 progress可能不存在。 擷取的記錄並未帶有progress欄位,因為該工作已處於終端狀態。當伺服器為非終端工作追蹤進度時,data.progress是一個從 0 到 100 的整數;請將缺少的欄位視為未知,而非零。failed工作會帶有data.error。 請將其記錄下來;切勿盲目重新提交。
一項轉譯工作的成本是一次提交、至多寥寥數次輪詢,以及一次下載。擷取的
meta.duration_ms 值道盡了一切:提交時轉譯發票花費 63.31 ms、未做任何工作的等冪重播花費 1.03 ms,而狀態讀取則在毫秒以下。請依伺服器的 Retry-After 節奏輪詢,而非以緊密迴圈進行;狀態讀取雖便宜卻非免費,且速率限制器會將其計入預算
(請留意擷取標頭中 X-Ratelimit-Remaining 逐次遞減)。對於批次,請對進行中的工作設限,而非一次提交全部——批次範例
便實作了該迴圈。
安全注意事項
標題為「安全注意事項」的區段- 僅將 bearer 權杖保留在
Authorization標頭中。 切勿放在查詢字串、日誌行或已提交的檔案裡。上方的逐字紀錄正是基於這個原因而以環境變數替換。 - 在信任下載的位元組之前先驗證它們。 步驟 5 是流程的一部分,並非可有可無的附加項:在封存或轉送之前,請確認回應是 PDF(最少要有
%PDF標頭,並以qpdf --check檢查結構)。 - 刪除您不再需要的已完成工作。 步驟 6 會從伺服器移除已儲存的結果;否則,已完成的工作在伺服器的工作垃圾回收將其移除之前,仍可持續下載。
- 使用最小權限金鑰。 此流程只需要一組 core 層級的轉譯金鑰,別無其他。
符合性
標題為「符合性」的區段本範例未做出任何規範性標準主張。它演練 Connect 非同步工作的 REST 端點,並讀取伺服器所定義的工作記錄欄位。qpdf --check 步驟僅確認結構完整性——「the file may
still contain errors that qpdf cannot detect」是 qpdf 自身的但書,已於上方逐字引用。判定是否符合某項標準(PDF/A-4、PDF/UA)是獨立驗證器的工作,且屬於不同的介面——關於該界線,請參閱
執行具名標準檢查。
另請參閱
標題為「另請參閱」的區段- 批次產生 PDF 並追蹤進度 ——以有界並行批次驅動的相同工作介面。
- 產生您的第一份 PDF ——最小的 Connect 轉譯。
- 透過 MCP 驅動代理程式文件工作階段 ——相同的引擎,逐一工具,透過 MCP stdio 傳輸。
- Connect 範例慣例 ——每個 Connect 範例都遵循的傳輸、層級與符合性合約。
- 透過 Connect 進行例外感知的錯誤處理 ——如何將傳輸失敗與非成功狀態區分開來。