Skip to content

Configure the client

The defaults are chosen to be right for most callers, so you can construct a client with nothing but an API key. This page covers the handful of settings worth changing deliberately — and the two lifetime rules that matter in production.

Setting Default Change it when
Base URL the hosted Pagr API You’re targeting another instance (a local dev server, a private deployment).
Request timeout 30 s A single render or download legitimately takes longer. Prefer a per-call override.
Max retries 2 (3 attempts) on idempotent GETs You want retries off (0), or you’re behind a flaky network and want more.
API key — (required) Rotation, or per-tenant keys in a multi-tenant service.

Backoff internals — 500 ms first step, 8 s ceiling, 60 s Retry-After ceiling, full jitter — are the same in every SDK and are not intended as tuning surface. See Handle errors and retries.

Terminal window
# There's no client to configure — set the base URL and header per request.
export PAGR_BASE_URL="https://pagr-prd-api-public.azurewebsites.net"
export PAGR_API_KEY="pagr_prod_xxxxxxxx"
curl "$PAGR_BASE_URL/v1/fonts" -H "Authorization: Bearer $PAGR_API_KEY" \
--max-time 30 --retry 2 --retry-connrefused

--max-time and --retry are the rough equivalents of the SDKs’ timeout and retry settings. Note curl --retry will retry a POST too — the SDKs deliberately don’t.

This is almost always the right knob, and the one most people miss. The client-wide default of 30 seconds is deliberately tight — it fails fast on a stalled connection — but it’s below the server’s 60-second per-document render budget. So a heavy render or a large download can time out on a request that would have succeeded.

Override it for the specific call rather than inflating the default for everything:

# A heavy render, close to the server's 60s budget
result = await client.render(template_id, data, timeout=90)
# A large batch
batch = await client.render_batch(template_id, documents, timeout=300)
# A big PDF over a slow link
pdf = await client.download_document(doc_id, timeout=120)

Every SDK lets you swap the key on a live client, so you don’t have to tear down a connection pool to rotate a credential — or to serve multiple tenants from one client.

client.set_api_key("pagr_prod_new_key")

set_api_key is not thread-safe against in-flight requests in the sense of ordering: a request already on the wire uses the old key, and there’s no way to know which key a concurrent request used. Rotate during a quiet moment, or drain first.

Two rules, and they differ by SDK because the underlying transports differ:

SDK Reuse the client? Dispose?
Python Yes — owns an httpx connection pool async with, or await client.aclose() on shutdown
C# Yes — critical. Owns a pooled HttpClient using, or Dispose() on shutdown
Java Yes try-with-resources, or close()
C++ Yes RAII — nothing to call
TypeScript Yes (cheap either way) Nothing to dispose — no pooled connection
Ruby Yes Nothing to dispose — the block form is scoping sugar only
SDK Style
Python Async only. Every method is a coroutine; use await and async with.
TypeScript Async only, on native fetch.
C# Async only. Every method has an Async suffix and takes a CancellationToken.
Java Both. PagrApiClient is synchronous; PagrAsyncApiClient mirrors every call returning CompletableFuture<T>.
Ruby Synchronous only.
C++ Both. Every method has a sync form and a *_async form returning std::future<T>.
  • Don’t set base_url unless you mean it. It defaults to the hosted API in every SDK. Pointing at the wrong instance is a confusing failure mode — usually surfacing as PagrDecodeError when a proxy or login page answers instead of Pagr.
  • Trailing slashes are trimmed for you, so http://localhost:5110/ and http://localhost:5110 behave identically.
  • max_retries is client-wide, with no per-call override. If one code path needs a different policy, build a second client.
  • max_retries=0 doesn’t disable anything for writes — writes were never retried in the first place.
  • Cancellation is C#-only as a first-class concept. Every C# method takes a CancellationToken. Elsewhere, cancel by tearing down the surrounding task or future.
  • The Python webhook receiver is optional and dev-only. LocalWebhookReceiver needs the webhook extra (pip install pagr[webhook]) and is meant for local development, examples and tests — not production traffic.
  • Probe once at start-up, not per call. get_status is a cheap readiness check; calling it before every render just doubles your request count against the read rate limit.