Skip to content

Validate before rendering

Validation runs the same checks a render would, but produces no document and consumes no credit. Use it wherever a human can still fix the input — a form submission, an import step, a CI check on your payload builder — so the failure arrives before the charge.

  • A published template version to validate against.
  • An API key. Either environment works: validation applies no environment-specific gate, so test and production keys produce identical results here. (Rendering doesn’t — see the severity gate below.)
  1. Send your data to the validate endpoint.

    Same payload shape as a render: an array of documents. The SDKs let you pass a single object and wrap it for you.

  2. Read is_valid for the yes/no answer.

    It’s true when there are no issues — the same gate a production render applies. See step 4 if you only ever render with test keys.

  3. Read errors and warnings for the detail.

    Each issue carries a type you can switch on, a human description, an optional element_id pointing at the template element, and a document_index for batches.

  4. If you only ever render with a test key, check errors instead.

    Test/preview rendering tolerates Warning-severity issues (it degrades to a marker or placeholder instead of blocking), so is_valid is stricter than it needs to be there. errors.empty? is the narrower, test-mode-accurate check.

  5. Render only what passed.

    Nothing is charged for a validation, so a reject-and-report loop costs only the request.

Validate, report anything wrong, and render only what’s is_valid — since is_valid is itself the production gate, that’s exactly the check a production render needs.

Terminal window
curl -X POST "https://pagr-prd-api-public.azurewebsites.net/v1/render/8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90/validate" \
-H "Authorization: Bearer pagr_test_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "documents": [ { "Title": "Acme Q3 Invoice", "Amount": 42 } ] }'
# → { "issues": [] } ⇒ is_valid: true, safe to render anywhere
# → { "issues": [ { "severity": "Warning", … } ] }
# ⇒ is_valid: false — renders on a test key, BLOCKED on a production key

is_valid is the production gate: it’s false as soon as any issue is Warning or Error severity, because production blocks on both.

Severity Test / preview render Production render is_valid says
Information renders renders valid
Warning renders blocked invalid
Error blocked blocked invalid

Read the middle row twice — it runs the other way round from what you’d expect. A payload with only warnings comes back is_valid: false, but it still renders fine against a pagr_test_ key; it only fails against a pagr_prod_ key. Typical culprits are all Warning-severity: MissingBinding, UnresolvedImage, UnresolvedFont, InvalidColor. These warnings can be highlighted in the test render.

Pass a list and every issue comes back with the document_index it belongs to, in one flat array. issues_for(i) slices it per document — and includes batch-wide issues (document_index is null), so a per-document report never silently drops a batch-level cause.

documents = [
{"Title": "Acme Q3 Invoice", "Amount": 42},
{"Title": "Acme Q4 Invoice"}, # missing Amount
]
check = await client.validate(TEMPLATE_ID, documents)
for i, doc in enumerate(documents):
issues = check.issues_for(i)
print(f"[{i}] {'OK' if not issues else f'{len(issues)} issue(s)'}")
for issue in issues:
print(" ", issue)
clean = [d for i, d in enumerate(documents) if not check.issues_for(i)]
if clean:
await client.render_batch(TEMPLATE_ID, clean)
  • Validation is not a guarantee. It catches data-shape and binding problems, not everything: a document can still hit RenderTimeout or RenderLayoutDegraded at render time, because those depend on how the content actually lays out. Treat validation as a cheap filter, not a contract.
  • Validate against the version you’ll render. With no version argument both calls target the latest published version — which can change under you between the two requests. Pin the same explicit version on both if the window matters.
  • sampleData is a free known-good payload. Fetch it from Get a version and validate it to confirm your plumbing works before debugging your own data.
  • Batch-wide issues have document_index: null. Don’t filter them out by looking for a matching index — use issues_for(i), which folds them in.
  • severity fails closed. An unrecognised severity string is parsed as Error by every SDK, so a value a newer server introduces can never let a document through that was meant to be blocked.
  • Validation costs a write permit, not a render credit — it counts against the render rate-limit category. Validating a 1,000-document batch is one request; a loop of 1,000 single validations is 1,000. See Errors → Rate limits.