Skip to content

Server and setup ​

Health and readiness, what the server is and runs, its events, first-run setup, settings, the licence and the branding.

GET /health ​

Process health, including the engine.

Responses

StatusDescriptionBody
200OK
503The engine is down

GET /livez ​

Liveness, for a process supervisor.

200 while reportd answers requests; it checks nothing else.

Responses

StatusDescriptionBody
200Alive

GET /readyz ​

Readiness, for a load balancer.

200 when reportd can do its work (the engine answers, at least one renderd instance when renders are spread, and Postgres on state.driver postgres), else 503.

Responses

StatusDescriptionBody
200Ready
503Not ready; the body says why

GET /v1/setup ​

Whether reportd needs setting up, and with the code, its steps.

In setup mode (no user, no admin.password), without X-Setup-Code the answer is {required: true}. With the right code it adds the steps (admin, licence, address, environments, storage, engine, summary), the answers they start from (values, without a password), the settings setup offers or shows (settings: path, value, origin, fixed, offered; a setting the config file or a REPORTD_ variable sets is fixed), settingsFile and the docs to read on. A wrong code is 401 setup-code and counts in the sign-in throttle by address. Once reportd is set up, 404.

Parameters

NameInTypeRequiredDescription
X-Setup-CodeheaderstringThe one-time setup code from reportd's log or dataDir/setup-code; dashes, spaces and case don't matter.

Responses

StatusDescriptionBody
200Setup is needed
401Wrong setup code (setup-code)
404reportd is set up
429Too many wrong codes from this address, or the signin rate limit; Retry-After says how many seconds to wait

POST /v1/setup ​

Apply setup.

Takes {code, values} with every step's answers (username, password, adminMfa, licence, publicURL, environments, and updateCheck, which turns the version check off when false; left out, updates.check stays as it is), runs every step's checks again and, when none fails, applies them in one go: the administrator into the auth store (never a file), the other settings into dataDir/settings.yaml, setup.complete into the audit log; the code file is deleted. The answer signs the administrator in as POST /v1/login does ({token, principal}, the session cookie) and lists the settings written; with restart, reportd starts again on them, and requests made meanwhile wait.

Request body (application/json)

FieldTypeRequiredDescription
codestringyes
valuesobjectyes

Responses

StatusDescriptionBody
200Set up and signed in
401Wrong setup code (setup-code)
404reportd is set up
422A check failed (invalid); results holds every step's checks, and nothing was applied
429Too many wrong codes, or the signin rate limit

POST /v1/setup/check ​

Run one setup step's checks, saving nothing.

Takes {code, step, values} and answers {step, ok, checks}: each check has a name, a level (ok, info, warning, error) and a message; an error stops setup. The engine step adds preview, page 1 of an example rendered to PNG; the summary step answers summary: what will be written and where, the settings, the fixed settings and the docs.

Request body (application/json)

FieldTypeRequiredDescription
codestringyes
stepstringyes
valuesobjectyes

Responses

StatusDescriptionBody
200The step's checks
401Wrong setup code (setup-code)
404reportd is set up
429Too many wrong codes, or the signin rate limit

GET /v1/events ​

What reportd tells signed-in browsers about itself, as server-sent events.

A text/event-stream for any signed-in principal, including a limited session, so the studio and the viewer carry on through a restart instead of finding it out from a failed request.

  • hello on connecting: {run, version}. run is new each time reportd starts again: a browser that sees another run reads again what the settings change, and reloads only when version is another (after saving what it holds).
  • restarting before reportd stops: {reason, paths?}. reason is settings (it starts again on new ones; paths are those this node changed) or stop. The stream then ends; EventSource connects again by itself and waits at the listener until reportd is back.

A comment every 25 s keeps proxies from closing a quiet stream.

Responses

StatusDescriptionBody
200The streamstring
401Not signed in
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/info ​

The versions of reportd and its engine, and the latest release.

version is reportd's (the release, such as 1.4.0); engine is the engine's name and version from its own /v1/info, or null while it doesn't answer. Any signed-in principal may read it, including a session limited by password-change-required or mfa-enrolment-required, and on an install that needs a licence. The studio shows it in the Account and Licence dialogs.

latest is the latest release the version check knows of (updates.check, docs/operations/updates.md): once a day reportd asks updates.releases, and keeps the answer. It is null while the check is off, before it has worked, and while it fails (no internet access, or no release published yet). newer says it is newer than this reportd; the studio then tells administrators.

