Skip to content

Rendering ​

Rendering reports, jobs and their results, report links and the viewer's sessions.

POST /v1/render ​

Render a template.

Renders a published template (at commit, or the tip), a draft (draft: true, needs template.write) or an inline document. A preview (layout or png) with no datasets needs only template.read; anything else needs report.render.

Datasets come from the data plan. Supplied datasets are used as given (for a sourced dataset only under data.suppliedData: allow). A sourced dataset is fetched live from the version the published template pinned, through the environment's connection, when the caller has report.render and data.live, isn't an agent (unless agents.liveData) and isn't an MCP tool, and the copy is published or data is live. Everyone else gets the samples: the document's, else the source's, and so does every dataset left out (the engine's options.samples). data: sample always uses samples; data: live fails with 403 where live data isn't allowed. A render with live data uses no samples at all: a required dataset that is neither supplied nor fetched fails with 422 bind/missing-dataset (RFML §10.1). Fetches run in parallel under data.maxBytes (else the data limit) and fail closed: a cap is 413, a missing connection binding 409, a failed source 502 with a message that names no connection details, bad sourceParams 422 with bind/source-param diagnostics. X-Data-Origins says where each sourced or supplied dataset came from (name=live;source@version, name=sample, name=supplied).

Data over rfml.limits.dataBytes (or the engine's default) is refused with 413 before the engine is called. Engine limit errors (504 limit, 413 too-large) are passed on with their diagnostics; there is never partial output. PDF renders are stored (X-Report-Id).

html-paged and html-flow (RFML §17.5) are served under a strict Content-Security-Policy (no scripts, default-src 'none', inline styles and data: images only, sandbox, framed only by this host), with X-Content-Type-Options: nosniff and Cache-Control: no-store. Their fonts come from GET /v1/fonts/{sha}, which font-src allows ('self' and <origin>/v1/fonts/, the origin being publicURL or the request's). With options.embedFonts the faces are embedded as data: URLs instead (font-src data:), and the HTML is sent as an attachment that needs nothing else: the Export dialog's download.

report() links (RFML §17.6.4) in PDF, DOCX, XLSX and HTML from here are signed by the engine under a grant for the environment, valid for viewer.linkTTLSec: <origin>/l/v2.<grant>.<target>.<mac>, followed through GET /l/{token}. With the viewer off they are left out (output/link-unresolved).

The engine runs only the RFML extensions (§23) the environment allows (config extensions); others render as placeholders.

Request body (application/json)

FieldTypeRequiredDescription
templateIdstring
templateDocument
environmentstring
commitstring
draftboolean
formatpdf, docx, xlsx, html-paged, html-flow, layout, png, svgDefault "pdf".
dataauto, sample, liveWhere sourced datasets the request doesn't supply come from. Default "auto".
datasetsobjectData per dataset name.
paramsobject
contextobject
context.nowstringRFC 3339 with an offset; defaults to the current time.
context.localestring
context.timezonestring
optionsobject
options.errorPolicyfail, continue
options.schemaModestrict, lenient, off
options.layoutDetailpages, boxes, text
options.pagesstringPage ranges, e.g. 1-3,7; png renders one page.
options.dpiinteger
options.embedFontsbooleanHTML only: embed the fonts and send the HTML as a download that works offline.
options.pdfobjectoutputs.pdf settings (RFML §17.1) for this render, with the spec's names (standard, tagged, compress, embedFonts, metadata, attachments); each property given replaces the document's.
options.pdf.standardnone, PDF/A-2b, PDF/A-3b, PDF/UA-1, PDF/A-2a+UA-1
options.pdf.taggedboolean

Responses

StatusDescriptionBody
200PDF, DOCX, XLSX, HTML, PNG or SVG bytes (X-Content-Hash, X-Rfml-Pages, X-Rfml-Diagnostics; PDF, DOCX and XLSX as attachments), or the layout model as JSON
403Role or environment denied, or the data policy refused supplied or live data
404The template is in the trash: {error: "in-trash", message, item}. item, the trash item's id, is there only for callers with template.read.object
409A source's connection has no binding for the environment
413The data is over the limit, or a source passed its cap
422The engine rejected the change or the document has errors: {error:"invalid", message, diagnostics}. A lock violation is an edit/locked diagnostic.
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object
501The engine has no writer for the format yet (not-implemented, with an output/not-implemented diagnostic)
502A data source failed
504The render stopped at a security/limit

GET /v1/fonts/{sha} ​

A font rendered HTML uses.

The sfnt bytes (TTF or OTF) of a face by the lower-case hex SHA-256 the engine's HTML refers to it by (@font-face src, under options.fontBase /v1/fonts/): a bundled font by its manifest hash, or a template's font asset (a WOFF2 asset by the hash of its decoded bytes), from what renderd holds. Needs no sign-in: rendered HTML is sandboxed, so the browser fetches its fonts cross-origin and without cookies; the hash is the capability. Served with Cache-Control: public, max-age=31536000, immutable, an ETag, nosniff and Access-Control-Allow-Origin: *. Anyone who has a face's hash can read it, so a template's font assets need a licence that allows web use.

Parameters

NameInTypeRequiredDescription
shapathstringyes

Responses

StatusDescriptionBody
200The font
304Not modified (If-None-Match)
400Not a lower-case hex SHA-256
404No font has that hash here
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/reports/{reportId} ​

Download a stored report.

Parameters

NameInTypeRequiredDescription
reportIdpathstringyes

Responses

StatusDescriptionBody
200Bytes
404discard stores nothing
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/jobs ​

The caller's jobs, newest first.

Parameters

NameInTypeRequiredDescription
statusqueryqueued, running, done, error, cancelled

Responses

StatusDescriptionBody
200Jobsobject
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

200 body (application/json)

FieldTypeRequiredDescription
jobsarray of Job

POST /v1/jobs ​

Render one template many times, in the background.

Queues a batch render of the published template: one render for each run, with that run's datasets and params, as the caller (looked up again, with the roles they have then, before each run). Jobs are kept in the template store, so they survive restarts, and carry on from the first run that hasn't rendered. A render that may pass later (the engine down, a timeout, a 429 or 5xx other than a render limit) is retried with a backoff, up to jobs.maxAttempts; any other failure ends the job as error. Finished jobs are kept for jobs.retentionHours, and a job that waits jobs.queueTimeoutHours to start ends as expired. With an Idempotency-Key, the caller's next submission with the same key, while the first job is kept, gets that job back (202, Idempotent-Replayed: true) instead of queueing another. With a callbackUrl, reportd POSTs to it once the job has finished (see callbacks).

Parameters

NameInTypeRequiredDescription
Idempotency-KeyheaderstringMakes the submission safe to repeat, such as after a timeout. 1 to 255 printable ASCII characters, chosen by the caller (a UUID, say), and the caller's own: other callers may use the same key. It must come with the same request each time.

Request body (application/json)

FieldTypeRequiredDescription
templateIdstringyes
environmentstring
formatpdf, docx, xlsx, layout, png, svg, html-paged, html-flowDefault "pdf".
runsarray of objectyesAt most jobs.maxRuns (default 1000).
runs[].datasetsobject
runs[].paramsobject
callbackUrlstring (uri)Called once the job has finished. An absolute http(s) URL, without credentials or a fragment, on an origin listed in the server's jobs.webhooks.endpoints.

Responses

StatusDescriptionBody
202The job, queued; or, for a repeated Idempotency-Key, the job it first made, as it is nowJob
400bad-request (no templateId, no runs, an unknown format, or an Idempotency-Key that isn't 1 to 255 printable ASCII characters) or callback-not-allowed (a callbackUrl that isn't on a configured endpoint)
404The template is in the trash: {error: "in-trash", message, item}. item, the trash item's id, is there only for callers with template.read.object
422idempotency-key-reused (the caller used this Idempotency-Key for a different request)
429The render rate limit (TooManyRequests), or too-many-runs (more than jobs.maxRuns) or too-many-jobs (the caller already has jobs.maxQueued unfinished jobs)

GET /v1/jobs/{jobId} ​

Job status.

Only its owner, or someone with user.manage, sees a job; anyone else gets 404.

Parameters

NameInTypeRequiredDescription
jobIdpathstringyes

Responses

StatusDescriptionBody
200JobJob
404No such job, or not the caller's
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

DELETE /v1/jobs/{jobId} ​

Cancel a job.

A queued job is cancelled at once; a running one after the run it is rendering.

Parameters

NameInTypeRequiredDescription
jobIdpathstringyes

Responses

StatusDescriptionBody
202The job, cancelled or to be cancelled after its current runJob
404No such job, or not the caller's
409The job has already finished
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

POST /v1/view/sessions ​

Open a report in the interactive viewer.

Opens a view session (RFML §17.6) on a published report (or a draft, with template.write) for the caller, who needs report.view in the environment. The parameters are checked against the report's (422 invalid-params, with bind/param diagnostics), then the data is fetched once through the data plan: live where the caller has data.live (agents and MCP tools get samples), else the samples. datasets supplied by the caller (who then needs report.render, as for a render) are used as they are and never fetched again; for a sourced dataset only under data.suppliedData: allow (else 403 data-policy), and within viewer.maxBytes (else 413). Every later render of the session uses that data; changing the view state never queries a source again.

data.live is checked again on every call to the session. When the caller no longer has it, the live data the session holds is dropped and those datasets get their samples; a session opened with data: live is ended instead (403 data-policy, then 404).

The session's data is capped at viewer.maxBytes (413, security/limit), all sessions' at viewer.maxTotalBytes (503 viewer-full, Retry-After). Data past viewer.memoryBytes is spilled to disk, encrypted with a key that exists only in memory. A session not used for viewer.sessionTTLSec (default 30 minutes) is wiped, and so are a person's sessions when they log out. A session belongs to the principal that opened it; anyone else gets 404.

Served only when viewer.enabled; otherwise 404 viewer-disabled.

Request body (application/json)

FieldTypeRequiredDescription
templateIdstringyes
environmentstring
commitstringA published commit; defaults to the tip.
draftboolean
paramsobject
viewViewState
dataauto, sample, liveDefault "auto".
datasetsobjectData per dataset name, supplied by the caller (needs report.render).
contextobject
context.nowstringRFC 3339 with an offset; defaults to when the session opens, and stays fixed for its life.
context.localestring
context.timezonestring

Responses

StatusDescriptionBody
201The session. X-Data-Origins says where each dataset came from.ViewSession
400invalid-view-state or view-state-too-large
403No report.view in the environment, a draft without template.write, supplied data without report.render, or the data policy refused supplied or live data
404No such report, or viewer-disabled, or the template is in the trash (in-trash, see InTrash)
413The data (supplied or fetched) is over viewer.maxBytes or a source's cap
422invalid-params, or the data sources can't be called (bind/source-param)
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object
503viewer-full

GET /v1/view/sessions/{sessionId} ​

Read a view session.

Parameters

NameInTypeRequiredDescription
sessionIdpathstringyes

Responses

StatusDescriptionBody
200The sessionViewSession
404No such session for the caller, or it expired, or the template is in the trash (in-trash, see InTrash)
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

PATCH /v1/view/sessions/{sessionId} ​

Change a session's view state or parameters (setParams).

view replaces the session's view state (checked as for opening). params sets parameter values (null goes back to the default): they are checked against the report's parameters, the engine's plan evaluates each dataset's sourceParams again, and only the live datasets whose sourceParams changed are fetched again. refetched lists them; it is empty when nothing was fetched.

Parameters

NameInTypeRequiredDescription
sessionIdpathstringyes

Request body (application/json)

FieldTypeRequiredDescription
paramsobject
viewViewState

Responses

StatusDescriptionBody
200The session, with refetchedViewSession
400invalid-view-state or view-state-too-large
404No such session for the caller, or the template is in the trash (in-trash, see InTrash)
413The new data is over the cap
422invalid-params
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

DELETE /v1/view/sessions/{sessionId} ​

End a view session and wipe its data.

Parameters

NameInTypeRequiredDescription
sessionIdpathstringyes

Responses

StatusDescriptionBody
204Ended
404No such session for the caller
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/view/sessions/{sessionId}/render ​

Render a view session.

Renders the session's data with its parameters and a view state: view from the query (the viewer's URL state, compact JSON, at most viewer.maxViewStateBytes, default 8 KB) for this render only, else the session's own. Nothing is fetched. Parameter values in view state are refused (400) unless viewer.viewStateParams allows them, and then work as setParams.

HTML (html-paged, html-flow) is served under the same strict Content-Security-Policy as /v1/render, with its fonts from /v1/fonts/{sha}. Its report() links are signed link tokens (/l/{token}) for the session's environment, valid for viewer.linkTTLSec (default 7 days). PDF, DOCX and XLSX exports are attachments whose report() links the engine signs under a grant, as in /v1/render; nothing is stored.

Parameters

NameInTypeRequiredDescription
sessionIdpathstringyes
formatqueryhtml-paged, html-flow, layout, svg, png, pdf, docx, xlsx
viewquerystringView state (ViewState) as JSON.
pagesquerystring
dpiqueryinteger
layoutDetailquerypages, boxes, text

Responses

StatusDescriptionBody
200The rendered output, or the layout model
400An unsupported format, invalid-view-state or view-state-too-large
404No such session for the caller, or the template is in the trash (in-trash, see InTrash)
422The engine rejected the change or the document has errors: {error:"invalid", message, diagnostics}. A lock violation is an edit/locked diagnostic.
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object
504The render stopped at a security/limit

POST /v1/links ​

Sign a link to a published report.

Signs a link token (HMAC with viewer.linkSecret) for a report, its environment, parameter values and an optional initial view state, valid for viewer.linkTTLSec. The caller needs report.view there; the parameters are checked as they will be when the link is followed. Viewer sessions sign the report() links in their HTML themselves; this is for hosts that build links of their own (mail, portals). Tokens are signed, not encrypted: parameter values can be read.

Request body (application/json)

FieldTypeRequiredDescription
templateIdstringyes
environmentstring
paramsobject
viewViewState

Responses

StatusDescriptionBody
201The linkobject
400invalid-view-state
403No report.view in the environment
404The report isn't published there, or viewer-disabled, or the template is in the trash (in-trash, see InTrash)
422invalid-params
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

201 body (application/json)

FieldTypeRequiredDescription
tokenstringyes
urlstringyes/l/{token}.
expiresAtstring (date-time)yes

GET /l/{token} ​

Follow a report() link.

Checks the token's signature and expiry, that the reader has report.view in its environment and that its parameters fit the report, then opens a view session for the reader and redirects (303) to the viewer at viewer.path + session id (default /view/). Readers sign in first: the session cookie from /v1/login works here. A browser that isn't signed in (no Authorization header, Accept with text/html) is sent (303) to the studio's sign-in page, viewer.signInPath (default /login), with next=/l/{token}; the studio returns there after signing in. API callers get 401. A browser whose link can't be opened is sent (303) to the viewer app's page for why, viewer.path + ?error=invalid-link (400), forbidden (403), not-found (404), link-expired (410) or invalid-params (422); API callers get those statuses with JSON errors.

Tokens are v1 (from POST /v1/links and the viewer's HTML) or v2 (signed by the engine in exported documents, under a grant for one environment and expiry).

Parameters

NameInTypeRequiredDescription
tokenpathstringyes

Responses

StatusDescriptionBody
303To the viewer, to the sign-in page, or (a browser) to the viewer's error page
400invalid-link (malformed, altered or signed with another key)
401Not signed in
403No report.view in the link's environment
404The linked report isn't published, or viewer-disabled, or the template is in the trash (in-trash, see InTrash)
410link-expired
422invalid-params
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object