Skip to content

Templates ​

The catalog, folders, drafts and their edits, publishing, promoting, tests and approvals.

GET /v1/templates ​

List templates (id, name from meta.title, folder, where published).

Each template's published maps each environment the caller may read and the template is published in to its newest published version there: the commit and when it was made (the first entry of its history). An environment it isn't published in is left out. frozen is true for a template past the licence's limit.

Responses

StatusDescriptionBody
200Templatesobject
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
templatesarray of object
templates[].idstringyes
templates[].namestringyes
templates[].folderstringyes
templates[].frozenboolean
templates[].publishedmap of objectyes

POST /v1/templates ​

Create a template draft.

Writes revision 1 of a new draft: the RFML document and any bundle files. Only the header (rfml, id, meta.title) is checked here; the engine checks the rest on the first edit or validate. 409 when a draft of that id already exists in the environment.

With Content-Type application/vnd.rfml+xml the body is the document as RFML markup (§27), parsed by the engine (/v1/markup/parse); environment and message go in the query. Markup with syntax errors is 422 with markup/* diagnostics.

Parameters

NameInTypeRequiredDescription
environmentquerystring
messagequerystringMarkup bodies only.

Request body (application/vnd.rfml+xml): string

Request body (application/json)

FieldTypeRequiredDescription
environmentstring
documentDocumentyes
filesobjectBundle files keyed by path (samples/…, schemas/…, tests/…).
messagestring

Responses

StatusDescriptionBody
201Created, with the draft revision
409A draft already exists, or a template in the trash holds the id:
422The header is not valid
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/templates/import ​

Make a new report from a file.

The body is a report bundle's .zip (the example gallery's download, or a bundle zipped by hand; its files may sit in one top-level folder) or its template.rfml.json alone. It becomes the first draft of a new report, with the checks POST /v1/templates makes: the header, an id the trash holds, the bundle's paths and caps, and the licence's template limit. Without id, the report takes the document's id, or the first of <id>-2, <id>-3… that is free. The body is capped by limits.maxBodyBytes. Its RFML version is checked as a copy's is (POST /v1/examples/{source}/{id}/copy): an older one the engine migrates is stored migrated, and the edit says "import, RFML <from> → <to>".

Parameters

NameInTypeRequiredDescription
environmentquerystring
idquerystring
titlequerystringSets meta.title; default the document's.
folderquerystring

Request body (application/zip): string (binary)

Request body (application/json): Document

Responses

StatusDescriptionBody
201The new report's first draftobject
400Neither a bundle's zip nor a document, or a file that isn't a bundle's
409The id is taken or in the trash, the licence's template limit is reached (licence-limit), or the document needs a newer RFML version (rfml-too-new)
413Larger than limits.maxBodyBytes
422The header is not valid, or the document is of an RFML version this Reportforge can't read (rfml-unsupported)
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
idstringyes
environmentstringyes
revisionintegeryes
folderstring

GET /v1/folders ​

List catalog folders.

Responses

StatusDescriptionBody
200Folder paths
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/folders ​

Create a folder path, including its parents.

Responses

StatusDescriptionBody
201Created
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/folders ​

Move a folder to the trash, with its subfolders and templates.

One trash item takes the folder, every folder under it and every template in them, each trashed as DELETE /v1/templates/{id} would. Needs template.write, and template.write in every environment when the folder holds templates. An empty folder goes to the trash too. Audited once as folder.trash, with the number of templates.

Parameters

NameInTypeRequiredDescription
pathquerystringyes

Responses

StatusDescriptionBody
200object
403The folder holds templates and the caller lacks template.write in an environment
404No such folder
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
pathstring
statustrashed
itemTrashItem

GET /v1/templates/{id} ​

Get a published template or its draft.

Returns {id, name, environment, document, commit, revision, draft, files}. With draft=true and no draft written yet, the published copy comes back with revision 0 and draft false; the first edit, sent with baseRevision 0, creates the draft.

With format=markup, or Accept: application/vnd.rfml+xml, the reply is the document alone as canonical RFML markup (§27.6, from the engine's /v1/markup/format), with X-Draft-Revision (drafts) or X-Template-Commit (published copies).

Parameters

NameInTypeRequiredDescription
idpathstringyes
environmentquerystring
draftquerybooleanRead the draft (or the published copy when no draft is written yet).
commitquerystring
formatqueryjson, markupmarkup reads or writes the document as RFML markup (application/vnd.rfml+xml) instead of JSON.

Responses

StatusDescriptionBody
200The templateobject, string
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
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/templates/{id} ​

Move the template to the trash, from every environment at once.

Needs template.write in every environment. Each environment branch gets a commit "trash <id>" removing the template, and the trash item keeps the tree each branch had; the template store keeps the draft, its edits and history, the published copies and the assistant's conversations. The template's unfinished jobs are cancelled (template-in-trash) and its pending approvals withdrawn. The id is held until the item is restored or purged. Audited as template.trash.

Responses

StatusDescriptionBody
200object
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
idstring
statustrashed
itemTrashItem

PUT /v1/templates/{id}/folder ​

Move a template into a catalog folder.

Responses

StatusDescriptionBody
200Folder updated
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
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/templates/{id}/draft ​

Replace the draft document, whole or as a JSON Patch.

The engine diffs the new document against the draft (/v1/diff) into edit operations, which are then applied like POST …/draft/ops, so locks and "no new errors" apply. A JSON Patch (RFC 6902) is applied to the draft document first. Bundle files are written with …/draft/files/{path}.

The document can also be sent as RFML markup: Content-Type application/vnd.rfml+xml (or format=markup), with environment, baseRevision (required), dryRun and message in the query. The engine parses it (/v1/markup/parse); syntax errors are 422 with markup/* diagnostics, and nothing changes.

Parameters

NameInTypeRequiredDescription
idpathstringyes
formatqueryjson, markupmarkup reads or writes the document as RFML markup (application/vnd.rfml+xml) instead of JSON.
environmentquerystring
baseRevisionqueryinteger (int64)Markup bodies only (JSON bodies carry it in the body).
dryRunquerybooleanMarkup bodies only.
messagequerystringMarkup bodies only.

Request body (application/vnd.rfml+xml): string

Request body (application/json)

FieldTypeRequiredDescription
environmentstring
baseRevisioninteger (int64)yes
documentDocument
patcharray of object
patch[].opadd, remove, replace, move, copy, testyes
patch[].pathstringyes
patch[].fromstring
patch[].valueany
dryRunboolean
messagestring

Responses

StatusDescriptionBody
200SavedDraftResult
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
412baseRevision is stale. The body is {error:"conflict", message, revision} with the draft's current revision.
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

POST /v1/templates/{id}/draft/ops ​

Apply a batch of RFML edit operations to the draft.

Loads the bundle, calls the engine's /v1/ops with canUnlock (the caller's template.unlock) and the "no new errors" rule, writes the result if baseRevision is still the draft's revision, and appends the batch and its inverse to the edit log. The engine is given the library releases the document resolves to (nested ones included), so imported components' props are checked; a batch that adds a library reference runs again with it resolved. Agents are rate limited by agents.maxOpsPerMinute (429).

Parameters

NameInTypeRequiredDescription
idpathstringyes

Request body (application/json)

FieldTypeRequiredDescription
environmentstring
baseRevisioninteger (int64)yesThe revision the batch was written against; 0 when no draft exists yet.
batcharray of Opyes
dryRunbooleanCheck and report without writing.
messagestringFor the edit log.
proposalstringThe id of the assistant proposal this batch accepts (POST …/assistant). When reportd relayed that proposal to the caller for this draft with exactly this batch, the edit log records the edit as the assistant agent's, on behalf of the caller (actorKind agent, source assistant), once. Relayed proposals are kept for 24 hours in the template store (file and sqlite drivers), so this holds across a reportd restart. Otherwise the edit is the caller's own. An edit that is the agent's marks the proposal accepted in the caller's conversation.

Responses

StatusDescriptionBody
200Applied (or, with dryRun, would apply)DraftResult
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
412baseRevision is stale. The body is {error:"conflict", message, revision} with the draft's current revision.
422The engine rejected the change or the document has errors: {error:"invalid", message, diagnostics}. A lock violation is an edit/locked diagnostic.
429An agent sent too many batches, or the write rate limit; see Retry-After

POST /v1/templates/{id}/draft/undo ​

Undo the caller's latest edit that is not undone yet.

Applies the edit's stored inverse through the engine (locks apply; no batch rule, since the state it returns to may have had errors).

Parameters

NameInTypeRequiredDescription
idpathstringyes

Request body, optional (application/json): UndoRequest

Responses

StatusDescriptionBody
200Undone; undone names the revisionDraftResult
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
409Nothing to undo
412baseRevision is stale. The body is {error:"conflict", message, revision} with the draft's current revision.
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/templates/{id}/draft/redo ​

Redo the caller's latest undone edit.

Parameters

NameInTypeRequiredDescription
idpathstringyes

Request body, optional (application/json): UndoRequest

Responses

StatusDescriptionBody
200Redone; redone names the revisionDraftResult
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
409Nothing to redo
412baseRevision is stale. The body is {error:"conflict", message, revision} with the draft's current revision.
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

GET /v1/templates/{id}/draft/edits ​

The draft's edit log.

Parameters

NameInTypeRequiredDescription
idpathstringyes
environmentquerystring
afterqueryinteger (int64)Only edits with a higher revision.
limitqueryinteger

Responses

StatusDescriptionBody
200{edits: [{revision, actor, actorKind, onBehalfOf, source, message, kind (undo, redo or empty), target, ops, inverse, createdAt}]}, oldest first
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
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/templates/{id}/draft/files/{path} ​

Read one bundle file of the draft (or of the published copy, when there is no draft).

Parameters

NameInTypeRequiredDescription
idpathstringyes
pathpathstringyesBundle path, e.g. samples/invoice.json or assets/logo.svg. May contain slashes.
environmentquerystring

Responses

StatusDescriptionBody
200The file's bytes, with the media type its name says (an asset's image or font type, application/json, else application/octet-stream); X-Draft-Revision names the revision
404No such file, 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

PUT /v1/templates/{id}/draft/files/{path} ​

Write one bundle file of the draft.

Samples, schemas, tests and local assets. The document changes only through edit operations, rfml.lock.json only through publish, and assets/remote/ only through publish's fetch of remote assets. JSON files must be valid JSON. Files under assets/ are images or fonts (PNG, JPEG, GIF, WebP, SVG, TTF, OTF, WOFF2): the name's extension says which, and the bytes must be that format; the body's Content-Type is not used. A document names one by its bundle path as the asset's src (assets/logo.svg); publish pins the ones it uses in rfml.lock.json by sha256, and renders, tests and previews send them to the engine by hash. Every bundle file is at most 16 MiB, and a bundle at most 64 MiB.

Parameters

NameInTypeRequiredDescription
idpathstringyes
pathpathstringyesBundle path, e.g. samples/invoice.json or assets/logo.svg. May contain slashes.
environmentquerystring
baseRevisionqueryinteger (int64)Optional revision check.

Request body (application/json)

Request body (application/octet-stream)

Request body (image/*)

Request body (font/*)

Responses

StatusDescriptionBody
200Saved, with the new revision
400Not a bundle path (including a name under assets/ that isn't an image or font), the document or lockfile, assets/remote/, or invalid JSON
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
412baseRevision is stale. The body is {error:"conflict", message, revision} with the draft's current revision.
413The file is over 16 MiB, or the bundle would be over 64 MiB (too-large)
415An asset's bytes aren't the image or font its name says (unsupported-media-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

DELETE /v1/templates/{id}/draft/files/{path} ​

Remove one bundle file from the draft.

Parameters

NameInTypeRequiredDescription
idpathstringyes
pathpathstringyesBundle path, e.g. samples/invoice.json or assets/logo.svg. May contain slashes.
environmentquerystring
baseRevisionqueryinteger (int64)

Responses

StatusDescriptionBody
200Removed, with the new revision
404No such file, or the template is in the trash (in-trash, see InTrash)
412baseRevision is stale. The body is {error:"conflict", message, revision} with the draft's current revision.
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/templates/{id}/outline ​

The engine's outline of the template (page templates and body nodes, with labels and locks).

Parameters

NameInTypeRequiredDescription
idpathstringyes
environmentquerystring
draftquerybooleanRead the draft (or the published copy when no draft is written yet).

Responses

StatusDescriptionBody
200{pages, body, diagnostics}, as the engine's POST /v1/outline
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
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/templates/{id}/validate ​

Validate the draft (or a document sent in the body) against its bundle.

RFML §3 steps 1–3, done by the engine: structure, references and expressions against the dataset schemas. Problems are diagnostics (RFML §22.1), not errors.

Parameters

NameInTypeRequiredDescription
idpathstringyes

Request body, optional (application/json)

FieldTypeRequiredDescription
environmentstring
documentDocument

Responses

StatusDescriptionBody
200
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
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/templates/{id}/publish ​

Publish the draft.

The publish pipeline: inspect → resolve (data sources, and library imports, subreports and library: assets by their version ranges, and those the resolved library reports and imported components use in turn, to 5 reports deep; cycles are ref/cycle, deeper nesting security/limit) → remote assets (fetched under assets.remote.<environment>: allowlisted origins, the SSRF guard, size caps; stored under assets/remote/) → validate (any error blocks, 422 with diagnostics and the stage; under outputs.pdf.standard PDF/UA-1 or PDF/A-2a+UA-1 the engine's errors include a11y/missing-title, a11y/missing-lang, a11y/untagged-pdf and a11y/missing-alt) → canonical form → rfml.lock.json (pinning sources, every library release resolved, nested ones included, and assets: the bundle's own assets/ files the document uses and library and remote assets, each by sha256) → tests (tests.publishGate: block, the default, refuses a publish whose tests fail or whose snapshots differ from the approved ones with 422 tests-failed; warn reports them in tests; off skips) → one commit on the environment branch → push when sourceControl.pushOnPublish. dryRun runs every check and returns the lockfile without writing.

Agents are held to agents.publish: deny (403), allow, or approval (the default: 202 with a pending approval, decided in /v1/approvals). Under approval the checks run first, as a dry run: a draft they block gets the 422 straight away instead of a pending approval. Dry runs need no approval.

Parameters

NameInTypeRequiredDescription
idpathstringyes

Request body, optional (application/json)

FieldTypeRequiredDescription
environmentstring
messagestring
dryRunboolean
baseRevisioninteger (int64)Publish only if the draft is still at this revision (409 otherwise).

Responses

StatusDescriptionBody
200The revision (id is the commit) plus dryRun, draftRevision, diagnostics (warnings), lock, files, pushed, and tests (the gate's run, as POST …/tests/run returns it) when tests ran
202An agent's request is waiting for approval
403No template.publish, or agents.publish is deny
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
409The draft moved on from baseRevision, or the branch diverged
422Blocked: {error: "invalid", message, stage: inspect|resolve|assets|validate, diagnostics}, or {error: "tests-failed", message, stage: "tests", tests}.
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/templates/{id}/history ​

Commits that touched the template on an environment branch.

Parameters

NameInTypeRequiredDescription
idpathstringyes
environmentquerystring

Responses

StatusDescriptionBody
200History
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
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/templates/{id}/promote ​

Copy the template, with its lockfile, from one environment onto the next.

Promotes with dependencies: the library releases and data source versions the lockfile pins are copied to the target first when it lacks them. Everything is checked before anything is written; the promote is blocked with 409 dependencies when a pinned release or version isn't in the source environment as pinned, the target holds a different copy of it, or a source's connection has no binding in the target: {error: "dependencies", message, dependencies: [{kind: library|source, name, version, refs, problem}]}. Agents are held to agents.publish, as for publish.

Parameters

NameInTypeRequiredDescription
idpathstringyes

Request body, optional (application/json)

FieldTypeRequiredDescription
fromstring
tostringDefaults to the environment after from.

Responses

StatusDescriptionBody
200The promotion commit (a revision) plus dependencies: [{kind, name, version, refs, action: present|promote}]
202An agent's request is waiting for approval
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
409The target environment can't get what the template pins
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/templates/{id}/rollback ​

Restore an older commit of this template as a new commit.

Agents are held to agents.publish, as for publish.

Parameters

NameInTypeRequiredDescription
idpathstringyes

Responses

StatusDescriptionBody
200Rollback commit
202An agent's request is waiting for approval
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
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/approvals ​

Agents' publish, promote and rollback requests that the caller could decide.

Parameters

NameInTypeRequiredDescription
statusquerystringpending (default), approved, rejected, withdrawn (the template went to the trash) or all

Responses

StatusDescriptionBody
200{approvals: [{id, action, templateId, environment, from, commit, message, draftRevision, requestedBy, onBehalfOf, requestedAt, status, decidedBy, decidedAt, comment, result}]}
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/approvals/{approvalId} ​

Approve or reject an agent's request.

Only a person (not an agent) holding the action's permission where it lands may decide. Approving runs the action at once, attributed to the agent; a publish fails with 409 if the draft has changed since the request.

Parameters

NameInTypeRequiredDescription
approvalIdpathstringyes

Request body (application/json)

FieldTypeRequiredDescription
decisionapprove, rejectyes
commentstring

Responses

StatusDescriptionBody
200
403An agent, or no permission
409Already decided, or the draft moved on
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/templates/{id}/tests/run ​

Run the template's embedded tests (RFML §20).

Runs the tests through the engine (/v1/test, with snapshots) on the draft, or the published copy with draft false. The engine is the judge: it compares each test's page snapshots (sha256 of the page SVG) with the approved baseline the bundle carries at tests/<test-slug>.snapshot.json (status same, changed or new), and a test with snapshots passes only when they are all the same. The clock is context.now, else 2000-01-01T00:00:00Z; a bundle's tests/context.json overrides both. rasters adds each snapshot page as a base64 PNG, for people to look at. Needs template.read.

Parameters

NameInTypeRequiredDescription
idpathstringyes

Request body, optional (application/json)

FieldTypeRequiredDescription
environmentstring
draftbooleanDefaults to true.
testsarray of stringOnly these tests.
contextobjectRender context {now, locale, timezone}.
rastersbooleanReturn snapshot pages as PNGs too.

Responses

StatusDescriptionBody
200{id, environment, draft, revision, commit, passed, total, failed, results: [{name, passed, pages, expectations, diagnostics, snapshots: [{page, sha256, status, baseline?, png?}], approval?: {path, status, baseline}, failures}]}, as the engine's /v1/test reports them plus failures (why a test failed, one line each)
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 published copy pins library releases its environment lacks (unresolved-library)
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/templates/{id}/tests/{name}/approve ​

Approve a test's page snapshots as its baseline.

Runs the one test on the draft and writes the baseline the engine proposes (approval.baseline, as JSON) at approval.path, tests/<test-slug>.snapshot.json, as a draft edit with a revision check; the next publish carries it. When the snapshots are already the same as the baseline nothing is written. Needs template.write.

Parameters

NameInTypeRequiredDescription
idpathstringyes
namepathstringyesThe test's name, URL-encoded

Request body, optional (application/json)

FieldTypeRequiredDescription
environmentstring
baseRevisioninteger (int64)
contextobject
messagestring

Responses

StatusDescriptionBody
200{revision, test, approval: {path, status, baseline}, snapshots}
404The document has no test of that name, or the template is in the trash (in-trash, see InTrash)
409The test takes no snapshots (no-snapshots)
412baseRevision is stale. The body is {error:"conflict", message, revision} with the draft's current revision.
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