Conventions And Errors
The platform API is easier to use once you understand its common contracts. The route groups cover different product areas, but they share the same broad envelope and error behavior.
Route Prefix
Section titled “Route Prefix”Most routes live under:
/api/v1/...Examples:
/api/v1/workflows/api/v1/tasks/api/v1/workspaces/api/v1/logs
Request Conventions
Section titled “Request Conventions”JSON bodies
Section titled “JSON bodies”Most mutation routes expect JSON and validate input explicitly with Zod. Unsupported keys are usually rejected rather than ignored.
Idempotency-style request ids
Section titled “Idempotency-style request ids”Many mutations accept a request_id field:
{ "request_id": "launch-001"}Use it for replay-safe or operator-driven actions such as:
- workflow launch
- activation enqueue
- task completion
- task retry or request-changes flows
request_id is an application-level idempotency key, not a server
trace id. Reuse it only when retrying the same intended mutation.
Strict vs flexible fields
Section titled “Strict vs flexible fields”The API mixes strict control-plane fields with intentionally flexible JSON payloads.
| Contract kind | How to treat it |
|---|---|
| fixed enum or scalar | Validate against the documented type and allowed values. |
| structured object | Follow the documented field shape. Unknown keys may be rejected or ignored depending on the route. |
| arbitrary JSON object | Treat it as caller-defined data. Only documented subfields have platform meaning. |
| opaque token or cursor | Store and replay it exactly. Do not infer semantics from its format. |
When in doubt:
- treat
enum,fixed string,UUID, and scalar labels as strict - treat
enum-like stringas a real value the platform uses today, but not a forever-closed public enum unless the page says so - treat
server-generated <type>as read-only - treat
... or nullliterally: the field can be present but empty - treat
record[]labels as nested read models, not arbitrary blobs
Pagination
Section titled “Pagination”The common list pagination contract is:
page=1per_page=20Current defaults from code:
- default
page:1 - default
per_page:20 - maximum
per_page:100
Cursor and stream fields
Section titled “Cursor and stream fields”Some routes use cursor-style paging or refresh markers:
cursorafterafter_cursorlimit
Those are used heavily in logs, event feeds, and workflow operations streams.
Response Envelopes
Section titled “Response Envelopes”Single resource
Section titled “Single resource”{ "data": { "id": "uuid", "name": "Example" }}Paginated collection
Section titled “Paginated collection”{ "data": [ { "id": "uuid" } ], "meta": { "total": 12, "page": 1, "per_page": 20, "pages": 1 }}Direct stream batch
Section titled “Direct stream batch”Some operations routes return data containing a prebuilt batch packet
instead of a single resource or paginated row list. That is especially
common in workflow operations and live-console surfaces.
No content
Section titled “No content”204 No Content usually means one of these:
- a claim route had nothing to claim
- a delete or acknowledgement completed without returning a resource
- a disconnect-style mutation intentionally returns no body
Do not treat 204 as an error on polling or claim routes.
Error Shapes
Section titled “Error Shapes”The platform error handler normalizes failures into this shape:
{ "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "recovery_hint": "optional", "details": {} }, "meta": { "request_id": "generated-or-fastify-id", "timestamp": "2026-03-31T18:55:00.000Z" }}Common Status Codes
Section titled “Common Status Codes”200 OK: successful read or mutation201 Created: new resource or enqueue result created204 No Content: successful claim poll, delete, or acknowledgement with no body400 Bad Request: validation or domain-level bad input401 Unauthorized: missing or invalid auth403 Forbidden: scope or ownership mismatch404 Not Found: resource missing409 Conflict: state conflict, duplicate, or invalid transition422 Unprocessable Entity: schema validation failed429 Too Many Requests: rate-limited503 Service Unavailable: temporary service problem
Validation Errors
Section titled “Validation Errors”Zod-driven validation failures typically include details.issues.
Representative shape:
{ "error": { "code": "SCHEMA_VALIDATION_FAILED", "message": "Invalid request body: name is required", "details": { "issues": { "fieldErrors": { "name": [ "Required" ] }, "formErrors": [] } } }, "meta": { "request_id": "req-123", "timestamp": "2026-03-31T18:55:00.000Z" }}Stream Shapes
Section titled “Stream Shapes”SSE routes generally emit text/event-stream with one JSON object per
event.
Representative form:
event: logdata: {"id":"...","operation":"task.complete","status":"completed"}Heartbeat events are also used on some streams:
event: heartbeatdata: {"ts":"2026-03-31T18:55:00.000Z"}Practical Integration Advice
Section titled “Practical Integration Advice”- Persist
request_idvalues for mutation replay safety. - Treat
204claim responses as normal control flow. - Expect secret-like values in payloads to be redacted in public read models.
- Keep scope-specific credentials separate instead of sharing one admin token across every integration path.