Responses

StatusDescriptionBody
200{version, engine: {name, version} | null, latest: {version, url, checkedAt, newer} | null}object
401Not signed in
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
versionstringyes
engineobjectyes
engine.namestringyes
engine.versionstringyes
latestobjectyes
latest.versionstringyes
latest.urlstringThe release's page, with its notes; left out when the answer had no http(s) one.
latest.checkedAtstring (date-time)yesWhen the check that found it ran.
latest.newerbooleanyesIt is newer than this reportd.

GET /v1/licence ​

The licence, the entitlements and their use.

What the install may do (docs/operations/licence.md): the phase, the features on and off, and each limit with Community's and the use reportd counts. Needs studio.access. Administrators (user.manage) also see the licensee, the id, the soft binding, the history, the warnings and use: the templates frozen now, and in the transition or as a licence ends, what Community would freeze and which configured features need a licence. Elsewhere the licence refuses with LicenceRequired (403) and LicenceLimit (409) bodies, and GET /v1/templates marks frozen templates with frozen: true.

Responses

StatusDescriptionBody
200The licence's stateLicence
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

PUT /v1/licence ​

Install a licence.

Takes the licence's text block, as text, or as {"licence": "<block>"}, checks it against the public keys this build embeds, keeps it (dataDir/licence, or the shared state) and applies it at once. Audited as licence.install <id>. Needs user.manage.

Request body (text/plain): string

Request body (application/json)

FieldTypeRequiredDescription
licencestringyes

Responses

StatusDescriptionBody
200The licence's state, as GET answersLicence
409The licence setting (REPORTD_LICENCE) fixes the licence:
422The licence is refused, with the reason in message: it doesn't verify, is signed by a key this reportd doesn't know, is incomplete, hasn't started, or has lapsed:
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/licence ​

Remove the licence.

Back to Community's limits. The history keeps the licence, so what is in use carries on as after a lapse. Audited as licence.remove <id>. Needs user.manage.

Responses

StatusDescriptionBody
200The licence's state, as GET answersLicence
404There is no licence installed
409The licence setting fixes the licence:
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/environments ​

Environment names in pipeline order.

Responses

StatusDescriptionBody
200Environments
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/extensions ​

The RFML extensions an environment's renders may run.

The extension namespaces (RFML §23, "x-acme") the operator allows in the environment (config extensions: its own list and "*"), sorted. Renders there run only these; any other extension renders as a placeholder with ref/unknown-extension. The designer shows the others as disabled. Needs template.read in the environment; 404 for an environment that doesn't exist.

Parameters

NameInTypeRequiredDescription
environmentquerystring

Responses

StatusDescriptionBody
200
404No such environment
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/schema ​

RFML JSON Schema, generated by the engine.

Parameters

NameInTypeRequiredDescription
partquerystringdocument (default), ops, node:<type>, library or source-def

Responses

StatusDescriptionBody
200JSON Schema (2020-12) with x-rfml-kind annotations
404Unknown part or element type
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/guide ​

Guide to how this report server works (environments, drafts, edits, publishing, rendering).

Responses

StatusDescriptionBody
200Guide (Markdown)
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 /mcp ​

Model Context Protocol (streamable HTTP, JSON responses).

Protocol 2025-06-18, falling back to 2025-03-26 and 2024-11-05. Requests after initialize carry MCP-Protocol-Version. Notifications get 202 with no body. Tools forward to this API as the caller, and wrap document, data, render and diagnostic content in <untrusted> envelopes. Tool calls are rate limited per principal (mcp.rateLimitPerMinute).

Responses

StatusDescriptionBody
200JSON-RPC result
202Notification accepted
400Unsupported MCP-Protocol-Version, or not a JSON-RPC request
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/settings ​

Every setting, and which the administration site can change.

Each setting by its YAML path: its value as reportd config show shows it (secrets masked), its origin, whether the site can change it, and the licence feature it needs. A list or a map is one setting. A setting on the editable list that the config file or a REPORTD_ variable sets is fixed. shared says a change restarts every node (state.driver postgres). Needs settings.manage, which agent keys never have.

Responses

