Bỏ qua để đến nội dung
getnextpdf.com

độ ổn định: Beta

Kết xuất hóa đơn từ đầu đến cuối qua REST

Đưa một hóa đơn từ JSON đến một tệp PDF được xác minh cục bộ qua bề mặt Representational State Transfer (REST) của NextPDF Connect, mỗi lần một lượt trao đổi trên đường truyền. Công thức này gửi một tác vụ kết xuất tới POST /api/v1/jobs, phát lại lượt gửi với cùng một Idempotency-Key để minh họa đường dẫn an toàn với trùng lặp, thăm dò GET /api/v1/jobs/{id}, tải tệp PDF về từ GET /api/v1/jobs/{id}/result, kiểm tra các byte bằng qpdf --check, và xóa tác vụ đã hoàn tất.

Mọi phản hồi bên dưới đều là bản thu thập nguyên văn từ một triển khai Connect bậc core thực tế (nextpdf/server chạy dưới RoadRunner, gắn với http://localhost:8080). Thay thế duy nhất là khóa API, được hiển thị dưới dạng biến môi trường $NEXTPDF_CONNECT_TOKEN; các mã định danh tác vụ, mã định danh yêu cầu, dấu thời gian, header và byte thân đều đúng chính xác những gì máy chủ trả về. Các giá trị header như DateX-Request-Id tất nhiên sẽ khác đi trên triển khai của bạn.

Công thức này điều khiển một tài liệu duy nhất để bạn có thể đọc trọn vẹn từng lượt trao đổi. Với nhiều tài liệu, xử lý đồng thời có giới hạn, và các vòng lặp thăm dò do Retry-After điều khiển, hãy xem Tạo PDF hàng loạt kèm theo dõi tiến độ, vốn dùng chung bề mặt tác vụ này.

Phía máy chủ là bản phân phối Connect tiêu chuẩn:

Terminal window
composer require nextpdf/server

Phía client của công thức này là curl cộng với qpdf, nên bạn có thể chuyển nó sang bất kỳ HTTP client nào. Trước tiên, hãy export các giá trị của triển khai của bạn:

/docs/connect/quickstart/
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:

Bề mặt tác vụ bất đồng bộ tách việc gửi khỏi việc truy xuất: bạn gửi một yêu cầu kết xuất, nhận một bản ghi tác vụ, và lấy kết quả khi tác vụ đạt trạng thái completed. Bản thân yêu cầu kết xuất là một mảng operations có thứ tự — cùng các loại thao tác (set_font, add_text, add_table, add_image, add_page) làm nền cho các lệnh gọi công cụ Connect trên mọi transport — cộng thêm các trường ở cấp tài liệu (page_size, orientation, title, author).

Hai chi tiết hợp đồng định hình bản ghi mà bạn sắp đọc:

  • Gửi bất biến. Một lượt gửi có khóa Idempotency-Key trả về 201 Created ở lần đầu và 200 OK với cùng bản ghi tác vụ khi được phát lại, nên một lần thử lại qua mạng không bao giờ kết xuất hai lần.
  • Lượt gửi có thể đã ở trạng thái kết thúc. Bản phát hành hiện tại xử lý tác vụ tại chỗ trước khi trả lời POST, nên phản hồi của lượt gửi có thể đã mang status: "completed" — như trường hợp bên dưới. Hợp đồng thăm-dò-đến-khi-kết-thúc mới là hình dạng API ổn định: hãy viết vòng lặp thăm dò, và chấp nhận một trạng thái kết thúc ở bất kỳ lần thử nào, kể cả lần đầu.

Bạn có thể xác nhận những gì triển khai của bạn cung cấp trước khi gửi bất cứ thứ gì: GET /api/v1/capabilities trả về danh mục thao tác mà bậc khóa API của bạn có thể truy cập. Trên triển khai bậc core được thu thập ở đây, nó chỉ liệt kê các thao tác core; danh mục chính thức luôn là phản hồi của chính máy chủ đang chạy, chứ không phải trang này.

Lượt trao đổiPhương thức và đường dẫnTrạng thái đã thu thập
Gửi tác vụ kết xuấtPOST /api/v1/jobs201 Created
Phát lại cùng lượt gửiPOST /api/v1/jobs (cùng Idempotency-Key)200 OK
Thăm dò bản ghi tác vụGET /api/v1/jobs/{id}200 OK
Tải tệp PDF vềGET /api/v1/jobs/{id}/result200 OK, application/pdf
Xóa tác vụ đã hoàn tấtDELETE /api/v1/jobs/{id}204 No Content

Xác thực là một bearer token trên mọi yêu cầu /api/v1/*: Authorization: Bearer npk_live_{kid}_{secret}. Các phản hồi JSON thành công dùng chung phong bì { "data": ..., "meta": ... }; các trường bạn thao tác nằm dưới data.

Ghi yêu cầu kết xuất vào invoice.json. Đó là một danh sách thao tác đơn giản, tất định — một dòng tiêu đề in đậm, một dòng phát hành, và một bảng các mục hàng:

{
"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>" }
]
}

Các trường hóa đơn ở đây là dữ liệu mẫu. Hình dạng đối số chính thức cho mỗi thao tác là hình dạng do triển khai của bạn báo cáo — qua MCP, tools/list trả về schema đầu vào đầy đủ cho mọi loại thao tác mà yêu cầu này sử dụng.

Terminal window
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

Máy chủ trả lời 201 Created:

HTTP/1.1 201 Created
Cache-Control: no-store
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'
Content-Type: application/json; charset=utf-8
Referrer-Policy: no-referrer
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Powered-By: NextPDF Connect
X-Ratelimit-Remaining: 99
X-Request-Id: 019f3fd6-c6ed-727c-958e-2fa4370ba97e
Date: Wed, 08 Jul 2026 03:47:48 GMT
Transfer-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"
}
}

Tác vụ đã ở trạng thái kết thúc trong bản thu thập này — status"completed"result_url hiện diện — vì bản phát hành hiện tại kết xuất tại chỗ trước khi trả lời. Đừng phụ thuộc vào điều đó: hãy xem phản hồi của lượt gửi như kết quả thăm dò đầu tiên và rẽ nhánh theo data.status như mọi lần thăm dò khác.

2. Phát lại lượt gửi (đường dẫn bất biến)

Phần tiêu đề “2. Phát lại lượt gửi (đường dẫn bất biến)”

Thử lại đúng cùng một lệnh — cùng Idempotency-Key, cùng thân:

Terminal window
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

Máy chủ trả về 200 OK — không phải 201 — với cùng job_id, và không có lượt kết xuất thứ hai nào diễn ra (hãy so sánh meta.duration_ms với phản hồi đầu tiên):

HTTP/1.1 200 OK
Cache-Control: no-store
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'
Content-Type: application/json; charset=utf-8
Referrer-Policy: no-referrer
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Powered-By: NextPDF Connect
X-Ratelimit-Remaining: 99
X-Request-Id: 019f3fd6-c756-7226-bc85-b255d75bd449
Date: Wed, 08 Jul 2026 03:47:48 GMT
Transfer-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"
}
}
Terminal window
curl -sS -i "$NEXTPDF_CONNECT_URL/api/v1/jobs/job_9bd0808960f10eb568484acc" \
-H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN"
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'
Content-Type: application/json; charset=utf-8
Referrer-Policy: no-referrer
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Powered-By: NextPDF Connect
X-Ratelimit-Remaining: 98
X-Request-Id: 019f3fd7-0038-7131-b71e-4c69e637233b
Date: Wed, 08 Jul 2026 03:48:02 GMT
Transfer-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"
}
}

Lần thăm dò này cho thấy một bản ghi ở trạng thái kết thúc, nên không có header Retry-After và không có trường poll_url. Khi một tác vụ vẫn còn pending hoặc running, máy chủ đặt Retry-After (một khoảng 2 giây) ở mỗi lần thăm dò — hãy tôn trọng nó thay vì thăm dò trong một vòng lặp dồn dập.

Terminal window
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.pdf
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Disposition: attachment; filename="job-job_9bd0808960f10eb568484acc.pdf"
Content-Length: 3663
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'
Content-Type: application/pdf
Referrer-Policy: no-referrer
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Powered-By: NextPDF Connect
X-Ratelimit-Remaining: 98
X-Request-Id: 019f3fd7-0056-711e-acad-9f7ac5f21ba7
Date: Wed, 08 Jul 2026 03:48:02 GMT

Thân phản hồi là tệp PDF nhị phân — 3.663 byte trong bản thu thập này, khớp với header Content-Length — và được lược bỏ ở đây. Nó được ghi vào invoice-inv-2026-0042.pdf.

Một 200 với Content-Type: application/pdf tự nó không phải là bằng chứng cho thấy thân phản hồi là một tệp PDF đúng định dạng. Hãy chạy một lượt kiểm tra cấu trúc bằng qpdf:

Terminal window
qpdf --check invoice-inv-2026-0042.pdf

Kết quả thu thập cho tệp đã tải về ở trên:

checking invoice-inv-2026-0042.pdf
PDF Version: 2.0
File is not encrypted
File is not linearized
No syntax or stream encoding errors found; the file may still contain
errors that qpdf cannot detect

Chính cách diễn đạt của qpdf là ranh giới trung thực: đây là một lượt kiểm tra cú pháp và luồng, không phải là kết luận về mức độ tuân thủ đối với bất kỳ tiêu chuẩn nào.

Terminal window
curl -sS -i -X DELETE "$NEXTPDF_CONNECT_URL/api/v1/jobs/job_9bd0808960f10eb568484acc" \
-H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN"
HTTP/1.1 204 No Content
Cache-Control: no-store
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'
Content-Type: application/json; charset=utf-8
Referrer-Policy: no-referrer
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Powered-By: NextPDF Connect
X-Ratelimit-Remaining: 97
X-Request-Id: 019f3fd7-0084-722f-bf54-3dabead8aeea
Date: Wed, 08 Jul 2026 03:48:02 GMT

Sau khi xóa, bản ghi tác vụ và kết quả đã lưu trữ của nó biến mất; một GET tiếp theo trên tác vụ trả về 404.

  • Rẽ nhánh theo data.status, không chỉ dựa vào trạng thái HTTP. Gửi, phát lại và thăm dò đều trả về 2xx kèm một bản ghi tác vụ; trạng thái vòng đời nằm trong data.status (pending, running, completed, failed, cancelled).
  • Một khóa được phát lại với thân khác là 409 Conflict. Lượt phát lại 200 bất biến chỉ xảy ra khi thân khớp với lượt gửi ban đầu. Đừng bao giờ tái sử dụng một khóa cho nội dung khác.
  • Gọi /result trước khi hoàn tất là 409. Chỉ tải về sau khi một lần thăm dò cho thấy completed. 409 là một phản hồi bình thường cần kiểm tra, không phải một lỗi truyền tải — chính sự tách biệt giữa truyền tải và trạng thái mà mọi công thức Connect đều tuân theo (xem Quy ước công thức).
  • Tác vụ bị giới hạn theo chủ sở hữu. Một tác vụ được gửi bằng một khóa API là vô hình đối với khóa khác: một GET xuyên chủ sở hữu trả về 404, không phải 403. Hãy thăm dò bằng chính thông tin xác thực mà bạn đã dùng để gửi.
  • progress có thể vắng mặt. Bản ghi được thu thập không mang trường progress vì tác vụ đã ở trạng thái kết thúc. Khi máy chủ theo dõi tiến độ cho một tác vụ chưa kết thúc, data.progress là một số nguyên từ 0 đến 100; hãy xem một trường thiếu là không xác định, chứ không phải bằng không.
  • Một tác vụ failed mang theo data.error. Hãy ghi lại nó; đừng gửi lại một cách mù quáng.

Một tác vụ kết xuất tốn một lượt gửi, nhiều nhất là một vài lần thăm dò, và một lượt tải về. Các giá trị meta.duration_ms được thu thập kể lại câu chuyện: 63.31 ms để kết xuất hóa đơn khi gửi, 1.03 ms cho lượt phát lại bất biến vốn không làm gì, và các lượt đọc trạng thái dưới một mili-giây. Hãy thăm dò theo nhịp Retry-After của máy chủ thay vì một vòng lặp dồn dập; lượt đọc trạng thái rẻ nhưng không miễn phí, và bộ giới hạn tần suất tính chi phí cho nó (hãy quan sát X-Ratelimit-Remaining giảm dần trong các header được thu thập). Với các lô, hãy giới hạn số tác vụ đang xử lý thay vì gửi tất cả cùng một lúc — công thức hàng loạt hiện thực vòng lặp đó.

  • Chỉ giữ bearer token trong header Authorization. Không bao giờ để trong query string, một dòng nhật ký, hay một tệp đã commit. Bản ghi ở trên thay thế bằng một biến môi trường chính vì lý do đó.
  • Xác thực các byte đã tải về trước khi tin tưởng chúng. Bước 5 là một phần của luồng, không phải phần thêm tùy chọn: hãy kiểm tra phản hồi là một tệp PDF (tối thiểu là header %PDF, qpdf --check cho cấu trúc) trước khi lưu trữ hoặc chuyển tiếp nó.
  • Xóa các tác vụ đã hoàn tất mà bạn không còn cần. Bước 6 loại bỏ kết quả đã lưu trữ khỏi máy chủ; nếu không, một tác vụ đã hoàn tất vẫn có thể tải về cho đến khi cơ chế thu gom rác tác vụ của máy chủ loại bỏ nó.
  • Dùng một khóa với đặc quyền tối thiểu. Luồng này cần một khóa kết xuất bậc core và không cần gì hơn.

Công thức này không đưa ra tuyên bố tiêu chuẩn quy phạm nào. Nó sử dụng các endpoint REST cho tác vụ bất đồng bộ của Connect và đọc các trường bản ghi tác vụ mà máy chủ định nghĩa. Bước qpdf --check chỉ xác nhận tính toàn vẹn về cấu trúc — “the file may still contain errors that qpdf cannot detect” là lời cảnh báo của chính qpdf, được trích nguyên văn ở trên. Việc xác định mức độ tuân thủ đối với một tiêu chuẩn (PDF/A-4, PDF/UA) là công việc của một trình xác thực độc lập, và là một bề mặt khác — hãy xem Chạy kiểm tra tiêu chuẩn có tên cho ranh giới đó.