Render a document
Render one or more documents from one of your templates. You send the template’s id and a JSON data object; Pagr renders the latest published version and returns a JSON envelope describing the outcome — optionally with the PDF bytes inline.
This is the endpoint every official SDK wraps as its
render call. The examples below use the SDKs; for the raw HTTP shape, see
Request body and Response.
Endpoint
Section titled “Endpoint”POST /v1/render/{templateId}POST /v1/render/{templateId}/versions/{version}The first form renders the latest published version; the second renders a specific version by number. Every request must carry a bearer API key — see Authentication.
Example
Section titled “Example”curl -X POST "https://pagr-prd-api-public.azurewebsites.net/v1/render/8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90" \ -H "Authorization: Bearer pagr_prod_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "documents": [ { "Title": "Acme Q3 Invoice", "Amount": 42 } ], "includeDocument": true }'import asynciofrom pagr import PagrApiClient
async def main(): # The base URL defaults to the hosted Pagr API, so you only pass the key. async with PagrApiClient("pagr_prod_xxxxxxxx") as client: result = await client.render( "8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90", # template id json_data={"Title": "Acme Q3 Invoice", "Amount": 42}, include_document=True, # return the PDF inline )
if result.ok: doc = result.document print(doc.document_name, doc.page_count, doc.view_url) doc.save("out/") # writes out/<name>.pdf else: if result.insufficient_credit: print("Out of credit:", result.message) for issue in result.issues: print(issue) # e.g. "Error: MissingBinding [total] — …"
asyncio.run(main())import { PagrApiClient } from 'pagr';
// The base URL defaults to the hosted Pagr API, so you only pass the key.const client = new PagrApiClient('pagr_prod_xxxxxxxx');
const result = await client.render( '8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90', // template id { Title: 'Acme Q3 Invoice', Amount: 42 }, { includeDocument: true }, // return the PDF inline);
if (result.ok) { const doc = result.document!; console.log(doc.documentName, doc.pageCount, doc.viewUrl); await doc.save('./out'); // writes ./out/<name>.pdf} else { if (result.insufficientCredit) console.log('Out of credit:', result.message); for (const issue of result.issues) console.log(issue.description);}import org.example.PagrApiClient;import org.example.RenderOptions;import org.example.models.RenderResult;import java.nio.file.Path;import java.util.UUID;
// The base URL defaults to the hosted Pagr API, so you only pass the key.try (PagrApiClient client = new PagrApiClient("pagr_prod_xxxxxxxx")) {
RenderResult result = client.render( UUID.fromString("8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90"), // template id "{\"Title\": \"Acme Q3 Invoice\", \"Amount\": 42}", RenderOptions.builder().includeDocument(true).build()); // return the PDF inline
if (result.isOk()) { var doc = result.getDocument(); System.out.println(doc.getDocumentName() + " — " + doc.getPageCount() + " pages — " + doc.getViewUrl()); doc.save(Path.of("out")); } else { if (result.isInsufficientCredit()) { System.out.println("Out of credit: " + result.getStatus()); } for (var issue : result.getIssues()) { System.out.println(issue); } }}A separate PagrAsyncApiClient mirrors the same calls, returning CompletableFuture<T>.
using Pagr.Sdk;
// The client owns a pooled HttpClient — create one and reuse it.// The base URL defaults to the hosted Pagr API, so you only pass the key.using var client = new PagrApiClient("pagr_prod_xxxxxxxx");
var result = await client.RenderAsync( Guid.Parse("8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90"), // template id new { Title = "Acme Q3 Invoice", Amount = 42 }, // any POCO, anonymous type, or JSON string includeDocument: true); // return the PDF inline
if (result.Ok){ var doc = result.Document!; Console.WriteLine($"{doc.DocumentName} — {doc.PageCount} pages — {doc.ViewUrl}"); await doc.SaveAsync(@"C:\out"); // directory → C:\out\<name>.pdf}else{ if (result.InsufficientCredit) Console.WriteLine($"Out of credit: {result.Message}"); foreach (var issue in result.Issues) Console.WriteLine(issue);}require "pagr"
# The base URL defaults to the hosted Pagr API, so you only pass the key.client = Pagr::Client.new("pagr_prod_xxxxxxxx")
result = client.render( "8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90", # template id { "Title" => "Acme Q3 Invoice", "Amount" => 42 }, include_document: true, # return the PDF inline)
if result.ok? doc = result.document puts "#{doc.document_name} — #{doc.page_count} pages — #{doc.view_url}" doc.save("out/") # writes out/<name>.pdfelse warn "Out of credit: #{result.message}" if result.insufficient_credit? result.issues.each { |issue| warn issue }end#include "pagr/PagrApiClient.hpp"
// The base URL defaults to the hosted Pagr API, so you only pass the key.pagr::PagrApiClient client("pagr_prod_xxxxxxxx");
std::string template_id = "8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90";auto result = client.render( template_id, R"({"Title": "Acme Q3 Invoice", "Amount": 42})", {.include_document = true});
if (result.ok() && result.document->has_content()) { auto& doc = *result.document; std::cout << doc.document_name << " — " << doc.page_count << " pages — " << doc.view_url << "\n"; doc.save("out/" + doc.document_name + ".pdf");} else { if (result.insufficient_credit()) { /* out of credit */ } for (auto& issue : result.issues) { /* handle */ }}Every call also has a _async form (e.g. render_async) returning std::future<T>.
Path parameters
Section titled “Path parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
templateId |
string (UUID) | Yes | The template to render. |
version |
integer | Only for the specific-version form | The template version number to render. Omit the whole path segment to render the latest published version. |
Query parameters
Section titled “Query parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
persist |
boolean | true |
Whether to store the rendered document. When true, the document’s id and viewUrl are populated and it appears in Renders; when false, both are null and the PDF is returned inline instead (see the note under Response). |
language |
string | — | Language variant to render, for multilingual templates. A value the template version doesn’t define is rejected with 400 ValidationError. |
Request body
Section titled “Request body”A JSON object with the documents to render. Even for a single document, documents
is an array — the same endpoint renders a synchronous batch when you pass more
than one.
| Field | Type | Default | Description |
|---|---|---|---|
documents |
array of objects | — (required) | One object per document. Each object is your template’s data; each document is limited to 50 MB of JSON, nested at most 32 levels deep. A test key caps the array at 10 entries. |
includeDocument |
boolean | false |
When true, each rendered document carries its PDF inline as Base64 in documentBase64. The SDKs decode this for you. |
{ "documents": [ { "Title": "Acme Q3 Invoice", "Amount": 42 } ], "includeDocument": true}Response
Section titled “Response”Every render returns HTTP 200 with a JSON envelope — any outcome, any batch
size. Inspect status and issues rather than relying on the status code: a
document that fails validation, or a job stopped for credit, is a normal
outcome reported here, not an HTTP error.
{ "status": "ok", // "ok" | "partial" | "failed" | "insufficient_credit" "message": "1 document(s) rendered.", "requestedCount": 1, // documents submitted "renderedCount": 1, // documents actually rendered "missingCount": 0, // requestedCount − renderedCount "issues": [], // per-document validation & render issues "documents": [ { "id": "f61aeff4-2c9d-4b7a-8e10-3a5b9c2d1e00", // null when persist=false "documentName": "Acme Q3 Invoice", "templateId": "8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90", "versionNumber": 3, "environment": "production", "fileSizeBytes": 24815, "pageCount": 1, "renderedAt": "2026-07-24T09:46:01Z", "renderDuration": 412.7, // milliseconds "viewUrl": "https://…", // null when persist=false "documentType": "Template", "language": null, "documentBase64": null, // populated only when includeDocument=true (or persist=false) "documentIndex": 0 // zero-based position in the documents array } ]}Envelope fields
Section titled “Envelope fields”| Field | Type | Description |
|---|---|---|
status |
string | ok, partial, failed, or insufficient_credit. |
message |
string | Human-readable summary of the outcome. |
requestedCount |
number | Documents submitted. |
renderedCount |
number | Documents that actually rendered. |
missingCount |
number | requestedCount − renderedCount — everything not rendered, whatever the reason. |
issues |
array | RenderIssue objects explaining why a document is missing. Each carries documentIndex, type, severity, and description. |
documents |
array | The rendered documents (see below). Blocked documents leave gaps, so correlate by documentIndex, never by list position. |
Document fields
Section titled “Document fields”| Field | Type | Description |
|---|---|---|
id |
string (UUID) or null | Stored document id. null when persist=false. |
documentName |
string | The document’s name (no extension; output is always PDF). |
templateId |
string (UUID) | The template it was rendered from. |
versionNumber |
number | The template version used. |
environment |
string | production or test, decided by the API key. |
fileSizeBytes |
number | Size of the rendered PDF. |
pageCount |
number | Number of pages. |
renderedAt |
string (ISO 8601) | When it rendered. |
renderDuration |
number | Render time in milliseconds. |
viewUrl |
string or null | Signed download URL. null when persist=false. |
documentType |
string | Template or Invoice. |
language |
string or null | The language variant rendered, or null. |
documentBase64 |
string or null | The PDF bytes, Base64-encoded. Populated only when includeDocument=true or persist=false. |
documentIndex |
number | Zero-based position in the request’s documents array. |
SDK reference
Section titled “SDK reference”| SDK | Method |
|---|---|
| Python | await client.render(template_id, json_data, *, version=None, include_document=False, language=None, persist=True, timeout=None) |
| TypeScript | await client.render(templateId, jsonData, options?) |
| Java | client.render(templateId, data) / client.render(templateId, data, RenderOptions) |
| C# | await client.RenderAsync(templateId, data, version, includeDocument, language, persist, timeout, cancellationToken) |
| Ruby | client.render(template_id, json_data, version:, include_document:, language:, persist:, timeout:) |
| C++ | client.render(template_id, json_data, RenderOptions) / client.render_async(…) |
For more than one document, use the batch form of the same call —
render_batch / renderBatch / RenderBatchAsync. See
Render a batch.
Errors
Section titled “Errors”Transport and protocol failures come back as HTTP error statuses with a
{ "error": { "code", "message" } } body — 400 (test-key batch over 10
documents, or an unknown language), 401 (bad key), 403 (not allowed),
404 (template or version not found), 413 (a document over 50 MB), 422
(couldn’t bind the body), 429 (rate limited). The SDKs map each to a typed
exception. See Errors for the full table.
Related articles
Section titled “Related articles”- Render and save a PDF — the same call, walked through step by step.
- Choose a rendering shape — single vs. batch vs. async vs. stateless.
- Render a raw PDF — the
Accept: application/pdfvariant. - Validate data — the same checks, without spending a render.
- Errors — the full status code and render issue reference.
- Renders — where persisted documents show up in the app.
