API reference
The base URL is wherever you installed the service (Deployment), for example http://reportdesigner:8080. All request bodies are
application/json. The machine-readable specification is GET /openapi.json (OpenAPI 3.1). Import it into Postman or your client generator.
| Method and path | Purpose |
|---|---|
POST /api/v1/render | Render a template with data to PDF |
POST /api/v1/validate | Validate a template without rendering |
GET /api/v1/fonts | List the fonts available on the server |
GET /v1/info | Name, version, license and designer SDK of this installation |
GET /health, GET /ready | Liveness and readiness probes |
GET /openapi.json | OpenAPI document |
GET /, GET /help/, GET /sdk/… | This portal, the user guide and the standalone designer bundle: static pages, not part of the API or openapi.json (see Guides served by the service) |
Endpoints on this server
Download openapi.jsonRendered from this server's GET /openapi.json (OpenAPI 3.1), so it always matches the installed version. Click an endpoint for its parameters, body and responses.
Loading the API description…
POST /api/v1/render#
The template and the data are separate parameters:
{
"template": { "version": 1, "name": "VICS BOL", "page": { "...": "..." }, "bands": { "...": "..." } },
"data": { "bolNumber": "BOL2026102001", "shipFrom": { "name": "Acme Distribution" }, "lines": [ ] },
"options": { "filename": "BOL2026102001", "disposition": "attachment", "locale": "en-US", "now": "2026-10-20T12:00:00Z" }
}
| Field | Required | Notes |
|---|---|---|
template | yes | Template JSON from the designer (getTemplate() or the save event) |
data | no | Any JSON value, default {}. It is the root scope for {{ expressions }}. Preview text ({{ x | sample }}) is ignored |
options.filename | no | Download name without .pdf (max 200 characters, sanitized). Default: the template's name |
options.disposition | no | inline (default, opens in the browser) or attachment (download) |
options.locale | no | BCP-47 tag that overrides the template's document language (number, date and currency formatting, CJK font choice), e.g. fr-FR |
options.now | no | ISO date-time used for {{ now }} / today(), for reproducible output (tests, reprints). Doesn't affect license expiry |
Response 200: application/pdf, with:
| Header | Meaning |
|---|---|
Content-Disposition | inline; filename="…pdf"; filename*=UTF-8''… (or attachment) |
X-Page-Count | Number of pages |
X-Render-Warnings | Number of non-fatal problems: a missing image, an unknown font (falls back to Arimo), an expression error (prints empty), an invalid barcode value |
X-License-Status | valid, trial, expired, missing or invalid. Anything but valid means a watermarked PDF (License). Sent on every response, not only renders |
X-Request-Id | Correlates with the server log. A well-formed incoming X-Request-Id (≤ 64 chars, [A-Za-z0-9._-]) is echoed |
?format=json returns the PDF base64-encoded together with the warning texts, which helps when testing templates:
{ "pdfBase64": "JVBERi0xLjcK…", "pageCount": 3, "warnings": [ { "elementId": "bc1", "message": "Barcode '12' (ean13): …" } ], "license": "trial", "watermark": "trial" }
license is the same value as X-License-Status; watermark is the mark on every page: trial, expired, unlicensed, or null (none).
Rendering is never refused for licensing reasons: without a valid paid license the request still succeeds and the PDF is watermarked.
Batches. If the template sets recordsPath (the author's Records → One copy per setting, e.g. "shipments"), send { "shipments": [ {…}, {…} ] }. The
service renders one copy per record into one PDF, with page numbers restarting for each copy.
POST /api/v1/validate#
Request { "template": { … } }. Response:
{ "valid": false, "issues": [ { "path": "bands.body.elements[3].dataPath", "message": "Expected an expression", "severity": "error" } ] }
Structural checks, value bounds and expression syntax, without rendering. Use it on template save. severity: "warning" issues don't prevent rendering.
A call to a function that doesn't exist is a warning (Unknown function 'formatCurency' (did you mean formatCurrency?)): the template renders, and
that placeholder prints empty.
GET /api/v1/fonts#
Returns the bundled font families (family, aliases such as Arial → Arimo, category, variants) plus the fonts installed from RD_FONT_DIR.
GET /v1/info#
{
"name": "reportdesigner",
"version": "1.0.0",
"license": {
"status": "valid", "trial": false, "source": "configured", "watermark": null, "products": ["reportdesigner"],
"licensee": "Acme Corp", "id": "LIC-0007", "expires": "2027-12-31", "daysLeft": 437, "maxInstances": 2, "instanceId": "rd-3f9c0a1e…"
},
"sdk": { "version": "1.0.0", "url": "/sdk/1.0.0/", "latest": "/sdk/latest/" }
}
license field | Meaning |
|---|---|
status | valid, expired (past the end of its expiry day, UTC), missing (no license key) or invalid (not accepted) |
trial | true for a trial license, including the evaluation license bundled with the release |
source | configured (LICENSE_KEY / LICENSE_FILE) or bundled (the evaluation license of the release); absent when missing |
watermark | What rendered PDFs carry now: trial, expired, unlicensed, or null (none) |
licensee, id, expires, daysLeft | From the license; expires and daysLeft are absent for a trial without expiry |
maxInstances, instanceId | Installations the license covers, and this installation's id (License) |
The answer is the same for everyone. It never contains the license key, and never why a key was not accepted (that is in the server log).
GET /health, GET /ready#
/healthreturns{ "status": "ok", "version": "1.0.0", "license": "trial", "sdk": { … } }(licenseas inX-License-Status). Use it for liveness./readyreturns{ "status": "ready", "inFlight": 0, "capacity": 40 }, and 503 while shutting down. Use it for readiness and load-balancer health.
Errors#
Every error is an RFC 9457 application/problem+json body:
{ "type": "/problems/invalid-template", "title": "Invalid template", "status": 400, "detail": "The template failed validation",
"instance": "/api/v1/render", "requestId": "8c88…", "issues": [ { "path": "page", "message": "Required", "severity": "error" } ] }
| Status | type | When | Client action |
|---|---|---|---|
| 400 | invalid-request | Malformed JSON, missing template, a bad option, an unknown locale | Fix the request. Don't retry |
| 400 | invalid-template | The template failed validation (issues lists the problems) | Fix the template. Don't retry |
| 404 | not-found | Unknown route | — |
| 413 | payload-too-large | Body over RD_BODY_LIMIT_MB | Smaller images, or raise the limit |
| 415 | unsupported-media-type | Body isn't JSON | Send Content-Type: application/json |
| 422 | render-failed | Valid template, but rendering failed (e.g. more pages than RD_MAX_PAGES) | Check detail. Don't retry unchanged |
| 422 | render-memory-exceeded | The render needed more than RD_WORKER_MAX_MEMORY_MB | Reduce images or data, or raise the limit |
| 499 | client-closed | The client disconnected; the render was aborted | — |
| 503 | server-busy | All workers busy and the queue full, or shutting down | Retry after the Retry-After header (seconds) |
| 504 | render-timeout | Over RD_RENDER_TIMEOUT_MS (queue time included) | Retry once. If it persists, simplify the document or raise the timeout |
| 500 | internal-error | A bug. Details are in the server log under requestId | Retry once. Report it with the requestId |
Limits#
| Limit | Default | Setting |
|---|---|---|
| Request size | 20 MB | RD_BODY_LIMIT_MB |
| Pages per document | 2,000 | RD_MAX_PAGES |
| Render time (including queue) | 60 s | RD_RENDER_TIMEOUT_MS |
| Memory per render | 512 MB | RD_WORKER_MAX_MEMORY_MB |
| Pixels per image / image bytes per render | 25 M / 50 MB | RD_MAX_IMAGE_PIXELS / RD_MAX_IMAGE_BYTES |
| Table rows per table | 100,000 (then truncated with a warning) | — |
| Template bounds | Font size ≤ 1000 pt, page ≤ 14,400 pt, Min. rows ≤ 10,000, ≤ 20,000 elements | — (validation errors) |
| Expression results | ≤ 1,000,000 characters per text | — |
See Render API clients for code in several languages.