SDKs
TODO: Still needs to be updated. If the code would be hosted publicly then there can be a link to the repository.
You can call the Pagr API directly over HTTP, but the official SDKs make it easier: they wrap the base URL, attach your API key, parse responses into typed objects, turn HTTP errors into exceptions you can catch, retry transient read failures, and validate list filters client-side. They cover the whole API — templates and versions, every rendering shape, validation, document browsing, fonts, org stats, service probes, and webhook payload parsing.
Available languages
Section titled “Available languages”| Language | Package (working name) | Client | Sync / async | Notes |
|---|---|---|---|---|
| cURL (raw HTTP) | — | — | — | Call the API directly with any HTTP client — no library to install. See Quickstart. |
| Python | pagr (PyPI) |
PagrApiClient |
Async only | Built on httpx, full type hints. The reference implementation — most actively maintained; the others are kept at feature parity with it. |
| TypeScript | pagr (npm) |
PagrApiClient |
Async only (native fetch) |
Zero runtime dependencies, Node.js 18+. Classes parse API responses into iterable result types. |
| Java | final Maven coordinates TBD | PagrApiClient / PagrAsyncApiClient |
Both — a sync client plus an async client returning CompletableFuture |
Gradle library on a Java 21 toolchain; JSON handling kept internal. |
| C# / .NET | Pagr.Sdk (NuGet) |
PagrApiClient |
Async only | net8.0, sealed strongly-typed models, Async suffix, CancellationToken throughout, built on System.Text.Json. |
| Ruby | pagr (RubyGems, unpublished) |
Pagr::Client |
Synchronous only | Ruby 3.0+, built on Faraday, idiomatic Ruby naming (see below). |
| C++ | pagr_sdk (CMake package) |
pagr::PagrApiClient |
Both — every call has a sync form and an _async form |
C++20, integrated via CMake, RAII for resource management. |
The Java SDK’s package is still on a placeholder identifier internally — don’t build against a Maven coordinate for it yet; ask us for the current artifact when you request access.
Getting started
Section titled “Getting started”Construct a client with your API key, render one document, and save the PDF:
curl -X POST "https://pagr-prd-api-public.azurewebsites.net/v1/render/8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90" \ -H "Authorization: Bearer pagr_test_xxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "documents": [ { "Title": "Acme Q3 Invoice", "Amount": 42 } ], "includeDocument": true }'No SDK required — this is the same call every official SDK below wraps.
pip install pagrimport asynciofrom pagr import PagrApiClient
async def main(): async with PagrApiClient("pagr_test_xxxxxxxxxxxxxxxx") as client: result = await client.render( "8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90", json_data={"Title": "Acme Q3 Invoice", "Amount": 42}, include_document=True, ) if result.ok: result.document.save("out/")
asyncio.run(main())The base URL defaults to the hosted Pagr API — only the key is required.
Everything is async; use await and async with.
npm install pagrimport { PagrApiClient } from 'pagr';
const client = new PagrApiClient('pagr_test_xxxxxxxxxxxxxxxx');
const result = await client.render( '8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90', { Title: 'Acme Q3 Invoice', Amount: 42 }, { includeDocument: true },);
if (result.ok) { await result.document!.save('./out');}Node.js only — save() uses node:fs/promises, so it doesn’t run in a browser bundle.
// build.gradle — final Maven coordinates still TBD, ask us for the current artifactimplementation "<pagr-java-sdk-coordinate>"import org.example.PagrApiClient;import org.example.RenderOptions;import org.example.models.RenderResult;import java.nio.file.Path;import java.util.UUID;
try (PagrApiClient client = new PagrApiClient("pagr_test_xxxxxxxxxxxxxxxx")) {
RenderResult result = client.render( UUID.fromString("8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90"), "{\"Title\": \"Acme Q3 Invoice\", \"Amount\": 42}", RenderOptions.builder().includeDocument(true).build());
if (result.isOk()) { result.getDocument().save(Path.of("out")); }}A separate PagrAsyncApiClient mirrors every call, returning CompletableFuture<T>.
dotnet add package Pagr.Sdkusing Pagr.Sdk;
using var client = new PagrApiClient("pagr_test_xxxxxxxxxxxxxxxx");
var result = await client.RenderAsync( Guid.Parse("8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90"), new { Title = "Acme Q3 Invoice", Amount = 42 }, includeDocument: true);
if (result.Ok) await result.Document!.SaveAsync(@"C:\out");The client owns a pooled HttpClient — register it as a singleton, never one per request.
# Gemfile — not yet published; point at the path we share with yougem "pagr", path: "path/to/pagr-ruby-sdk"require "pagr"
client = Pagr::Client.new(ENV.fetch("PAGR_API_KEY"))
result = client.render( "8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90", { "Title" => "Acme Q3 Invoice", "Amount" => 42 }, include_document: true,)
if result.ok? result.document.save("out")endfind_package(pagr_sdk REQUIRED)target_link_libraries(your_target PRIVATE pagr_sdk::pagr_sdk)#include "pagr/PagrApiClient.hpp"
pagr::PagrApiClient client("pagr_test_xxxxxxxxxxxxxxxx");
auto result = client.render( std::string("8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90"), R"({"Title": "Acme Q3 Invoice", "Amount": 42})", {.include_document = true});
if (result.ok() && result.document->has_content()) result.document->save("out/" + result.document->document_name + ".pdf");Every method also has an _async form returning std::future<T> — the HTTP client itself
is synchronous, so this runs the call on a worker thread rather than doing true async I/O.
Common conventions
Section titled “Common conventions”Whichever language you use, the SDKs share the same behaviour — so what you learn in one carries over:
- Same authentication — a bearer API key passed at construction, swappable at runtime with
set_api_key. See Authentication. - Same error split — protocol failures raise; business outcomes (validation failure, insufficient credit, partial batch) come back as data on the result object.
- Same error mapping — one small exception tree, with transport failures (timeouts, connection errors) folded into it, so you only ever catch one family. See Handle errors and retries.
- Same retry policy — idempotent
GETs retried on500/502/503/504, timeouts and connection errors, with capped exponential backoff and full jitter. Writes never;429never. - Same rendering model — single, synchronous batch, async job and stateless renders, with the same counts,
statusvalues andissueson every result. See Choose a rendering shape. - Same paging —
skip/takewith atotaland a client-side “more pages?” flag. See Listing & pagination. - Same client-side filter validation — an unknown filter field or operator raises immediately instead of silently returning the unfiltered result set.
- Same webhook parsing — one function turns an incoming callback body into the right typed progress or completion object, validating the shape first.
Feature matrix
Section titled “Feature matrix”Nearly everything is at parity. These are the genuine differences worth knowing before you pick a language:
| Capability | Python | TypeScript | Java | C# | Ruby | C++ |
|---|---|---|---|---|---|---|
| Every endpoint | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Retry policy on reads | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Webhook payload parsing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Raw-PDF render (render_pdf) |
✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
| Per-call timeout override | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
Typed list/batch overload for validate |
✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
| Bundled webhook receiver (dev only) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
Filename sanitisation on save() |
✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
| Sync and async client | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ |
| Cancellation tokens | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
Where a cell is ❌, there’s a documented workaround on the linked page — e.g. C# and Java get the PDF bytes via includeDocument: true instead of render_pdf, and Java uses a longer-timeout client instead of a per-call override.
Naming conventions
Section titled “Naming conventions”Method names follow each language’s conventions rather than Python’s, so the same call reads natively everywhere:
| Operation | Python | TypeScript | Java | C# | Ruby | C++ |
|---|---|---|---|---|---|---|
| Render | render |
render |
render |
RenderAsync |
render |
render |
| Render a batch | render_batch |
renderBatch |
renderBatch |
RenderBatchAsync |
render_batch |
render_batch |
| Enqueue async | enqueue_batch_render |
enqueueBatchRender |
enqueueBatchRender |
EnqueueBatchRenderAsync |
enqueue_batch_render |
enqueue_batch_render |
| Poll a job | get_job_status |
getJobStatus |
getJobStatus |
GetJobStatusAsync |
job_status |
get_job_status |
| Validate | validate |
validate |
validate |
ValidateAsync |
validate |
validate |
| List templates | get_templates |
getTemplates |
getTemplates |
GetTemplatesAsync |
templates |
get_templates |
| List documents | get_documents |
getDocuments |
getDocuments |
GetDocumentsAsync |
documents |
get_documents |
| Download a PDF | download_document |
downloadDocument |
downloadDocument |
DownloadDocumentAsync |
download_document |
download_document |
| Org stats | get_org_stats |
getOrgStats |
getOrgStats |
GetOrgStatsAsync |
org_stats |
get_org_stats |
| Parse a callback | parse_callback |
parseCallback |
RenderCallback.parse |
RenderCallback.Parse |
Pagr.parse_callback |
pagr::parse_callback |
Note Ruby drops the get_ prefix throughout (client.templates, not client.get_templates) — an accessor doesn’t announce itself as a getter in Ruby.
Related articles
Section titled “Related articles”- Configure the client — base URL, timeouts, retries, key rotation, and client lifetime.
- API Overview — the API the SDKs wrap.
- Choose a rendering shape — all four rendering shapes.
- Handle errors and retries — the exception tree, per language.
- Webhooks — async job callbacks the SDKs parse for you.
