안정성: 베타
REST로 인보이스를 처음부터 끝까지 렌더링하기
한눈에 보기
섹션 제목: “한눈에 보기”인보이스 하나를 JSON에서 시작해 NextPDF Connect의 Representational
State Transfer (REST) 표면을 통해 로컬에서 검증된 PDF까지, 한 번에 하나의
와이어 교환씩 따라갑니다. 이 레시피는 렌더링 작업을 POST /api/v1/jobs에
제출하고, 동일한 Idempotency-Key로 제출을 재전송해 중복 안전 경로를
보여주며, GET /api/v1/jobs/{id}를 폴링하고,
GET /api/v1/jobs/{id}/result에서 PDF를 다운로드한 뒤, qpdf --check로
바이트를 확인하고, 완료된 작업을 삭제합니다.
아래의 모든 응답은 실제 core 등급 Connect 배포(RoadRunner 위에서 실행되며
http://localhost:8080에 바인딩된 nextpdf/server)에서 그대로 캡처한
것입니다. 유일하게 치환된 값은 $NEXTPDF_CONNECT_TOKEN 환경 변수로 표시된
API 키뿐이며, 작업 식별자, 요청 식별자, 타임스탬프, 헤더, 본문 바이트는
모두 서버가 반환한 그대로입니다. 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"를 담고 있을 수 있습니다 — 아래에서 실제로 그렇습니다. 종료 상태까지 폴링하는(poll-until-terminal) 계약이 안정적인 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서버는 동일한 job_id와 함께 201이 아닌 200 OK를 반환하며, 두 번째
렌더링은 일어나지 않습니다(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 바이너리이며 — 이 캡처에서는 Content-Length 헤더와 일치하는
3,663바이트입니다 — 여기서는 생략되었습니다. 이 데이터는
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를 담고 있습니다. 이를 기록하십시오. 무작정 재제출하지 마십시오.
하나의 렌더링 작업은 한 번의 제출, 많아야 몇 차례의 폴링, 그리고 한 번의
다운로드로 이루어집니다. 캡처된 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 단계는 구조적 무결성만 확인합니다 — “파일에는
여전히 qpdf가 감지할 수 없는 오류가 있을 수 있습니다”라는 문구는 위에서
그대로 인용한 qpdf 자체의 주의 사항입니다. 표준(PDF/A-4, PDF/UA)에 대한
적합성을 판정하는 것은 독립적인 검증기의 몫이며, 다른 표면의 일입니다 —
그 경계에 대해서는
명명된 표준 검사 실행하기를
참고하십시오.
함께 보기
섹션 제목: “함께 보기”- 진행 상황 추적과 함께 PDF 일괄 생성하기 — 제한된 동시성 일괄 처리로 구동되는 동일한 작업 표면입니다.
- 첫 PDF 생성하기 — 가장 작은 Connect 렌더링입니다.
- MCP로 에이전트 문서 세션 구동하기 — 동일한 엔진을 MCP stdio 전송을 통해 도구 하나하나로 구동합니다.
- Connect 레시피 규약 — 모든 Connect 레시피가 따르는 전송, 등급, 적합성 계약입니다.
- Connect를 통한 예외 인식 오류 처리 — 전송 실패를 성공이 아닌 상태와 분리하는 방법입니다.