StatusDescriptionBody
200The settingsobject
403The caller lacks settings.manage
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
settingsarray of Settingyes
sharedbooleanyes
changeablebooleanyesfalse when this reportd can't change its settings.
undoneobjectThe last change reportd couldn't start on (it failed before it listened), which it put back before starting on the settings it had. Gone once another change is made.
undone.pathsarray of stringyes
undone.reasonstringyes
undone.atstring (date-time)yes

PATCH /v1/settings ​

Change settings, and restart.

{path: value} changes, null putting a setting back to what the config file or the default gives. Each must be editable and not fixed; a secret takes only a secret: reference, and a masked value is refused. The whole config is resolved and checked with the changes, and the licence must cover them, before anything is written: to dataDir/settings.yaml, or the shared state's settings table. Audited as settings.change with the paths, never the values. A value a setting with options doesn't take, and a negative number, are refused (422 invalid). reportd then restarts in-process to apply them (every node on the shared state), telling GET /v1/events streams first. If it can't start on them, it puts the settings back, starts on those, and GET says so (undone).

Request body (application/json): object

Responses

StatusDescriptionBody
200The paths written, and whether reportd restartsobject
400Not an object of changes
403The caller lacks settings.manage, or the licence doesn't cover the change:
409The config file or the environment fixes the setting: {error: setting-fixed, path, origin, message}. Or another change was written meanwhile:
422{error, path, message}: not-editable (not on the list), secret-literal (a secret that isn't a secret: reference), masked (a masked value sent back), or invalid (the config wouldn't be valid with it)
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
503This reportd can't change its settings

200 body (application/json)

FieldTypeRequiredDescription
settingsarray of stringyes
restartbooleanyes
sharedbooleanyes

GET /v1/branding ​

The branding people see.

Public, so the sign-in pages can draw with it; signed out, it counts in the public rate-limit class. It answers the stored branding while a licence grants branding, or a licence this install had did (a lapse freezes it), and the defaults (Reportforge's) otherwise. It carries no editor and no licence detail.

Responses

StatusDescriptionBody
200The brandingBranding
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

PUT /v1/branding ​

Set the product name, the colours and the sign-in copy.

Needs settings.manage and a licence that grants branding now. Text is plain, an empty name or colour takes its default, and the support link is https: or mailto:. The palette is derived from the colours, and text on it must reach WCAG AA (4.5:1). Audited as branding.change with the fields that changed.

Request body (application/json): BrandingEdit

Responses

StatusDescriptionBody
200The branding nowBrandingChanged
403The caller lacks settings.manage, or no licence grants branding now (LicenceRequired)
422{error: invalid, field, message}, or {error: contrast, field: colours, message, checks} when text on the palette would fall below 4.5:1
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/branding ​

Put the branding back to the defaults, files and all.

Needs settings.manage and the branding feature. Audited as branding.reset.

Responses

StatusDescriptionBody
200The defaultsBrandingChanged
403The caller lacks settings.manage, or no licence grants branding now
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

PUT /v1/branding/files/{name} ​

Upload the logo, the favicon or the sign-in background.

The body is the file. Its type is read from its content, never its name or Content-Type: the logo is SVG or PNG, up to 512 KB; the favicon SVG, PNG or ICO, up to 128 KB; the background PNG, JPEG or WebP, up to 2 MB. Needs settings.manage and the branding feature. Audited as branding.change files.<name>.

Parameters

NameInTypeRequiredDescription
namepathlogo, favicon, backgroundyes

Request body (application/octet-stream): string (binary)

Responses

StatusDescriptionBody
200The branding nowBrandingChanged
403The caller lacks settings.manage, or no licence grants branding now
404Not a branding file
413Larger than the file may be:
415Not a type the file may be:
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/branding/files/{name} ​

Remove an uploaded file.

The favicon goes back to the default; the logo and background to none.

Parameters

NameInTypeRequiredDescription
namepathlogo, favicon, backgroundyes

Responses

StatusDescriptionBody
200The branding nowBrandingChanged
403The caller lacks settings.manage, or no licence grants branding now
404Not a branding file, or none uploaded
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 /branding/{name} ​

A branding file.

Public. The uploaded file the branding shows, as its own type, the default favicon, or 404. Served with Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline', nosniff, and an ETag (If-None-Match answers 304). The pages show the files only through img and CSS, so a script in an SVG never runs.

Parameters

NameInTypeRequiredDescription
namepathlogo, favicon, backgroundyes

Responses

StatusDescriptionBody
200The file
304Not modified
404No such file
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