Endpoint summary
Fifty-two operations across 48 paths. The set is generated from the running application, so it is complete for the default
all role; other roles serve a subset.
Conventions
Base URL
Every path above is relative to the origin XORCISE is serving on, plus/api. Do not hard-code a port: the REST plane starts at 3001 and walks up to 50 ports to find a free one, so a second instance, or a busy port, moves it. xorcise up prints the URL it took, xorcise status prints it on the REST API row, and xorcise ui prints the same origin with /ui appended.
The examples on this page take the base URL from xorcise ui and strip the /ui suffix:
Authentication
The operator API has no authentication. No API key, no session, no token, on any of the agent, mission, run, result, configuration, system, or filesystem endpoints. In the defaultlocal deployment topology XORCISE binds loopback plus, on Linux, the Docker bridge gateway — so every local process and every container on that bridge can read your runs and traces, change your judge model, start runs, and delete results. Set XORCISE_HOST=0.0.0.0 and the network can do the same. Reachability is the access control. Security and isolation explains why the bind is what it is and what to do about it.
The one exception is the eight run-control endpoints your agent calls. Those authenticate per run with a bearer token, and they authorize nothing else — a run’s token opens that run and no other.
The run-control token
POST /api/runs mints a 32-character hexadecimal token and returns it as run_control_key in the 201 body. That is the only place it appears. GET /api/runs does not carry it, and there is no endpoint that re-reads it, so capture it when you create the run.
401:
Authorization header was absent or was not a Bearer credential. The second means the token does not belong to that run.
Run ids
The REST API matches run ids exactly and takes the full 32 characters.xorcise run status 4c62254b works because the CLI expands a unique prefix before it calls the API; GET /api/runs/4c62254b/result returns 404.
Errors
Every error carries FastAPI’s default shape, a single string underdetail:
422 is the exception and carries a list instead, one entry per invalid field:
422. It is omitted from the per-endpoint status tables below except where a specific value, such as an unsupported report format, is rejected that way.
The 202 grading window
Grading runs after a run ends, not during the request that ends it, so there is a window in which a run is finished but has no score. Four endpoints share one state ladder:GET /result, GET /report, and GET /stats all return 202 this way, and POST /regrade returns 202 on success. Poll on 202 — it is a normal transient state, not a failure. Polling also re-drives grading, de-duplicated, so a grade lost to a restart heals on the next poll instead of hanging at grading forever.
Agents
GET /api/agents
Synopsis200 with an array of agent records. Each carries id, name, created_at, version, and the optional declared fields endpoint, kind, model, and otel. version starts at 1 and increments on every update. otel is stored and read by nothing — the OTLP endpoint your agent actually uses comes from GET /api/runs/{run_id}/launch-profile.
Example
POST /api/agents
Synopsiskind is the one field that changes behaviour: it selects the replay adapter, the telemetry provider, and the launch provider together. The built-in values are openhands, claude-code, and codex; any other value, including none, falls back to the generic adapter.
Parameters. None.
Request. name is required. endpoint, kind, model, and otel are optional strings.
Example
PUT /api/agents/
Synopsisversion. Sending a different name renames the agent, keeping its id, its version history, and its runs. Results already recorded keep the version they were scored under, which is what lets you compare an agent against its earlier self.
Parameters.
Request. The same shape as
POST /api/agents. name is required and may differ from the path parameter.
Example
DELETE /api/agents/
Synopsis
Request. No body.
Response.
Example
GET /api/agents//history
Synopsisintel_disclosed is filled at read time from what the agent actually requested during each run.
Parameters.
Request. No body.
Response.
200 with an array of history entries carrying run_id, agent_id, created_at, overall, deterministic, judge, partial, partial_trigger, trace_ref, and a conditions object. 404 when no agent has that name.
Example
Missions and the library
GET /api/missions
Synopsissource. The library needs no account, key, or sign-in; when it is disconnected or unreachable you get the installed set only.
Parameters. None.
Request. No body.
Response. 200 with an array of catalog entries: source (your_own or library), mission_id, name, summary, proficiency, specialty, type (lab or static), skills, technologies, installed, and image. A static mission has no environment and therefore no image.
Example
GET /api/missions//manifest
Synopsismission.json for a mission — metadata, environment, attachments, artifacts, checks, rubric, intel, and terrain. XORCISE prefers the installed copy and falls back to the library’s. The rubric and the checks are visible here, so treat this endpoint as spoiler territory for anyone about to attempt the mission. Mission manifest documents every field.
Parameters.
Request. No body.
Response.
200 with the manifest. 404 when the mission is neither installed nor in the library.
Example
DELETE /api/missions/
Synopsis
Request. No body.
Response.
Example
POST /api/missions/ingest
Synopsisbundle_dir is required and is an absolute path on the XORCISE host.
202 with {"job_id": "<job_id>"}. Poll GET /api/missions/ingest/{job_id} for the outcome. A malformed body is 422; a bad manifest is not rejected here but surfaces as an error job status.
Example
GET /api/missions/ingest/
Synopsissince is a line cursor: pass the number of lines you have already consumed and you get only what is new, which is how the CLI and the web UI tail a build without re-reading it.
Parameters.
Request. No body.
Response.
200 with job_id, status (building, installed, or error), logs, and, once installed, slug and image. On failure detail carries the reason, and a manifest rejection reads as invalid mission.json — most often an unknown key, since every manifest model forbids extras. 404 when the job id is unknown.
Example
POST /api/missions//pull-jobs
Synopsis
Request. No body.
Response.
202 with {"job_id": "<job_id>"}. Poll GET /api/missions/pull-jobs/{job_id}.
Example
GET /api/missions/pull-jobs/
Synopsispercent and eta_seconds are null until Docker knows the total size, and neither is monotonic, because the daemon discovers layers as it goes. A pull that shows no byte movement is usually not stuck: cached layers and registry negotiation report status without transferring anything, which is what phase is for.
Parameters.
Request. No body.
Response.
200 with job_id, mission_id, status (pulling, installed, error, or cancelled), phase (resolving, pulling_image, downloading_bundle, installing, or done), bytes_current, bytes_total, percent, eta_seconds, detail, cancel_requested, and, once installed, the catalog entry. 404 when the job id is unknown.
Example
GET /api/missions/pull-jobs
Synopsis
Request. No body.
Response.
200 with the same job object as GET /api/missions/pull-jobs/{job_id}, or null when nothing is pulling. Omitting mission_id is 422.
Example
POST /api/missions/pull-jobs//cancel
Synopsis
Request. No body.
Response.
200 with the job object, cancel_requested set to true and status moving to cancelled. 404 when the job id is unknown.
Example
POST /api/missions//pull
Synopsis
Request. No body.
Response.
Example
GET /api/catalog/status
Synopsisdisconnected means you turned the library off; error means it is on but did not answer.
Parameters. None.
Request. No body.
Response. 200 with state (connected, error, or disconnected), last_sync, and message.
Example
Runs
GET /api/runs
Synopsiscreated and terminal, so a run that is currently being worked on reads as created.
Parameters. None.
Request. No body.
Response. 200 with an array of run records carrying run_id, agent_id, mission, name, state, created_at, budget_seconds, terminal_trigger, completed_at, model, sandbox_ref, agent_version, mission_version, source_agent, intel_policy, and last_telemetry_at. The run_control_key is never included.
Example
POST /api/runs
Synopsislab mission this takes seconds and can fail for real infrastructure reasons, which is why it has more failure codes than any other endpoint. A static mission deploys nothing.
Parameters. None.
Request. agent and mission are required and are the agent’s name and the mission’s id. budget_seconds bounds the run in wall-clock seconds. name overrides the generated run name. intel_policy is all or empty for every authored intel item, none for no intel, or a comma-separated list of intel ids such as i1,i3; it defaults to all.
Example
DELETE /api/runs/
Synopsis
Request. No body.
Response.
Example
POST /api/runs//terminate
Synopsispartial with a partial_trigger of operator, which is how a deliberately-stopped run is told apart from one that scored badly.
Parameters.
Request. No body.
Response.
Grading has not finished when this returns. Poll
GET /api/runs/{run_id}/result through the 202 window.
Example
GET /api/runs//environment
Synopsis
Request. No body.
Response.
200 with run_id, state, ready, and detail. state is none for a static mission with no environment, then starting, ready, failed, or released once the run is over and the environment is torn down. 404 when the run id is unknown.
Example
GET /api/runs//prompt
Synopsislaunch_mode rebases the run-control base URL in that text to where your agent actually runs, and getting it wrong is the most common reason an agent cannot check in.
Parameters.
An unrecognised
launch_mode is clamped to container rather than rejected, then narrowed to what the agent’s harness supports.
Request. No body.
Response. 200 with {"run_id": "...", "prompt": "..."}. 404 when the run id is unknown.
Example
GET /api/runs//launch-profile
Synopsisotel field on the agent record is not read. correlation reports how strongly traces will bind to this run: resource-attr for a harness with a telemetry provider, prompt-sentinel otherwise, which relies on the marker in the connect prompt surviving.
Parameters.
Request. No body.
Response.
200 with run_id, env, correlation, notes, fallback, launch_mode, launch_modes, command, shell_block, and tips. env is empty when no OTLP collector is configured. 404 when the run id is unknown.
Example
GET /api/runs//events
Synopsisnext_cursor from the previous response.
Parameters.
Request. No body.
Response.
200 with run_id, source_agent, adapter_name, adapter_version, events, counts, warnings, fallback, next_since, and next_cursor. fallback is true when the generic adapter handled the stream because the harness had no adapter. An unknown run id returns an empty stream rather than 404.
Example
GET /api/runs//events//raw
Synopsis
Request. No body.
Response.
200 with the source record as it arrived. 404 when the event id is not in that run.
Example
GET /api/runs//traces
Synopsissince is exclusive, so passing the last seq you saw gives you only what arrived after it. This is the endpoint behind xorcise run traces, and it is the right one for confirming that telemetry is reaching XORCISE at all.
Parameters.
Request. No body.
Response.
200 with {"run_id": "...", "records": [{"seq": 0, "payload": {...}}]}. An empty records array on a run whose agent is working usually means correlation failed rather than that nothing happened — check correlation on the launch profile.
Example
GET /api/runs//terrain2
Synopsis
Request. No body.
Response.
200 with the resolved terrain — nodes with their groups and states, edges, updates, and an attribution status. 404 when the run id is unknown. A mission that declares no terrain returns an empty map, not an error.
Example
GET /api/runs//artifacts
Synopsisflag.
Parameters.
Request. No body.
Response.
200 with an array of {name, kind, seq, payload}. 404 when the run id is unknown.
Example
Results
GET /api/runs//result
Synopsis0.5, so a run graded with no judge configured tops out at 50% and that is not a failure. judge_status is the diagnostic to read first when the judge half is zero.
Parameters.
Request. No body.
Response.
grade.judge_status is ok, model-not-configured, or unavailable, with the reason in judge_detail. grade also declares hard_fails, key_evidence, and major_deductions; no code path fills them and they are always empty. How grading works explains the arithmetic.
Example
GET /api/runs//report
Synopsis
Request. No body.
Response.
Example
GET /api/runs//stats
Synopsis0 on a run that emitted no telemetry, which is the normal reading rather than a fault.
Parameters.
Request. No body.
Response.
Example
POST /api/runs//regrade
Synopsis
Request. No body.
Response.
The new score arrives through the same
202 poll as the first grade, so a client needs no extra state. Calls are de-duplicated: pressing the button twice grades once.
Example
Run control
These are the endpoints your agent calls during a run. Every one requiresAuthorization: Bearer <run_control_key> and returns 401 without it. Two shared mappings apply throughout: once the run is over, any call returns 409, and a mission that cannot be read returns 404.
Connect any agent walks through the contract these endpoints form.
GET /api/runs//mission
Synopsis
Request. No body.
Response.
Example
POST /api/runs//artifacts
Synopsisflag — there is no separate flag endpoint. Content is inline text or JSON; binary upload is out of scope. Submitting a name twice records both, in order.
The name matters: deterministic checks look the artifact up by the name the agent used, so a misspelled name fails the check silently rather than raising an error here.
Parameters.
Request.
name and content are both required strings.
Example
GET /api/runs//intel
Synopsisintel_disclosed, so disclosed intel is visible in the conditions a score is compared under. remaining tells the agent how many intel items are left after this call. When the policy allows no intel, or all of it is spent, intel is null and remaining is 0.
Parameters.
Request. No body.
Response.
Example
POST /api/runs//complete
Synopsis
Request. No body.
Response.
Example
GET /api/runs//connect
Synopsislogin_server is already rewritten to an address the agent can reach, and ca_cert carries the local certificate authority when one is needed and is an empty string otherwise. Most agents use the script form below instead of assembling the join themselves.
Parameters.
Request. No body.
Response.
200 with login_server, join_key, and ca_cert. 401 without a valid bearer.
Example
GET /api/runs//join.sh
Synopsis
Request. No body.
Response.
200 with text/x-shellscript. 401 without a valid bearer.
Example
GET /api/runs//tailscale.tgz
Synopsis
Request. No body.
Response.
Example
GET /api/runs//attachments/
SynopsisX-Run-Key header and you get the bytes. The split keeps file bytes off the bearer-authenticated call so a download can be streamed or handed to another process without carrying the run token in a shell history.
Parameters.
Request. No body.
Response.
The mint response carries
name, url, expires_at, media_type, and sha256.
Example
Harnesses
GET /api/harnesses
Synopsiskind. Each descriptor carries a registration-time preview of how that harness is launched. The preview is a template, not a runnable command: run credentials, endpoints, and the mission text only exist once a run is created, so GET /api/runs/{run_id}/launch-profile remains authoritative for a real launch.
Parameters. None.
Request. No body.
Response. 200 with an array of descriptors carrying kind, display_name, description, model_hints, capabilities (the same profile the capabilities endpoint serves), and launch — launch_modes (host, container), command_template, model_flag, model_flag_anchor, tips, and mission_preamble.
Example
GET /api/harnesses/capabilities
Synopsisnotes carry the user-facing gap sentences for partially-supported kinds, rendered verbatim in the UI and the judge disclosure. verified is false only for the generic fallback adapter, whose profile is not audited against a real harness.
Parameters. None.
Request. No body.
Response. 200 with an array of profiles carrying adapter_name, adapter_version, verified, kinds (a mapping over every event kind to supported, partial, or unsupported), message_roles, and notes.
Example
System and configuration
GET /api/health
Synopsis200 with {"status": "ok", "service": "rest"}.
Example
GET /api/system
Synopsisxorcise status probes from your machine; this asks the instance, and the two disagreeing is itself the diagnosis.
Parameters. None.
Request. No body.
Response. 200 with role, topology (local or distributed), home, db_url, db_schema (head, behind, fresh, or unknown), planes, catalog, and remotes. On the default all role, planes carries four rows — rest, docker, headscale, and otlp — each with name, ok, detail, location, role, label, and state. A db_schema of behind means xorcise db upgrade is due.
Example
GET /api/config
Synopsiskey_hint shows the last four characters so you can tell which key is loaded. judge.configured is a presence check only and does not prove the key works; the test endpoints do that.
Parameters. None.
Request. No body.
Response. 200 with judge, terrain, catalog, network, and default_budget_seconds. terrain.uses_judge_default is true when no terrain override is set, which means terrain attribution calls the judge model. Configuration keys documents every field.
Example
PUT /api/config/model
Synopsisspan_max_tokens truncates each span, and transcript_max_tokens caps the whole prompt before it is sent.
Writes land in ~/.xorcise/.env as XORCISE_* variables, never in config.toml, so a value set here survives a restart but does not appear in your config file.
Parameters. None.
Request. All fields optional: model_name, base_url, key, transcript_max_tokens, span_max_tokens, tokenizer, timeout_seconds.
200 with the updated masked configuration, the same shape as GET /api/config. 422 when a value is out of range — the caps must be non-negative and timeout_seconds must be greater than zero.
Example
POST /api/config/model/test
Synopsis200 with ok, status (ok, not_configured, or error), model_name, and message. A failed call is still 200 — read ok, not the status code.
Example
PUT /api/config/terrain-model
Synopsismodel_name, base_url, key, transcript_max_tokens. Send empty strings to clear the override.
200 with the updated masked configuration. terrain.uses_judge_default becomes false once an override is set. 422 when transcript_max_tokens is not greater than zero.
Example
POST /api/config/terrain-model/test
Synopsis200 with the same shape as the judge test: ok, status, model_name, message.
Example
PUT /api/config/catalog
Synopsisconnected is required.
200 with the updated masked configuration. 422 when connected is missing or is not a boolean.
Example
PUT /api/config/network
Synopsisheadscale_url, advertise_host.
200 with the updated masked configuration.
Example
GET /api/fs/list
Synopsis
Request. No body.
Response.
200 with path, parent, and entries, each entry carrying name, path, and is_dir. 400 when the path does not exist or is not a directory.
Example
Related pages
- CLI reference — the same operations as commands, with exit codes and the web UI equivalent for each task.
- Security and isolation — what the missing authentication means in practice, and how to place a boundary around it.