REST API¶
Two APIs share the port. /v1 is the OpenAI-compatible surface your app calls. /api/v1 is the admin surface that the CLI, the MCP server and the dashboard all use, and you can call it too. Both honour SEMULA_API_KEY when it is set. Errors are described in Errors.
For your app: /v1¶
| Method and path | What it does |
|---|---|
POST /v1/chat/completions |
Chat completions, streaming and non-streaming, with tool calls. Pick a project with the X-Semula-Project header. |
POST /v1/projects/{project_id}/chat/completions |
The same, with the project in the path. Use this as the SDK's base_url. |
GET /v1/models |
The model ids semula lists (SEMULA_MODELS). |
GET /v1/models/{model_id} |
One model, or 404 model_not_found. |
GET /v1/projects/{project_id}/models |
The models for one project. |
GET /v1/projects/{project_id}/models/{model_id} |
One model for one project. |
GET /health |
{"status": "ok", "version": ..., "mode": ...}. Always open. |
For people and agents: /api/v1¶
| Method and path | What it does |
|---|---|
GET /api/v1/projects |
Projects with entry and pending counts. |
POST /api/v1/projects |
Create one: {"id": "my-app", "name": "My app"}. |
GET /api/v1/projects/{project_id} |
One project with its settings and defaults. |
PATCH /api/v1/projects/{project_id} |
Rename it or patch settings. |
GET /api/v1/projects/{project_id}/upstream |
Calls and tokens forwarded upstream against the caps, and would_forward (the prompts a dry run refused). |
POST /api/v1/projects/{project_id}/upstream/reset |
Count upstream calls and tokens from zero again. Admin. |
DELETE /api/v1/projects/{project_id} |
Delete it and everything in it. |
GET /api/v1/projects/{project_id}/entries |
List entries without bodies. Filters: q, pinned, has_alias, tool_calls, never_hit; sort, order, limit, offset. |
POST /api/v1/projects/{project_id}/entries |
Author an entry: {"request": {...}, "response": ..., "version"?: n}. 201 if created, 200 if replaced. |
GET /api/v1/entries/{entry_id} |
One entry in full. |
PUT /api/v1/entries/{entry_id} |
Change it. Needs version; fields response, add_response, selection, pinned, force. |
DELETE /api/v1/entries/{entry_id} |
Delete it. Needs ?version=. |
GET /api/v1/entries/{entry_id}/history |
Revisions, newest first. ?version=N for one in full. |
POST /api/v1/projects/{project_id}/test-match |
What would happen to a request, tier by tier. |
GET /api/v1/mode |
The server default and each project's mode. |
PUT /api/v1/mode |
Set the mode: {"mode": "record", "project"?: id}. |
GET /api/v1/pending |
Parked calls, each with a conversation (its group, depth and size). ?project=, ?status=all, and ?wait=SECONDS (up to 60) to hold until one is parked. |
GET /api/v1/pending/{pending_id} |
One parked call in full, with a shape: each offered tool (name, description, argument schema, a skeleton answer that passes validation), a text skeleton, and the max_tokens limit if the app set one. |
GET /api/v1/conversations |
Calls grouped by conversation: a call whose messages extend an earlier call's joins its group and lists the messages it added. ?source=pending (default) or log, ?project=, ?min_size=2 to leave out calls that continue nothing, ?limit=. |
POST /api/v1/pending/claim |
Hold the oldest unclaimed parked call for a lease and return it in full (as GET does, plus claim), or {"pending": null}. Body, all optional: project, lease_seconds (default 300, at most 3600), worker. |
POST /api/v1/pending/{pending_id}/answer |
Answer it. The body is the answer. Releases the held call. |
GET /api/v1/candidates |
The review queue. |
GET /api/v1/candidates/{candidate_id} |
One candidate next to its entry. |
POST /api/v1/candidates/{candidate_id}/approve |
Serve that prompt from the entry from now on. |
POST /api/v1/candidates/{candidate_id}/reject |
Never pair them again. |
GET /api/v1/log |
Recent calls, newest first. |
GET /api/v1/log/stats |
Counts (hits, misses, forwarded, recorded, timed out), hit rate, median latency. ?window=30m. |
GET /api/v1/log/stream |
The same feed as server-sent events. |
GET /api/v1/projects/{project_id}/export |
A semula/1 bundle of a project. |
POST /api/v1/import |
Load a bundle: {"bundle": {...}, "project"?: id, "force"?: bool}. Idempotent. |
GET /api/v1/usage |
What the workspace holds against its limits: projects, entries, stored bytes, parked calls, active tokens. Limits are null locally. |
GET /api/v1/audit |
The audit log, newest first: ?limit=, ?before_id=, ?action=. People and local use only; a token cannot read it. |
GET /api/v1/tokens |
The workspace's API tokens, without secrets. People and local use only; a token cannot call it. |
POST /api/v1/tokens |
Mint a token: {"name", "scope": "replay"\|"record"\|"admin", "project"?, "expires_at"?}. The secret is in the answer once. |
POST /api/v1/tokens/{token_id}/rotate |
Mint a successor; the old token works for grace_seconds more (default a day). |
POST /api/v1/tokens/{token_id}/revoke |
Stop a token working. |
GET /api/v1/embeddings |
Whether semantic matching is on and how far indexing has got. |
Who is writing¶
A write is treated as an agent unless the request sends X-Semula-Actor: human. Agents cannot change a pinned entry. The CLI's --as-human sends that header.
Wait for a parked call¶
Returns as soon as a call is parked, at once if some already wait, and {"pending": []} after 30 seconds. No database connection is held while it waits.