Reports
HTTP API routes for reports.
Reports are markdown documents your organization writes together.
{reportId} is the report's UUID from the list route. Bodies travel as
bodyMarkdown in the report document format
and are returned whole; there is no block tree.
| Route | Use | CLI equivalent |
|---|---|---|
GET /reports | List reports without bodies. | ax report list |
POST /reports | Create a report. | ax report create |
GET /reports/{reportId} | One report and its body. | ax report get |
PATCH /reports/{reportId} | Change title, description or body. | ax report edit |
POST /reports/{reportId}/blocks | Append markdown to the body. | ax report append |
GET /reports/blocks/kinds | Structured block kinds a new block can use. | ax report blocks kinds |
GET /reports/blocks/schema | One kind's block schema and example. | ax report blocks schema |
POST /reports/{reportId}/blocks/add | Add one structured block from JSON. | ax report blocks add |
GET /reports/{reportId}/blocks | The report's blocks with their IDs. | ax report blocks list |
POST /reports/trace-quote-source | Find and read assistant messages for a Trace Quote. | MCP: block_list_data_sources (Trace quote candidates for an identified report), report_trace_quote_source |
POST /reports/{reportId}/archive | Archive a report. Nothing is deleted. | ax report archive |
POST /reports/{reportId}/share | Get the report's public link, creating it if needed. | ax report share |
DELETE /reports/{reportId}/share | Revoke the report's public link. | ax report unshare |
GET /experiments/{id}/version-selection-rate-evidence-sources | What an A/B Comparison block can name. | ax report version-selection-rate-evidence-sources |
GET /experiments/{id}/selection-rate-sources | References a selection-rate block can use. | ax report selection-rate-sources |
Every route takes org_id (required only when the key can see several orgs).
Parameters are optional unless marked required. Report write bodies accept
lowerCamelCase or snake_case field names. Trace Quote source requests use lowerCamelCase. A report row carries id, orgId,
title, description, createdBy, updatedBy, createdAt, updatedAt and,
once archived, archivedAt; empty fields are omitted. updatedAt is the
conflict stamp the write routes take back.
GET /api/v1/reports
List reports, most recently changed first.
curl -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports?limit=20"| Parameter | What it does |
|---|---|
limit | Reports per page (default 50, max 200; page_size is an alias). |
cursor | Opaque nextCursor from the previous page (page_token is an alias). |
include_archived | true also lists archived reports. Off by default, so a page holds active reports only; send the same value on every page of one listing. |
The response is {reports, nextCursor}; nextCursor is omitted on the last
page.
POST /api/v1/reports
Create a report.
curl -X POST \
-H "Authorization: Bearer $AX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Checkout latency","description":"Where the p95 went","bodyMarkdown":"# Summary\n"}' \
"https://app.514.ax/api/v1/reports"| Body field | What it does |
|---|---|
title | Required. Surrounding whitespace is trimmed; blank is refused. |
description | One-paragraph summary shown under the title. |
bodyMarkdown | Initial body. Normalized in the document format before it is stored, so the response holds the canonical form of what was sent. |
Returns {report, bodyMarkdown}. Actor and org come from authentication.
GET /api/v1/reports/{reportId}
One report and its body as markdown.
curl -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports/$REPORT_ID"Returns {report, bodyMarkdown, contextLinks}; an empty report omits
bodyMarkdown, and a report with no linked Context objects omits
contextLinks. Each link carries kind and id, plus name and
description when they are not empty; an object that can no longer be read
has no name. Keep report.updatedAt for PATCH. When the report has a
public link, the response also carries it as publicUrl, except for
read-only keys, which never receive it.
PATCH /api/v1/reports/{reportId}
Change the title, the description and/or the whole body.
curl -X PATCH \
-H "Authorization: Bearer $AX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Checkout latency, week 2","expectedUpdatedAt":"2026-09-02T11:30:00.000Z"}' \
"https://app.514.ax/api/v1/reports/$REPORT_ID"| Body field | What it does |
|---|---|
expectedUpdatedAt | Required. The updatedAt you last read. |
title | New title; blank is refused. |
description | New description; an empty string clears it. |
bodyMarkdown | Replacement for the whole body; an empty string clears it. |
The route is presence-aware: a field you send changes, a field you leave out
stays. Sending none of the three is a 400. When the report changed since
expectedUpdatedAt, the route answers 409 with {error, message} and
writes nothing; read the report again and retry with its new stamp. Returns
{report, bodyMarkdown}.
POST /api/v1/reports/{reportId}/blocks
Append markdown to the end of the body, after a blank line, leaving what is there untouched.
curl -X POST \
-H "Authorization: Bearer $AX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"markdown":"> [!FINDING] Slower on Fridays\n> The p95 doubles after 16:00 UTC."}' \
"https://app.514.ax/api/v1/reports/$REPORT_ID/blocks"| Body field | What it does |
|---|---|
markdown | Required. Prose, headings, callouts, ax:// mentions or ax-block fences. Blank is refused. |
expectedUpdatedAt | Refuse the append with 409 when the report changed since this stamp. Omit to append to the body as it stands. |
Returns {report, bodyMarkdown} with the whole body after the append.
GET /api/v1/reports/blocks/kinds
The structured block kinds a new block can use, from the report block
registry, with retired kinds left out. Returns {kinds}, each with kind,
label, description, whenToUse, and the sources and figures it reads.
curl -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports/blocks/kinds"GET /api/v1/reports/blocks/schema
One kind's block schema: the same payload as the MCP report_block_schema
tool for operation add, with schema (JSON Schema), example, wrapper,
runtimeConstraints, sources, guidance and a revision that changes with
the contract. An unknown kind returns 404 with the closest kinds.
curl -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports/blocks/schema?kind=finding"| Parameter | What it does |
|---|---|
kind | Required. A kind from the kinds route. |
POST /api/v1/reports/{reportId}/blocks/add
Add one block, validated against its kind's schema, at the end or after a
block. Returns {reportId, blockId, updatedAt, blocks}, where blockId is the
new block's ID.
curl -X POST -H "Authorization: Bearer $AX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"block":{"kind":"ax-block","block":{"kind":"finding","id":"agents-skip-docs","title":"Agents skip the docs","conclusion":"Most runs called the API first."}}}' \
"https://app.514.ax/api/v1/reports/<REPORT_ID>/blocks/add"| Body field | What it does |
|---|---|
block | Required. The block, as the schema route describes it. |
afterBlockId | Insert after this block ID from the list route. Omit to add at the end. |
expectedUpdatedAt | Refuse with 409 when the report changed since this stamp. |
An invalid block returns 400: a known kind's error names each failing field,
and an unknown kind's names the closest kinds.
GET /api/v1/reports/{reportId}/blocks
The saved body's blocks in document order: {reportId, updatedAt, blocks},
each block with position, blockId, type, kind for structured blocks,
text (a preview) for text blocks, and parentBlockId inside columns.
curl -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports/<REPORT_ID>/blocks"POST /api/v1/reports/{reportId}/archive
Archive a report. The row, its body, its follows and the Context citation a
saved product-import report carries all stay; archivedAt is set and the
report leaves GET /reports unless include_archived=true. GET /reports/{reportId} still returns it. Archiving an archived report returns it
unchanged. Nothing is deleted.
curl -X POST -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports/$REPORT_ID/archive"Returns {report} with archivedAt set.
POST /api/v1/reports/{reportId}/share
Return the report's public link, creating it if the report has none. Anyone with the URL can read the report and its data without signing in. A report has at most one public link, so calling this again returns the same URL.
curl -X POST -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports/$REPORT_ID/share"Returns {reportId, url, created, createdAt}. created is true when this
call created the link. Read-only keys are refused.
DELETE /api/v1/reports/{reportId}/share
Revoke the report's public link. Anyone holding the old link loses access; sharing again creates a new one.
curl -X DELETE -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/reports/$REPORT_ID/share"Returns {reportId, revoked}. A report without a public link succeeds and
omits revoked (false). Read-only keys are refused.
POST /api/v1/reports/trace-quote-source
List a run's assistant messages, or read one listed message's exact text and recorded identity.
curl -X POST \
-H "Authorization: Bearer $AX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"experimentId":"<EXPERIMENT_ID>","runId":"<RUN_ID>"}' \
"https://app.514.ax/api/v1/reports/trace-quote-source"| Body field | What it does |
|---|---|
experimentId, runId | Required. The experiment and one of its runs in your organization. |
entityId | Copy a listed source.message.entityId to read that message; omit to list. |
afterSeq, afterEntityId | Pass the listing's nextAfterSeq and nextAfterEntityId together for the next page. |
A ready listing returns messages, each with a canonical source, an exact
text prefix in preview, and previewTruncated. A ready message read returns
its exact text, recorded identity, and block, the Trace Quote payload to
write in an ax-block fence. Copy the whole source; never invent identifiers
or identity fields. The returned block already carries the message's text as
text, which you may edit. Missing, unavailable and empty sources return
no quote text or block. Reports must be enabled for your organization.
GET /api/v1/experiments/{id}/version-selection-rate-evidence-sources
The ids an A/B Comparison (version-selection-rate-evidence) block
can name for one experiment: its versions, its products with each registered product
version's fingerprint, and its selection tests. No selection counts or rates.
curl -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/experiments/$EXPERIMENT_ID/version-selection-rate-evidence-sources"| Parameter | What it does |
|---|---|
experiment_fingerprint | Read products and tests at one of the experiment's versions. Omit to read them across every version. |
Returns {experimentId, experimentFingerprint, experimentVersions, products, tests} with every field present; an empty experimentFingerprint means every
version. An experiment without a registered version, or a fingerprint that is
not one of its versions, returns 404.
Only test names with one definition in the chosen scope are listed. Set
experiment_fingerprint to narrow the scope when a name has several definitions.
GET /api/v1/experiments/{id}/selection-rate-sources
List the references a selection-rate block is configured with for one
experiment. {id} is the experiment ID returned by the experiment listing.
curl -H "Authorization: Bearer $AX_API_KEY" \
"https://app.514.ax/api/v1/experiments/$EXPERIMENT_ID/selection-rate-sources?test_name=uses-cli"| Parameter | What it does |
|---|---|
experiment_fingerprint | Read the tests at this version instead of the current one. |
test_name | Check a test name against that version. |
Returns {experimentId, experimentFingerprint, versions, tests, scope, selectedTest}. experimentFingerprint is the version tests were read at.
Each test has a name and a test-<uuid> key. scope lists the values
each filter may hold (agents, agentModes, models, products,
environments, promptIds). selectedTest is set only when exactly one test
at that version has test_name. An experiment with no version in the org, or
an unregistered fingerprint, returns 404. No rates are returned.
Selection blocks can persist partial choices with configuration: "unconfigured"
and empty strings for missing experiment IDs or test names. Source changes clear
stale test, version, scope and search choices in the same write. Incomplete blocks
do not query measurements; completing the required choices removes the marker.