docs
API

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>.

MethodPathScope
POST/v1/operationsoperations:write
GET/v1/operationsdocuments:read
GET/v1/operations/{id}documents:read
GET/v1/operations/{id}/resultdocuments:read
GET/v1/operations/{id}/eventsdocuments:read
GET/v1/operations/{id}/briefdocuments:read
POST/v1/operations/{id}/claimoperations:write
POST/v1/operations/{id}/resultoperations:write
POST/v1/documents/{id}/dispatchoperations:write
POST/v1/operations/{id}/messageoperations:write
POST/v1/operations/{id}/steeroperations:write
POST/v1/operations/{id}/parkoperations:write
POST/v1/operations/{id}/resumeoperations:write
POST/v1/operations/{id}/canceloperations:write
POST/v1/operations/{id}/inspectdocuments:read
POST/v1/operations/{id}/raise-limitoperations: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

POST/v1/operations
scopeoperations:writeMCP toolstart_operation

Starts 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.

kindenumrequired

implement_spec, fix_ci, reproduce_issue, run_checks, browser_check or task. See Operation kinds and their input.

inputobjectrequired

The input object for the kind. It is validated per kind.

targetenum

own_harness or nima_vm. Omit it to let nima choose. See How the target is chosen.

publishPolicyenum

What 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.

attemptsinteger

Parallel attempts, 1 to 10. Defaults to 1. The platform caps it lower (3 by default); above the cap the call is a 400.

notestring

Up to 20,000 characters of context for the run.

idempotencyKeystring

1 to 200 characters. Reuse it on a retry so a second call does not start a second operation.

budgetobject

Optional 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.

KindInputDefault publish policyAllowed publish policies
implement_specdocumentId (uuid), repositoryId (uuid or null to use the spec's own repository)new_draft_pr on a VM, none on own-harnessnew_draft_pr on a VM; none on own-harness
fix_cirepositoryId, pullRequestNumber (integer above 0)append_to_prappend_to_pr
reproduce_issuerepositoryId, signalId (uuid)nonenone, comment_only, new_draft_pr
run_checksrepositoryId, pullRequestNumber (integer or null), branch (1 to 255 characters or null); exactly one of the two is setnonenone, comment_only
browser_checksame as run_checksnonenone, comment_only
taskrepositoryId, instruction (1 to 8000 characters), baseBranch (branch name or null), requesterMessage (up to 8000 characters or null, default null)nonenone

Rules that apply across kinds:

  • implement_spec needs a spec whose status is approved or in_progress. Any other status is a 409. A spec id that does not exist in your workspace is a 404.
  • reproduce_issue needs a signal in your workspace, else 404.
  • task is report-only: it ends in a report and never opens a PR. Its baseBranch must be a plain branch name (no refs/, no .., no leading -, no whitespace); a tag, sha or PR head is not accepted. A task is left out of a workspace's allowed kinds by default, so an owner or admin must add task to allowedKinds in agent compute settings first.
  • An instruction or requesterMessage that looks like a credential is refused with 400. 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.

  1. Hosted compute is off for the workspace (the default): asking for nima_vm is a 409 with reason org_disabled. Otherwise the target is own_harness, and any kind other than implement_spec is a 409 with reason kind_requires_vm.
  2. Hosted compute is on and you pass target: that target is used.
  3. Hosted compute is on, no target, kind is not implement_spec: nima_vm.
  4. Hosted compute is on, no target, kind is implement_spec: the workspace's defaultTarget (own_harness unless changed).
  5. On a VM, the kind must be in the workspace's allowedKinds, else 409 with reason kind_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

POST/v1/documents/{id}/dispatch
scopeoperations:write

Starts 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

GET/v1/operations
scopedocuments:readMCP toollist_operations

Lists 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.

pageinteger

Defaults to 1, minimum 1.

pageSizeinteger

Defaults to 20, range 1 to 100. Out-of-range values are a 400.

kindstring

Filter to one kind.

statestring

Filter to a single state. A list of states is not accepted.

documentIduuid

Filter to operations started for one spec.

Get an operation

GET/v1/operations/{id}
scopedocuments:readMCP toolget_operation

Returns 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.

iduuid
The operation id.
organizationIduuid
Your workspace.
kindstring
One of the kinds.
inputobject
The input, as parsed for the kind.
statestring
One of the operation states.
targetstring
own_harness or nima_vm.
publishPolicystring
The publish policy in effect.
requestedByUserIduuid
The member who started it.
attemptsinteger
The number of parallel attempts requested.
budgetobject
aiUsd, activeMinutes, maxSessions: the caps for this operation.
usageobject

sessions, activeSeconds, computeUsd, aiUsd, aiUsdReserved, tokensIn, tokensOut, tokensCachedIn, tokensThinking, llmRequests, mcpCalls. Nobody is billed today; the numbers are metering.

parkReasonstring | null
Set while the operation is parking or parked. See Operation park reasons.
pullRequestUrlstring | null
Set once nima published a PR or comment.
endReasonstring | null
Why the operation ended. See Operation end reasons.
endDetailstring | null
The verdict line, then the agent's report and a log tail. Redacted.
createdAtISO 8601
UTC.
updatedAtISO 8601
UTC.
resumeAtISO 8601 | null
Set only while parked with a timer.
endedAtISO 8601 | null
Set when the operation reaches a final state.

Operation 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).

endReasonMeaning
checks_passed, checks_failed, checks_timed_out, checks_not_runnima'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_endedA finished task. task_completed means the agent reported completion and nima accepted the export.
org_disabled, budget_exhausted, concurrency_limit, daily_limitAdmission 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_disabledAn owner or admin turned hosted compute off.
gate_rejectednima'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

GET/v1/operations/{id}/result
scopedocuments:readMCP toolget_operation_result

Returns 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").

maxPatchCharsinteger

Range 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

GET/v1/operations/{id}/events
scopedocuments:read

Streams 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.

afterSeqstring

Digits 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.

iduuid
The event id.
operationIduuid
The operation.
seqnumber
Position in the operation's ledger, from 1. The SSE id: line carries the same value. Use it as afterSeq.
sessionSeqnumber | null
The session the event happened in. null for events outside a session (queued, parked, gate).
actorobject
type is user, operator, agent, station or system; userId is present for a user.
typestring
Includes state_changed, command_issued, command_acked, agent_event, question_asked, usage_recorded, limit_reached, gate_verdict, operator_action.
payloadobject
Event data. It can carry text written by the agent, so treat it as untrusted.
occurredAtISO 8601
UTC.

A 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").

RouteWhat it does
GET /v1/operations/{id}/briefReturns operationId, execution (own_harness), hostedExecutionStarted (false) and brief (Markdown).
POST /v1/operations/{id}/claimMarks the handoff started. The operation becomes active. Returns the operation object.
POST /v1/operations/{id}/resultBody {"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.

RouteBodyWhat 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}/parknoneParks the operation with reason requested. It must be active.
POST /v1/operations/{id}/resumenoneResumes 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

POST/v1/operations/{id}/inspect
scopedocuments:readMCP toolinspect_operation

Queues 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

POST/v1/operations/{id}/raise-limit
scopeoperations:writeMCP toolraise_operation_limit

Sets 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"}.

aiUsdnumber

New AI spend cap, above 0.

activeMinutesnumber

New active-time cap, above 0.

maxSessionsinteger

New 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.

StatusWhen
400Invalid 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.
403Not 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.
404The operation, spec, signal or repository is not in your workspace.
409Spec 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.