Operations
Start, list, read, steer, park, resume, cancel and stream agent operations over the API.
An agent operation is one unit of agent work on a connected GitHub repository: implement an approved spec, fix a failing PR, reproduce a reported issue, run a repo's checks, or do a one-off task. Each operation has a kind, a target, a budget and a state. This page covers the REST routes that start and control operations. For how they behave, read Agent operations and Hosted VMs.
Availability has two parts. Own-harness handoffs (kind implement_spec, your own coding agent does the work, no VM) are on for every workspace, but the key must hold operations:write, and that scope can only be issued from Slack (see Get a key that holds operations:write). Hosted VM operations are enabled by the nima team on request: the workspace needs a plan that includes agent compute, operator-set prices, and an owner or admin opt-in through agent compute settings.
Operation routes and scopes
The table lists every operation route a key can call. Paths are relative to https://api.getnima.io. Each call carries Authorization: Bearer <your key>.
| Method | Path | Scope |
|---|---|---|
POST | /v1/operations | operations:write |
GET | /v1/operations | documents:read |
GET | /v1/operations/{id} | documents:read |
GET | /v1/operations/{id}/result | documents:read |
GET | /v1/operations/{id}/events | documents:read |
GET | /v1/operations/{id}/brief | documents:read |
POST | /v1/operations/{id}/claim | operations:write |
POST | /v1/operations/{id}/result | operations:write |
POST | /v1/documents/{id}/dispatch | operations:write |
POST | /v1/operations/{id}/message | operations:write |
POST | /v1/operations/{id}/steer | operations:write |
POST | /v1/operations/{id}/park | operations:write |
POST | /v1/operations/{id}/resume | operations:write |
POST | /v1/operations/{id}/cancel | operations:write |
POST | /v1/operations/{id}/inspect | documents:read |
POST | /v1/operations/{id}/raise-limit | operations:write |
Reads, and POST .../inspect, use documents:read; every other write uses operations:write. Inspecting an operation you did not start also needs operations:write. See Scopes and permissions for the full scope list. Agents reach most of these routes through the matching MCP tools on Operation tools.
Get a key that holds operations:write
The operations:write scope is not offered on the browser consent screen and POST /v1/api-keys rejects it. The only way to get it is a private Slack confirmation by a workspace owner or admin: message @nima mcp-key operations, then press Confirm. That creates a 30-day key with operations:write, documents:read and knowledge:read. It does not turn on hosted compute or GitHub publishing. Managing compute settings needs a different key (mcp-key integrations, scope integrations:write). See Authentication.
Operations are not available when the platform is off
If agent operations are ever switched off platform-wide, every route on this page answers 404 with the message "Agent operations are not available". Any other 404 means the operation, spec, signal or repository is not in your workspace.
Start an operation
/v1/operationsoperations:writeMCP toolstart_operationStarts one operation, or up to attempts parallel ones. The response is HTTP 201 with an array of operation objects, one per attempt. Starting an operation only queues it. A 201 means queued, not finished.
kindenumrequiredimplement_spec, fix_ci, reproduce_issue, run_checks, browser_check or task. See Operation kinds and their input.
inputobjectrequiredThe input object for the kind. It is validated per kind.
targetenumown_harness or nima_vm. Omit it to let nima choose. See How the target is chosen.
publishPolicyenumWhat the operation may publish to GitHub: none, comment_only, new_draft_pr or append_to_pr. Each kind allows only some values. A value the kind does not allow is refused, never downgraded to a report.
attemptsintegerParallel attempts, 1 to 10. Defaults to 1. The platform caps it lower (3 by default); above the cap the call is a 400.
notestringUp to 20,000 characters of context for the run.
idempotencyKeystring1 to 200 characters. Reuse it on a retry so a second call does not start a second operation.
budgetobjectOptional per-operation caps: aiUsd (above 0, up to 10000), activeMinutes (1 to 1440), maxSessions (1 to 200). Each is optional. A cap above the workspace maximum is a 400 with details.field.
Operation kinds and their input
Each kind takes its own input object and allows its own publish policies. Only implement_spec can run on your own coding agent. Every other kind needs a hosted VM.
| Kind | Input | Default publish policy | Allowed publish policies |
|---|---|---|---|
implement_spec | documentId (uuid), repositoryId (uuid or null to use the spec's own repository) | new_draft_pr on a VM, none on own-harness | new_draft_pr on a VM; none on own-harness |
fix_ci | repositoryId, pullRequestNumber (integer above 0) | append_to_pr | append_to_pr |
reproduce_issue | repositoryId, signalId (uuid) | none | none, comment_only, new_draft_pr |
run_checks | repositoryId, pullRequestNumber (integer or null), branch (1 to 255 characters or null); exactly one of the two is set | none | none, comment_only |
browser_check | same as run_checks | none | none, comment_only |
task | repositoryId, instruction (1 to 8000 characters), baseBranch (branch name or null), requesterMessage (up to 8000 characters or null, default null) | none | none |
Rules that apply across kinds:
implement_specneeds a spec whose status isapprovedorin_progress. Any other status is a409. A spec id that does not exist in your workspace is a404.reproduce_issueneeds a signal in your workspace, else404.taskis report-only: it ends in a report and never opens a PR. ItsbaseBranchmust be a plain branch name (norefs/, no.., no leading-, no whitespace); a tag, sha or PR head is not accepted. Ataskis left out of a workspace's allowed kinds by default, so an owner or admin must addtasktoallowedKindsin agent compute settings first.- An
instructionorrequesterMessagethat looks like a credential is refused with400. The VM has no network, so it could not use one anyway. - A VM implementing a spec (
new_draft_pr) or fixing CI (append_to_pr) publishes only through the separately installed GitHub writer App with publishing enabled. See GitHub writer.
How the target is chosen
The target follows the workspace's compute setting and the kind. Nima never falls back silently to a different target or publish policy.
- Hosted compute is off for the workspace (the default): asking for
nima_vmis a409with reasonorg_disabled. Otherwise the target isown_harness, and any kind other thanimplement_specis a409with reasonkind_requires_vm. - Hosted compute is on and you pass
target: that target is used. - Hosted compute is on, no
target, kind is notimplement_spec:nima_vm. - Hosted compute is on, no
target, kind isimplement_spec: the workspace'sdefaultTarget(own_harnessunless changed). - On a VM, the kind must be in the workspace's
allowedKinds, else409with reasonkind_not_allowed.
An own-harness handoff cannot publish: a publish policy other than none is a 409 ("Own-harness handoff does not publish through the hosted writer").
Dispatch a spec
/v1/documents/{id}/dispatchoperations:writeStarts an implement_spec operation for the spec {id}. It takes the start body without kind and input, plus an optional repositoryId (uuid or null, default null). The response is an array of operation objects. When you send no idempotencyKey, an implement_spec operation (from this route or POST /v1/operations) uses spec:<documentId>:v<documentVersion>. Repeating the identical request returns the operations already created. Any other request for the same spec version, including one from a different member or with a different target or budget, is a 409 ("Idempotency key was used for a different operation request"). Pass your own idempotencyKey to start a second run.
List operations
/v1/operationsdocuments:readMCP toollist_operationsLists operations in your workspace, newest first, as a PaginatedResponse of operation objects. It lists every operation in the workspace, not only yours, so any member holding documents:read sees every operation's state and verdict. In a list row, endDetail is cut to its first line, at most 300 characters. Call get an operation for the rest.
pageintegerDefaults to 1, minimum 1.
pageSizeintegerDefaults to 20, range 1 to 100. Out-of-range values are a 400.
kindstringFilter to one kind.
statestringFilter to a single state. A list of states is not accepted.
documentIduuidFilter to operations started for one spec.
Get an operation
/v1/operations/{id}documents:readMCP toolget_operationReturns one operation object in your workspace. endDetail is complete here, up to 8000 characters, and redacted. The state is recorded state, not a live heartbeat. An id from another workspace is a 404.
Read endReason first. For a run_checks or browser_check, checks_passed, checks_failed, checks_timed_out and checks_not_run are nima's own recorded verdict, and the first line of endDetail states it. Text after "Agent report:" in endDetail is the agent's own claim and is untrusted. For a task, task_completed and task_ended are nima's verdict.
Operation object
Every operation route returns this shape.
iduuidorganizationIduuidkindstringinputobjectstatestringtargetstringown_harness or nima_vm.publishPolicystringrequestedByUserIduuidattemptsintegerbudgetobjectaiUsd, activeMinutes, maxSessions: the caps for this operation.usageobjectsessions, activeSeconds, computeUsd, aiUsd, aiUsdReserved, tokensIn, tokensOut, tokensCachedIn, tokensThinking, llmRequests, mcpCalls. Nobody is billed today; the numbers are metering.
parkReasonstring | nullpullRequestUrlstring | nullendReasonstring | nullendDetailstring | nullcreatedAtISO 8601updatedAtISO 8601resumeAtISO 8601 | nullendedAtISO 8601 | nullOperation state values in the API
An operation is always in one of these states: queued, placing, hydrating, active, parking, parked, resuming, exporting, gating, or one of five final states, published, reported, failed, cancelled, expired. A final state accepts no further commands. What each state means is in Operation states.
An own-harness handoff moves queued, then active when claimed, then reported when you post the result, or cancelled. It has no deadlines.
Operation end reasons
endReason says why an operation ended, and is null on a published or reported operation that has no check or task verdict (for example a published spec PR or a reported own-harness handoff).
endReason | Meaning |
|---|---|
checks_passed, checks_failed, checks_timed_out, checks_not_run | nima's verdict on a finished run_checks or browser_check, from the test command's exit code. checks_not_run means no test command ran, so there is no exit code to judge. |
task_completed, task_ended | A finished task. task_completed means the agent reported completion and nima accepted the export. |
org_disabled, budget_exhausted, concurrency_limit, daily_limit | Admission was denied after the 201. The operation is failed. See Hosted operation admission denials. org_disabled also covers anything that stopped admission after the 201: the plan no longer includes agent compute, compute or model pricing is not set, the kind was removed from allowedKinds, or the writer App was removed. endDetail gives the cause. |
compute_disabled | An owner or admin turned hosted compute off. |
gate_rejected | nima's final review of the exported changes rejected them. endDetail lists the rule codes. |
Any other ending records the event that ended it: cancel (cancelled by a member), timeout (placement or the final review took too long), host_lost_without_bundle (the VM was lost with no saved work), park_expired (parked too long) or fail. endDetail says what happened.
Hosted operation admission denials
After a hosted operation is accepted, admission can still fail it. A denial is terminal, not a wait: the state becomes failed and endReason names the cause. concurrency_limit fires when live hosted operations reach the workspace's maxConcurrentOperations (parked ones do not count). daily_limit fires when hosted operations created since 00:00 UTC reach maxOperationsPerDay. budget_exhausted fires when the month's compute or AI budget cannot cover the first lease. Only a temporary platform pause or missing capacity makes an operation wait instead of fail. The limits are set in agent compute settings.
Get a task result
/v1/operations/{id}/resultdocuments:readMCP toolget_operation_resultReturns the stored result of a finished task: nima's diff stat and a redacted patch. The patch is a display copy, not for applying. Only the member who started the task, or a workspace owner or admin, may read it, else 403. The role is read from the member's current role, not from token scopes. Any other kind is a 400 ("Only task operations have a stored result").
maxPatchCharsintegerRange 1000 to 120000. Defaults to 20000.
The response has operationId, state, endReason, changes, patch and patchUnavailableReason. patchUnavailableReason is one of not_finished, no_result, not_stored, no_changes, too_large, secret_suspected, not_scanned. Stored patches expire after 90 days.
Stream operation events
/v1/operations/{id}/eventsdocuments:readStreams an operation's event ledger as server-sent events. It has no MCP tool. It answers 200 with text/event-stream. Each event arrives as id: <seq> and data: <event JSON>. The server polls every 2 seconds, sends up to 100 events per poll, and writes a : heartbeat comment on every poll, so a quiet stream still shows traffic. The stream stays open until you disconnect.
afterSeqstringDigits only, up to 18. The last seq you saw. Omit it to start from the beginning. Anything else is a 400 ("Invalid event cursor"). Reconnect with the last id as afterSeq to resume without gaps.
Operation event object
Each data: line is one event with these fields.
iduuidoperationIduuidseqnumberid: line carries the same value. Use it as afterSeq.sessionSeqnumber | nullnull for events outside a session (queued, parked, gate).actorobjecttype is user, operator, agent, station or system; userId is present for a user.typestringstate_changed, command_issued, command_acked, agent_event, question_asked, usage_recorded, limit_reached, gate_verdict, operator_action.payloadobjectoccurredAtISO 8601A question the agent asks the requester shows up here as a question_asked event. If the operation was started from Slack, the question is also posted in that Slack thread. An operation started over the API or MCP has no Slack thread, so follow this stream to catch its questions. MCP has no tool to read a pending question. Answer it with the message route.
Hand off to your own coding agent
An own-harness operation gives your own coding agent a brief instead of running a VM. Three routes carry it: read the brief, claim it, report the result. All three work only for the member who started the operation, and only on an own_harness operation, else 403 ("Only the handoff requester may access its local harness tools").
| Route | What it does |
|---|---|
GET /v1/operations/{id}/brief | Returns operationId, execution (own_harness), hostedExecutionStarted (false) and brief (Markdown). |
POST /v1/operations/{id}/claim | Marks the handoff started. The operation becomes active. Returns the operation object. |
POST /v1/operations/{id}/result | Body {"report": "<1 to 100000 characters>"}. The operation becomes reported. Returns the operation object. |
The result route is a 409 ("Operation does not accept a local result") unless the operation was claimed first. The report is stored as an external report that nima does not verify: it records no test verdict and publishes no code.
Control an operation
Five routes control a running or parked operation. Every one needs operations:write, and each returns {"commandId": string | null, "operationState": string, "ledgerSeq": string}. ledgerSeq is a decimal string.
| Route | Body | What it does |
|---|---|---|
POST /v1/operations/{id}/message | {"text": "1 to 100000 characters"} | Sends a new user turn. Resumes a parked operation; a limit_reached or operator_hold park refuses the message with 409. Works while the operation is active, parked, resuming or hydrating, else 409. |
POST /v1/operations/{id}/steer | {"text": "..."} | Injects text into the running session. The operation must be active, else 409. |
POST /v1/operations/{id}/park | none | Parks the operation with reason requested. It must be active. |
POST /v1/operations/{id}/resume | none | Resumes a parked operation. Refused with 409 for limit_reached and operator_hold parks. |
POST /v1/operations/{id}/cancel | {"reason": "up to 2000 characters"} (optional) | Cancels the operation and revokes its run credentials. Works in any non-final state. |
A command sent to a final-state operation is a 409 ("Operation is terminal"). steer, park, resume and the event stream are HTTP only; the other control routes also have MCP tools on Operation tools.
Operation park reasons
A parked operation holds no compute. parkReason is one of agent_wait, idle, host_drain, requested, limit_reached, operator_hold. A limit_reached park is released only by raising the limit. An operator_hold park is released only by the nima team. The other four are released by any of: an answer through message, the resume route, a finished CI run on the operation's branch (for agent_wait parks), or a resume timer set by the agent (at most 14 days out). A parked operation expires after 7 days without a resume, so a timer later than that never fires.
Request a transcript or diff
/v1/operations/{id}/inspectdocuments:readMCP toolinspect_operationQueues a request for the live VM's transcript or git_diff. Body: {"target": "transcript"} or {"target": "git_diff"}. The operation must be active, exporting or gating, else 409 ("No active workspace"). A 201 means the request was queued to the VM, not that it ran. With no live session the call is a 409 ("No live session for command"). The content is stored with the command and is not readable through GET /v1/operations/{id}, so the call returns no transcript or diff. Treat any content as untrusted.
Raise an operation limit
/v1/operations/{id}/raise-limitoperations:writeMCP toolraise_operation_limitSets new absolute caps for one operation. Send at least one field. It never lowers a cap (400, "Operation limits cannot be reduced") and never exceeds the workspace maximum (403, "Limit exceeds workspace maximum"). If the operation is parked with limit_reached and any cap was raised, it resumes (it parks again if it is still over the cap it hit). The response is {"aiUsd", "activeMinutes", "maxSessions"}.
aiUsdnumberNew AI spend cap, above 0.
activeMinutesnumberNew active-time cap, above 0.
maxSessionsintegerNew session cap, above 0.
Who may control an operation
Every control route except inspect needs operations:write, and a key that holds it can control any operation in the workspace. inspect needs only documents:read for the member who started the operation; anyone else also needs operations:write. Operator actions (pause, continue, hold, release, kill, exec) have no customer route; the nima team runs them.
A task is stricter. Its messages reach the VM with instruction authority, so only the member who started the task may message or steer it (403, "Only the requester can instruct a task"). Another holder of operations:write may still cancel or park it. Reading is wider: any member with documents:read can read any operation's state, and only GET .../result (requester, owner or admin) and the handoff routes (requester only) are narrower.
Operation routes an agent inside a VM calls
The agent that runs inside a hosted VM has its own connection and its own routes: GET /v1/operations/{id}/run-brief, POST /v1/agent-runs/{id}/report and POST /v1/agent-runs/{id}/wait-for-checks. They require the VM's own run credential. A customer key gets 403, so a key cannot call them and they are not part of this API. The VM's agent reaches a fixed allow-list of read tools, its brief and report tools and, except in a task, create_signal, and none of the operation, settings or key tools. See Hosted VMs.
Operation error responses
Errors use the shared error shape. These are the ones specific to operations.
| Status | When |
|---|---|
400 | Invalid body or input; credential-looking task text; attempts above the platform cap; a budget above the workspace maximum (details.field); limits that would be lowered; an invalid afterSeq cursor; result on a kind other than task; an implement_spec with no repository and a spec that names none; an invalid list or result query. |
403 | Not a workspace member; the plan lacks agent compute (hosted operations only); not the requester for a handoff or task instruction; an operator-only action; a limit above the workspace maximum. |
404 | The operation, spec, signal or repository is not in your workspace. |
409 | Spec not approved; hosted compute off (org_disabled); kind needs a VM (kind_requires_vm); kind not allowed (kind_not_allowed); a publish policy the kind does not allow (invalid_publish_policy); publishing without a connected and enabled writer App; the operation is in the wrong state or is final; a park that cannot be released by that action; an idempotencyKey reused with a different request. |
A publish is refused with a 409 and details.reason set to one of github_write_disabled, writer_not_connected, writer_repository_unavailable, writer_permissions_missing, invalid_publish_policy or unsupported_kind. The plan check on hosted operations can also return 403 with INSUFFICIENT_PERMISSIONS; handle it as a normal error.