HTTP methods
| Method | Use |
|---|---|
GET | Read a resource or a list |
POST | Create, or run a named action (publish, assign-users, merge, retry) |
PATCH | Partial update — a merge: only the keys you send change, and a null value clears one |
DELETE | Remove or deactivate |
Record updates have no PUT. Updates there are merges by design, so a
partial body can never wipe fields you did not send. (Earlier releases
accepted PUT on records as an undocumented alias for PATCH; on the
record endpoint it now returns 405. Other endpoints that accept PUT
treat it the same as PATCH.)
For record updates specifically, PATCH validates the fields you send
against the record's own form version by default — publishing a new
version of a form does not break an integration mid-flight. Sending
"schema": "current" opts the record into the newest version instead:
the whole merged record is checked against it, and the record moves to
that version when it conforms. Invalid payloads — unknown field ids,
wrong value types, list values outside the option set — return 400
with a details array naming each problem.
Status codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 202 | Accepted — the work was queued (report merge returns this with a merge_run_id) |
| 400 | validation_failed — body or query is malformed |
| 401 | unauthorized — missing, expired, or invalid API key |
| 403 | forbidden — valid key, wrong scope or cross-org access |
| 404 | not_found |
| 405 | method_not_allowed |
| 409 | conflict — slug/identifier already exists, or idempotency replay with a different body |
| 429 | rate_limited |
| 500 | internal_error — unexpected server fault |
| 502 | bad_gateway — an upstream call failed (e.g. a report merge) |
| 503 | returned by /health when the database is unreachable; the body is a success envelope with data.status: "degraded", not an error code |
Response envelopes
Single resource:
{
"data": { ... },
"request_id": "req_a1b2c3d4..."
}List:
{
"data": [ ... ],
"meta": {
"next_cursor": "eyJpZCI6Ii4uLiJ9" | null,
"limit": 25
},
"request_id": "req_a1b2c3d4..."
}Error:
{
"error": {
"code": "validation_failed",
"message": "Invalid request body",
"details": [
{ "path": "config.recipient", "issue": "Required" }
]
},
"request_id": "req_a1b2c3d4..."
}details is included only when the server has structured field-level
information. For other 4xx codes you'll just see code + message.
Warnings on a successful write
A write can succeed and still have something worth telling you about. When
it does, the success envelope carries an optional warnings array:
{
"data": { ... },
"warnings": [
{
"code": "expression_cycle",
"path": "fields",
"message": "Circular visibility rule: field_a → field_a"
}
],
"request_id": "req_a1b2c3d4..."
}The key is absent when there is nothing to report, so a client that ignores it sees exactly the envelope it saw before.
Warnings describe something we accepted but you probably did not mean.
Read code and not message — the code is the contract, the wording may
change.
POST and PATCH on /forms return the same warnings the form builder
shows a person in its warnings panel. Until this release an integration
building forms through the API got none of them, so a form could save
cleanly and then behave in a way nobody meant to ask for. Six codes:
code | What it means |
|---|---|
duplicate_field_ids | Two fields in the same scope share an id. One of them wins when a record is read. |
expression_cycle | A Visible when rule or formula refers back to itself in a circle. |
icon_unresolvable | A form or page icon that will not draw, so the crew sees a blank-looking button. |
lookup_filter_unresolvable | A lookup filter points at a field that is not there to read. |
display_fields_unknown | The fields chosen to label a record in lists do not exist on the form. |
unknown_field_reference | A Visible when rule or formula reads a field that is not on the form — usually one that was deleted after the rule was written. |
Warnings never block. A warning does not become a 400 in a later
release — a form that saves today has to keep saving — so an integration
can log them, show them to whoever authors the form, and ship. Two of
them are stricter later on. A circular expression, and a rule that reads
a field which is not on the form, are both accepted while saving a
draft — a form mid-authoring passes through either on its way to
something sensible — and both are refused at publish and at
ready-for-testing, because there is no reason to send one to a device.
A dangling reference is worth the block: the usual shape of it hides the
field that carries the rule, and a hidden field is not checked for being
required, so a form can go out missing an answer nobody meant to make
optional.
The warnings are read from the saved form rather than from your request,
so a PATCH that changes one part of a form is judged on the whole
thing, the way a field user will meet it.
One warning the builder shows is deliberately left out here: it nudges a person who has marked nothing required, which is a fair prompt in the builder and a false alarm over the API, where plenty of correct forms have no required field. A channel that cries wolf gets ignored, and the six above are worth reading.
The same warnings come back from the form tools on the MCP server.
Request id
Every response includes:
X-Request-Idheader (e.g.req_a1b2c3d4e5f60718293a4b5c)request_idin the body envelope
These match. Quote the id when reporting issues — we use it to join HTTP logs, audit rows, and observability events.
Content types
- Requests:
application/jsonfor everything except file uploads (forthcoming in a later phase). - Responses:
application/json. The reportmergeendpoint is no exception — it queues the render and returns202with amerge_run_id; the document is rendered once, then sent to every destination configured on the report.
Time format
All timestamps are ISO-8601 UTC: 2026-05-23T15:00:00Z. Send the same
format on writes.
UUIDs
Resource ids are UUIDs in the 8-4-4-4-12 hex format. The API accepts
both RFC 4122 v1-v8 UUIDs and lower-case hex with no version/variant
bits (handy for fixture data).