/api/schema/, /api/docs/ (Swagger UI), and /api/redoc/. Coding agents get the same surface as typed tools over MCP.
Authentication
Three ways to authenticate; the first is right for almost all programmatic use:
A key has a scope:
project pins it to one project, account reaches every project the user is a member of. MCP and .overmind/credentials.toml use a project-scoped key; of the two, only an account-scoped key can POST /api/projects/. Keys are created and revoked under Projects → your project → API keys or via the API:
name (optional) and either project (a project id → project-scoped) or scope ({"scope": "account"}, or {"scope": "project", "resourceIds": ["<project-uuid>"]}); the response carries the plaintext key once. The list returns metadata only: id, name, prefix, project, scope, is_active, expires_at, last_used_at, created_at. current/ returns the calling key’s scope document. Keys do not expire unless expires_at is set on the row; revoke what you no longer need.
Endpoint map
Standard REST resources follow Django REST Framework conventions:GET list with query-param filtering (paged, 25 per page), POST create, GET/PATCH/DELETE on /{id}/, plus the custom actions listed here.
Core resources
Evaluation
Optimisation, training, serving
Special endpoints
Building a dataset
A dataset lands its rows as-is and its agent shapes them cell by cell; a run uses one version. The same sequence backs the Console, the CLI, and the MCP dataset tools.1
Land a source
A file goes up in chunks (
POST /api/uploads/, then PUT /api/uploads/{id}/chunk/?offset=).
POST /api/datasets/ with project, name, optional intent (train or eval) and
capability, and exactly one source: upload_id (+ filename), rows or text for
pasted data, or traces (trace_ids, or a trace-list selection of filters and/or
search; one row lands per trace). The selection is validated and counted first: an
unknown filter key or a selection that matches no trace is a 400, so a dataset never lands
empty. The response is the dataset; landing runs in the background and state moves from
landing to diagnosing while the agent runs its preparation turn, then idle (error
on failure). Landing proposes capability_rank and the intent when they were left out.2
Shape it
POST /api/datasets/{id}/cells/ adds a cell (title, script, note) at the end of the
chain; PATCH .../cells/{id}/ edits it and a script change queues it and every cell after
it; DELETE .../cells/{id}/ removes it unless it is frozen; POST .../run/ executes the
queued cells in order (an unchanged cell keeps its frame). POST .../chat/ sends a message
to the dataset’s agent, which lands cells itself; POST .../cells/{id}/accept/ runs a cell
the agent proposed. Writes are refused with 409 while the agent is working.
GET .../events/ streams land_started, land_progress, land_done, land_failed,
run_started, cell_started, cell_done, cell_failed, run_done, run_failed,
chat_turn, chat_thinking, chat_step, chat_delta, chat_cell, chat_failed,
cells_changed and dataset_changed as server-sent events, replaying what you missed first.3
Read a version
The dataset’s
cells carry every version: version, state, rows, columns,
intent_report and capability_report, fits against the intent, stats, and frozen.
GET .../rows/?cell=&offset=&limit=&sort=&dir=&filters=&search=&diff=1 pages a frame (up to
500 rows) with marks (rows added, values changed with the old value) against the cell
before it; GET .../columns/?cell= returns per-column stats.4
Use it
Eval runs, optimisation runs and training runs take
dataset and, optionally, cell;
without it the active version is used. A version whose contracts do not fit the intent, a
dataset of the other intent, or a cell of another dataset, is refused with the reason. A
use freezes the version and starts a new major number.5
Pull it
A local loop reads the used version, never the dataset:
GET .../export/?fmt=jsonl&cell={id}
(fmt=csv for CSV; a raw stream, not a use). The response carries X-Overmind-Cell,
X-Overmind-Version and X-Overmind-Fingerprint; the CLI caches the file as
.overmind/datasets/{cell}.jsonl with the fingerprint beside it and fetches again when it
no longer matches.Conventions
- Filtering uses django-filter query parameters on list endpoints (the Console’s filter bar builds exactly these URLs — copy them from your browser). List responses are paged:
count,next,previous,results. - Errors are JSON with
detailand, when the client needs to tell cases apart,code:401unauthenticated,402 insufficient_creditson launching paid work when the credit gate is on (hosted; a positive balance passes),403 plan_limit_exceeded/seat_limit_exceededon Free-plan caps,403 guest_upgrade_requiredwhen a guest writes,404wrong project or missing resource,409 conflicton a uniqueness race,500 internal_errorwith anerror_idto quote. - IDs are UUIDs except spans (16-hex
span_id) and traces (32-hextrace_id).