安定性: ベータ
REST でインボイスをエンドツーエンドでレンダリングする
1 件のインボイスを、JSON からローカルで検証済みの PDF まで、NextPDF
Connect の Representational State Transfer(REST)サーフェス上で、ワイヤー上のやり取りを 1 回ずつたどりながら処理します。このレシピでは、レンダリングジョブを 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 などのヘッダー値は、
当然ながらお使いのデプロイでは異なります。
このレシピは 1 件の文書を扱うので、各やり取りを最後まで通して読むことができます。多数の文書、上限を設けた並行処理、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)を加えたものです。
これから読むトランスクリプトを形作る、2 つの契約上の詳細があります。
- 冪等な送信。
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/* リクエストでベアラートークンを使います。
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サーバーは、201 ではなく 200 OK を、同じ job_id とともに返します。
2 回目のレンダリングは発生しません(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 を返します。
エッジケースと注意点
「エッジケースと注意点」という見出しのセクション- HTTP ステータスだけでなく、
data.statusで分岐してください。 送信、 再実行、ポーリングはいずれも、ジョブレコードとともに2xxを返します。 ライフサイクルの状態はdata.status(pending、running、completed、failed、cancelled)にあります。 - 同じキーでボディを変えて再実行すると
409 Conflictになります。 冪等な200の再実行は、ボディが元の送信と一致する場合にのみ発生します。 異なる内容に対してキーを再利用しないでください。 - 完了前の
/resultは409になります。 ポーリングがcompletedを示してからのみダウンロードしてください。この409は、トランスポートの失敗ではなく、確認すべき通常のレスポンスです。これは、すべての Connect レシピが従う、トランスポートとステータスの分離と同じです (レシピの規約を参照)。 - ジョブはオーナースコープです。 ある API キーで送信したジョブは、
別のキーからは見えません。オーナーをまたぐ
GETは、403ではなく404を返します。送信に使用した資格情報でポーリングしてください。 progressが存在しないことがあります。 キャプチャしたレコードにはprogressフィールドがありません。ジョブが既に終端状態だったためです。 サーバーが非終端状態のジョブの進捗を追跡する場合、data.progressは 0 から 100 までの整数になります。フィールドが欠けている場合は、0 ではなく不明として扱ってください。failedのジョブにはdata.errorが入ります。 それを記録してください。 やみくもに再送信しないでください。
パフォーマンス
「パフォーマンス」という見出しのセクション1 件のレンダリングジョブのコストは、1 回の送信、多くても数回のポーリング、
そして 1 回のダウンロードです。キャプチャした meta.duration_ms の値がその実態を示しています。送信時のインボイスのレンダリングに 63.31 ms、
何も処理しなかった冪等な再実行に 1.03 ms、ステータスの読み取りはサブミリ秒台です。タイトなループではなく、サーバーの Retry-After の間隔でポーリングしてください。ステータスの読み取りは安価ですが無料ではなく、
その分だけレートリミッターの予算を消費します(キャプチャしたヘッダーで
X-Ratelimit-Remaining が減っていくのを確認してください)。バッチの場合は、すべてを一度に送信するのではなく、処理中のジョブ数に上限を設けてください。バッチレシピ
がそのループを実装しています。
セキュリティに関する注意
「セキュリティに関する注意」という見出しのセクション- ベアラートークンは
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 での例外を意識したエラー処理 — トランスポートの失敗を非成功ステータスから分離する方法。