estabilidade: Beta
Renderize uma fatura de ponta a ponta via REST
Visão geral
Seção intitulada “Visão geral”Leve uma fatura de JSON até um PDF verificado localmente, através da superfície
Representational State Transfer (REST) do NextPDF Connect, uma troca de
rede por vez. Esta receita envia um job de renderização para
POST /api/v1/jobs, repete o envio com a mesma Idempotency-Key para
mostrar o caminho seguro contra duplicação, consulta
GET /api/v1/jobs/{id}, baixa o PDF de
GET /api/v1/jobs/{id}/result, verifica os bytes com qpdf --check e
exclui o job finalizado.
Cada resposta abaixo é uma captura literal de uma implantação Connect real
de tier core (nextpdf/server sob RoadRunner, associada a
http://localhost:8080). A única substituição é a API key, mostrada como
a variável de ambiente $NEXTPDF_CONNECT_TOKEN; identificadores de job,
identificadores de requisição, timestamps, cabeçalhos e bytes do corpo são
exatamente o que o servidor retornou. Valores de cabeçalho como Date e
X-Request-Id naturalmente serão diferentes na sua implantação.
Esta receita conduz um único documento para que você possa ler cada troca
por completo. Para muitos documentos, concorrência limitada e laços de
consulta guiados por Retry-After, consulte
Gere PDFs em lote com rastreamento de progresso,
que usa a mesma superfície de jobs.
Instalação
Seção intitulada “Instalação”O lado do servidor é a distribuição Connect padrão:
composer require nextpdf/serverO lado do cliente desta receita é curl mais qpdf, então você pode
portá-la para qualquer cliente HTTP. Exporte primeiro os valores da sua
implantação:
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:Visão conceitual
Seção intitulada “Visão conceitual”A superfície de jobs assíncronos separa o envio da recuperação: você envia
uma requisição de renderização, recebe um registro de job e busca o
resultado quando o job atinge completed. A própria requisição de
renderização é um array operations ordenado — os mesmos tipos de operação
(set_font, add_text, add_table, add_image, add_page) que
sustentam as chamadas de ferramenta do Connect em todo transporte — mais
campos no nível do documento (page_size, orientation, title,
author).
Dois detalhes do contrato moldam a transcrição que você está prestes a ler:
- Envio idempotente. Um envio identificado com
Idempotency-Keyretorna201 Createdna primeira vez e200 OKcom o mesmo registro de job quando repetido, de modo que uma nova tentativa de rede nunca renderiza duas vezes. - O envio pode já estar em estado terminal. A versão atual processa o
job inline antes de responder ao
POST, então a resposta de envio já pode carregarstatus: "completed"— como acontece abaixo. O contrato de consultar até o estado terminal é o formato estável da API: escreva o laço de consulta e aceite um estado terminal em qualquer tentativa, inclusive na primeira.
Você pode confirmar o que sua implantação expõe antes de enviar qualquer
coisa: GET /api/v1/capabilities retorna o catálogo de operações que o
tier da sua API key consegue alcançar. Na implantação de tier core
capturada aqui, ele listou apenas as operações core; o catálogo oficial é
sempre a resposta do próprio servidor em execução, não esta página.
Superfície da API
Seção intitulada “Superfície da API”| Troca | Método e caminho | Status capturado |
|---|---|---|
| Enviar o job de renderização | POST /api/v1/jobs | 201 Created |
| Repetir o mesmo envio | POST /api/v1/jobs (mesma Idempotency-Key) | 200 OK |
| Consultar o registro do job | GET /api/v1/jobs/{id} | 200 OK |
| Baixar o PDF | GET /api/v1/jobs/{id}/result | 200 OK, application/pdf |
| Excluir o job finalizado | DELETE /api/v1/jobs/{id} | 204 No Content |
A autenticação é um bearer token em toda requisição /api/v1/*:
Authorization: Bearer npk_live_{kid}_{secret}. Respostas JSON
bem-sucedidas compartilham o envelope { "data": ..., "meta": ... }; os
campos sobre os quais você atua ficam em data.
A requisição da fatura
Seção intitulada “A requisição da fatura”Escreva a requisição de renderização em invoice.json. É uma lista
simples e determinística de operações — uma linha de cabeçalho em negrito, uma
linha de emissão e uma tabela de itens:
{ "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>" } ]}Os campos da fatura aqui são dados de exemplo. O formato autoritativo dos
argumentos de cada operação é o que sua implantação relata — via MCP,
tools/list retorna o schema de entrada completo para cada tipo de
operação que esta requisição usa.
Transcrição de ponta a ponta
Seção intitulada “Transcrição de ponta a ponta”1. Enviar o job de renderização
Seção intitulada “1. Enviar o job de renderização”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.jsonO servidor responde 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" }}O job já está em estado terminal nesta captura — status é "completed"
e result_url está presente — porque a versão atual renderiza inline antes
de responder. Não dependa disso: trate a resposta de envio como o primeiro
resultado de consulta e ramifique com base em data.status como em
qualquer outra consulta.
2. Repetir o envio (caminho idempotente)
Seção intitulada “2. Repetir o envio (caminho idempotente)”Repita exatamente o mesmo comando — mesma Idempotency-Key, mesmo corpo:
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.jsonO servidor retorna 200 OK — não 201 — com o mesmo job_id, e nenhuma
segunda renderização acontece (compare meta.duration_ms com a primeira
resposta):
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. Consultar o registro do job
Seção intitulada “3. Consultar o registro do 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" }}Esta consulta mostra um registro terminal, portanto não há cabeçalho
Retry-After nem campo poll_url. Enquanto um job ainda está pending ou
running, o servidor define Retry-After (um intervalo de 2 segundos) em
cada consulta — respeite-o em vez de consultar em um laço apertado.
4. Baixar o PDF
Seção intitulada “4. Baixar o 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 GMTO corpo é o binário do PDF — 3.663 bytes nesta captura, correspondendo ao
cabeçalho Content-Length — e está omitido aqui. Ele é gravado em
invoice-inv-2026-0042.pdf.
5. Verificar os bytes baixados localmente
Seção intitulada “5. Verificar os bytes baixados localmente”Um 200 com Content-Type: application/pdf não é, por si só, prova de que
o corpo é um PDF bem-formado. Execute uma verificação estrutural com qpdf:
qpdf --check invoice-inv-2026-0042.pdfSaída capturada para o arquivo baixado acima:
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 detectAs próprias palavras do qpdf são o limite honesto: esta é uma verificação de sintaxe e de streams, não uma determinação de conformidade com qualquer padrão.
6. Excluir o job finalizado
Seção intitulada “6. Excluir o job finalizado”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 GMTApós a exclusão, o registro do job e seu resultado armazenado desaparecem;
um GET subsequente no job retorna 404.
Casos extremos e armadilhas
Seção intitulada “Casos extremos e armadilhas”- Ramifique com base em
data.status, não apenas no status HTTP. Envio, repetição e consulta retornam todos2xxcom um registro de job; o estado do ciclo de vida fica emdata.status(pending,running,completed,failed,cancelled). - Uma chave repetida com corpo diferente é um
409 Conflict. A repetição idempotente com200só acontece quando o corpo corresponde ao envio original. Nunca reutilize uma chave para conteúdo diferente. /resultantes da conclusão é um409. Baixe somente depois que uma consulta mostrarcompleted. O409é uma resposta normal a ser inspecionada, não uma falha de transporte — a mesma separação entre transporte e status que toda receita do Connect segue (consulte Convenções de receita).- Jobs têm escopo de proprietário. Um job enviado sob uma API key é
invisível para outra chave: um
GETentre proprietários retorna404, não403. Consulte com a credencial usada no envio. progresspode estar ausente. O registro capturado não carrega o campoprogressporque o job já estava em estado terminal. Quando o servidor rastreia o progresso de um job não terminal,data.progressé um inteiro de 0 a 100; trate um campo ausente como desconhecido, não como zero.- Um job
failedcarregadata.error. Registre-o; não reenvie às cegas.
Desempenho
Seção intitulada “Desempenho”Um job de renderização custa um envio, no máximo um punhado de
consultas e um download. Os valores meta.duration_ms capturados contam a
história: 63,31 ms para renderizar a fatura no envio, 1,03 ms para a
repetição idempotente que não fez trabalho algum e leituras de status
abaixo de um milissegundo. Consulte na cadência de Retry-After do
servidor em vez de um laço apertado; a leitura de status é barata, mas não
gratuita, e o rate limiter a contabiliza (observe X-Ratelimit-Remaining
decrescer nos cabeçalhos capturados). Para lotes, limite os jobs em
andamento em vez de enviar tudo de uma vez — a
receita de lote
implementa esse laço.
Notas de segurança
Seção intitulada “Notas de segurança”- Mantenha o bearer token apenas no cabeçalho
Authorization. Nunca em uma query string, uma linha de log ou um arquivo commitado. A transcrição acima substitui uma variável de ambiente exatamente por esse motivo. - Valide os bytes baixados antes de confiar neles. A etapa 5 faz parte
do fluxo, não é um extra opcional: verifique se a resposta é um PDF (o
cabeçalho
%PDFno mínimo,qpdf --checkpara a estrutura) antes de arquivá-la ou encaminhá-la. - Exclua os jobs finalizados de que você não precisa mais. A etapa 6 remove o resultado armazenado do servidor; caso contrário, um job concluído permanece disponível para download até que a coleta de lixo de jobs do servidor o remova.
- Use uma chave de privilégio mínimo. Este fluxo precisa de uma chave de renderização de tier core e nada mais.
Conformidade
Seção intitulada “Conformidade”Esta receita não faz nenhuma afirmação normativa sobre padrões. Ela
exercita os endpoints REST de jobs assíncronos do Connect e lê os campos do
registro de job que o servidor define. A etapa qpdf --check confirma
apenas a integridade estrutural — “the file may still contain errors that
qpdf cannot detect” é a própria ressalva do qpdf, citada literalmente
acima. Determinar a conformidade com um padrão (PDF/A-4, PDF/UA) é tarefa
de um validador independente, e uma superfície diferente — consulte
Execute uma verificação de padrão nomeado
para esse limite.
Veja também
Seção intitulada “Veja também”- Gere PDFs em lote com rastreamento de progresso — a mesma superfície de jobs conduzida como um lote de concorrência limitada.
- Gere seu primeiro PDF — a menor renderização do Connect.
- Conduza uma sessão de documento de agente via MCP — o mesmo motor, ferramenta a ferramenta, pelo transporte stdio do MCP.
- Convenções de receita do Connect — o contrato de transporte, tier e conformidade que toda receita do Connect segue.
- Tratamento de erros ciente de exceções via Connect — como separar falhas de transporte dos status de não sucesso.