Conventions
These hold across every endpoint. Learning them once is cheaper than learning them one resource at a time.
JSON in and out (application/json), except file uploads (multipart/form-data) and the
plain-text documentation endpoints.
- Field names are
snake_caseeverywhere, including nested configuration objects. - Identifiers are UUID strings.
- Timestamps are RFC 3339 with an explicit offset, normalised to
Zon output. - Money is a decimal string (
"0.014823"), never a float. A float total does not reconcile — parse it into a decimal type or leave it as a string, but do not put it throughfloat. - Token counts and durations are integers. A duration’s field name ends in
_msor_sand says which.
Enumerations are closed. Every vocabulary in the reference is the exact set of labels the database enum carries; a value outside it cannot be stored and will not appear on the wire.
Collections
Section titled “Collections”Every list endpoint accepts:
| Parameter | |
|---|---|
limit | Default 50, max 200. |
cursor | The primary pagination mechanism. |
offset | Only when cursor is absent, capped at 10 000. For page-number UIs. |
sort | field or -field, allowlisted per resource. |
q | Free-text search, where the resource supports it. |
And answers:
{"items": [], "next_cursor": "…", "total": 1234}Two things worth knowing:
total is null when counting is not cheap, and null means “not computed”. It never
means zero. A UI that renders null as 0 will tell somebody their workspace is empty.
An unknown filter is a 422 naming it, not a silently ignored parameter. A typo’d query
string fails loudly rather than quietly returning everything.
Loop on next_cursor until it is absent:
curl "https://your-deployment.example.com/runs?status=failed&limit=100" \ -H "X-Api-Key: $COBRE_KEY"Idempotency
Section titled “Idempotency”Implemented on POST /runs. Send the header — Idempotency-Key: <≤200 chars> is the canonical
spelling — and the key is bound to a hash of the request body.
| Situation | Result |
|---|---|
| First use | The run is created; the key, the body hash and the response are recorded. |
| Replay, same body | The original response verbatim, with Idempotent-Replay: true. |
| Replay, same body, first still running | 409 idempotency_in_flight, Retry-After: 1. |
| Replay, different body | 422 idempotency_key_reuse — the key is bound to its body. |
| After 48 h | The record expires and the key is free again. |
Two things before you build retries on this:
- The hash is taken over the validated document with the key removed. Re-ordering the JSON object, or switching between the header and the body field, is the same request; changing any field is a different one.
run.idempotency_keyis permanently unique per workspace. A repeat after the 48-hour record has expired still returns the original run rather than creating a second one — it just arrives withoutIdempotent-Replay, because as far as the record is concerned it is a first use.
The body field idempotency_key in RunSubmit is the older spelling and works identically,
hash binding included. Sending both is fine when they agree and is 422 idempotency_key_mismatch when they do not: a client that believes it sent one key is never
silently deduplicated against another.
Other POST endpoints accept the header without acting on it. Only /runs is wired.
Partial updates
Section titled “Partial updates”PATCH implements RFC 7396 merge-patch:
- a present field is set;
nulldeletes;- an absent field is untouched.
application/json on a PATCH is treated identically to application/merge-patch+json.
PUT is a full replacement and exists only where a full replacement is meaningful.
On a versioned resource — agent, toolkit, skill, review playbook — both verbs create a new revision and never mutate an existing one. See Agents.
Optimistic concurrency
Section titled “Optimistic concurrency”Mutable single-object resources return an ETag and accept If-Match.
A mismatch is 409 stale_write with the current representation in the body, so a client
can merge rather than guess. Read it, reconcile, resubmit with the new ETag.
Deprecation
Section titled “Deprecation”The path carries no version segment, because breaking changes are not made — only additive
ones. A field is retired with a Deprecation header and a sunset date at least 180 days
out. Nothing disappears without that warning.
Where to next
Section titled “Where to next”- Errors — the envelope every one of those
4xxs arrives in. - Streaming a run — the one place the rules on this page do not apply.