Skip to main content

Workforce Agents API

Drive a single agent over HTTP. Every request is authenticated with that agent's API key, sent as a bearer token:

text
Authorization: Bearer oct_agt_...

All endpoints are scoped to one agent through the {agentId} path segment - find the agent ID in the agent's page URL in the dashboard. A key only works for the agent it was created for.

Get an agent

Read whether the agent can take a run now, what it is working on, and the state of its computer.

text
GET /api/v1/workforce/agents/{agentId}

Response

json
{
  "agentId": "cm5abc123def456ghi",
  "name": "Avery Stone",
  "status": "busy",
  "unavailableReason": null,
  "currentThreadId": "cm5xyz123abc456def",
  "queuedThreads": 0,
  "computer": { "kind": "dedicated", "os": "linux", "state": "running" },
  "definition": { "slug": "research-analyst", "version": 12 },
  "capabilities": { "memory": true, "credentials": false }
}
FieldTypeDescription
statusstringidle (ready for a run), busy (a run is in flight or queued), or unavailable (a new run cannot start - see unavailableReason)
unavailableReasonstring | nullpaused (the owner paused the agent), computer_down (its computer cannot be reached), or cli_only (it runs only from the Agent CLI)
currentThreadIdstring | nullThe thread holding the agent: the running one, else the oldest dispatched or queued one
queuedThreadsnumberThreads waiting behind the current one
computerobjectkind (dedicated, e2b, mac, or cli), os (linux, windows, or macos), and state (see below)
definitionobject | nullThe pre-built agent the agent was hired from (slug) and the version of its definition (version); null for an agent not hired from a pre-built one
capabilitiesobjectThe capabilities the agent's definition declares, each with whether it is enabled

The computer state is running, starting, paused (stopped; it resumes on the next run), down, or none (no computer yet, or the caller's own machine for a CLI agent). The status is computed on every read, so it reflects a dispatch immediately. New values may be added to any of these fields; treat an unrecognized one as unknown.

Example

bash
curl https://octavus.ai/api/v1/workforce/agents/AGENT_ID \
  -H "Authorization: Bearer oct_agt_..."

Start a thread

Dispatch a message to the agent. This starts a new thread and returns immediately.

text
POST /api/v1/workforce/agents/{agentId}/threads

Request Body

json
{
  "message": "Summarize the latest sales report"
}
FieldTypeRequiredDescription
messagestringYesThe task or message for the agent
filesFileReference[]NoHosted file attachments
configRunConfigNoPer-run configuration for this thread (see below). Omitted fields inherit the agent's stored settings.
ifIdlebooleanNoRefuse instead of queuing when the agent already has a run in flight or queued (see Only run on an idle agent)

RunConfig

Configure how the agent runs for this thread without changing its dashboard settings - the same shape the Agent CLI accepts. Set on thread creation only; a thread's run config is fixed once it starts. The server validates and bounds it before the run starts, so an unrunnable model or an undeclared capability is rejected up front.

FieldTypeDescription
modelstringPrimary model for the run, provider/model-id (e.g. anthropic/claude-sonnet-5).
backupModelstringBackup model, provider/model-id.
thinkingstringThinking/reasoning effort: off, low, medium, high, xhigh, max, or auto (the model decides); see Thinking.
capabilitiesRecord<string, boolean>Per-capability toggles (slug -> enabled). Unlisted capabilities inherit the agent default.
recordbooleanRecord this run's execution view (working process + computer) to a shareable video.
recordVisibilitystringWhere a recording is stored: private (default) or public (permanent URL). Ignored without record.

Any model from the model catalog is allowed, as long as a key resolves for its provider (your project/org key or the platform default), and so is any openrouter/... model when your project or org has its own OpenRouter key; any other model is rejected (see Unsupported Models). Capability toggles are bounded to the capabilities the agent's protocol declares.

Response

Returns 201 (200 when an idempotency key returns an earlier request's thread).

json
{
  "threadId": "cm5xyz123abc456def",
  "status": "pending"
}

Poll the Get a thread endpoint until the status is terminal.

Retry safely with an idempotency key

Send an Idempotency-Key header with a value unique to the task, such as a job id or a UUID (1-255 printable ASCII characters, no spaces):

text
Idempotency-Key: job-2026-03-04-reconcile-march

If you send the same key with the same request again - for example because the first response was lost - you get back 200 with the thread the first request created and an Idempotent-Replayed: true response header, and no second thread is started. The same key with a different request body is rejected with 409 IDEMPOTENCY_KEY_REUSED. A retry that arrives while the first request is still being processed can get 409 IDEMPOTENCY_KEY_IN_PROGRESS with a Retry-After header; retry after that delay to get the thread. A key stays bound to its thread for the thread's lifetime.

Only run on an idle agent

An agent runs one task at a time, so a new thread normally waits behind a run already in flight (queued). With "ifIdle": true in the body, a dispatch to an agent that already has a run in flight or queued is refused with 409 AGENT_BUSY instead, and nothing is created:

json
{
  "error": "The agent already has a run in flight or queued",
  "code": "AGENT_BUSY",
  "currentThreadId": "cm5xyz123abc456def"
}

currentThreadId names the thread holding the agent; it can be null when another dispatch is just starting. To check without dispatching, use Get an agent. ifIdle looks at this agent's threads only: a Mac shared with other agents may be running one of theirs, in which case the new thread waits its turn on that computer.

Example

bash
curl -X POST https://octavus.ai/api/v1/workforce/agents/AGENT_ID/threads \
  -H "Authorization: Bearer oct_agt_..." \
  -H "Content-Type: application/json" \
  -d '{ "message": "Summarize the latest sales report" }'

With a per-run config (model, thinking, capability toggles, recording):

bash
curl -X POST https://octavus.ai/api/v1/workforce/agents/AGENT_ID/threads \
  -H "Authorization: Bearer oct_agt_..." \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Summarize the latest sales report",
    "config": {
      "model": "anthropic/claude-sonnet-5",
      "thinking": "high",
      "capabilities": { "memory": false },
      "record": true
    }
  }'

Get a thread

Read a thread's status and messages. Poll this until the run finishes.

text
GET /api/v1/workforce/agents/{agentId}/threads/{threadId}

Response

json
{
  "threadId": "cm5xyz123abc456def",
  "status": "completed",
  "failureReason": null,
  "failureType": null,
  "messages": [],
  "runConfig": { "model": "anthropic/claude-sonnet-5", "thinking": "high" },
  "usage": {
    "currency": "USD",
    "costUsd": 0.0421,
    "totalFeeUsd": 0.0455,
    "byok": false,
    "inputTokens": 18234,
    "outputTokens": 1207,
    "totalTokens": 19441
  },
  "recording": null,
  "startedAt": "2026-03-04T10:12:03.418Z",
  "completedAt": "2026-03-04T10:19:47.902Z",
  "provenance": {
    "platform": {
      "deployments": [
        {
          "deploymentId": "dpl_9Xa2mQ7kLp3RtV8Wn",
          "commitSha": "4f1c2e9a7b3d5f60817c9e2a4b6d8f0a1c3e5b79",
          "firstSeenAt": "2026-03-04T10:12:03.912Z"
        }
      ]
    },
    "definition": { "slug": "research-analyst", "version": 12 },
    "computer": {
      "kind": "dedicated",
      "os": "linux",
      "runtimeVersion": "75",
      "imageVersion": "46",
      "region": "us-east-1"
    }
  }
}
FieldTypeDescription
threadIdstringThe thread identifier
statusstringidle, queued, pending, running, completed, failed, cancelled, or blocked
failureReasonstring | nullWhy the run stopped, when status is failed or blocked (a usage/spending limit)
failureTypestring | nullThe class of failureReason - see Failure types. Null when there is no failure.
messagesUIMessage[]The conversation - see UIMessage parts
runConfigobject | nullThe effective per-run config the thread ran under (model, backupModel, thinking, capabilities). Null for a run with no per-run config.
usageobject | nullPer-run cost + token summary: costUsd (model/provider cost), totalFeeUsd (provider + bandwidth fee), byok, and input/output/total tokens.
recordingobject | nullThe execution recording when the run was recorded: status, visibility, a playable url once ready, and error. Null when not recorded.
startedAtstring | nullWhen the thread's first run started on the agent's computer (ISO 8601). Null while queued or pending, or when it never started.
completedAtstring | nullWhen the thread's latest run reached its terminal status (ISO 8601). Null while a run is in flight (a follow-up clears it). With startedAt, the thread's own duration - for a single-run thread, the run's - so there is no need to time your polls.
provenanceobject | nullWhat the thread ran on, recorded while it ran - see Provenance. Null until a step of the thread has run (for example, a blocked thread).

Keep polling while the status is pending, queued, or running. Stop when it is completed, failed, cancelled, or blocked. A blocked thread means a usage or spending limit was reached (see failureReason); the thread is created even when the run is blocked before it starts, so a blocked attempt is still a pollable thread rather than an error. If the run was recorded and you need the video, keep polling until the recording is final too (see Recording).

Recording

A recorded run's recording has its own lifecycle, and it settles shortly after the run ends: a thread can read completed while its recording is still processing. To get the video, keep polling the thread until recording.status is ready, failed, or unavailable. It normally takes a few seconds.

StatusMeaning
requestedRecording was requested, but the run has not started yet (for example, it is queued behind another run)
recordingThe run is being recorded
processingThe run has ended; the video is being finalized
readyFinal. url plays the video
failedFinal. The video could not be produced; error says why
unavailableFinal. The recording never started (for example, the plan does not include recording); error says why

recording is null only when the run was not recorded.

Provenance

provenance describes the run itself, not whatever is current when you read it, so a result can be attributed to exactly what produced it:

FieldTypeDescription
platform.deploymentsobject[]The platform builds that executed the thread's steps, oldest first: deploymentId, commitSha, and firstSeenAt. Usually one; more than one means a new build was deployed mid-thread.
definitionobject | nullThe pre-built agent the agent was hired from (slug) and the version of its definition (version), as of the thread's latest step. Null for an agent not hired from a pre-built one.
computerobject | nullThe computer, as of the thread's latest step: kind, os, runtimeVersion, imageVersion, and region. Null when no step reached a computer.

computer.kind is dedicated, e2b, mac, or cli. runtimeVersion is the computer's runtime release (the desktop app version on a Mac, the Agent CLI version for a CLI run), and imageVersion is the image a dedicated computer launched from or the sandbox template of an E2B computer.

Failure types

failureType classifies why a failed or blocked run stopped, so you can tell the agent failing at its task from a problem reaching it without parsing failureReason.

TypeMeaning
computer_unreachableThe agent's computer could not be reached, so the run never started
networkA connectivity failure on the way to the run: it could not be handed off, or its connection dropped mid-run
transportThe run's connection to Octavus kept dropping and could not be recovered
platformA failure on the Octavus side
providerThe model provider returned an error, was overloaded, or rate limited the run
timeoutA request timed out, usually to the model provider
billingA plan allowance was reached; accompanies blocked
limitA spending limit was reached; accompanies blocked
session_too_largeThe thread can no longer be stored; continue in a new thread
unknownThe failure could not be classified

Only computer_unreachable guarantees the run never started. Every other type can follow work the agent already did, so check messages before retrying a task with side effects. New types may be added; treat an unrecognized value like unknown.

Example

bash
curl https://octavus.ai/api/v1/workforce/agents/AGENT_ID/threads/THREAD_ID \
  -H "Authorization: Bearer oct_agt_..."

Follow up in a thread

Send another message into an existing thread. If the agent is still working the message runs after the current turn finishes; otherwise it starts immediately.

text
POST /api/v1/workforce/agents/{agentId}/threads/{threadId}/messages

Request Body

json
{
  "message": "Now turn that into a slide deck"
}
FieldTypeRequiredDescription
messagestringYesThe follow-up message
filesFileReference[]NoHosted file attachments

Response

Returns 202.

json
{
  "threadId": "cm5xyz123abc456def",
  "status": "running"
}

Poll Get a thread for the new run's result.

Example

bash
curl -X POST https://octavus.ai/api/v1/workforce/agents/AGENT_ID/threads/THREAD_ID/messages \
  -H "Authorization: Bearer oct_agt_..." \
  -H "Content-Type: application/json" \
  -d '{ "message": "Now turn that into a slide deck" }'

Cancel a thread's run

Stop a thread's in-flight run - the programmatic equivalent of the dashboard Stop button. The executor is signaled to abort, so a run that has overrun stops instead of continuing to bill. Idempotent: a thread that has already finished (or never started) is left as-is.

text
POST /api/v1/workforce/agents/{agentId}/threads/{threadId}/cancel

Response

Returns 200.

json
{
  "threadId": "cm5xyz123abc456def",
  "status": "cancelled"
}

status is cancelled once the run was in flight, or the thread's unchanged status when it had already finished. The executor abort completes shortly after; poll Get a thread for the settled state.

Example

bash
curl -X POST https://octavus.ai/api/v1/workforce/agents/AGENT_ID/threads/THREAD_ID/cancel \
  -H "Authorization: Bearer oct_agt_..."

Errors

Errors return { "error": string, "code": string } with an HTTP status:

StatusMeaning
400The request is invalid (for example a malformed body or Idempotency-Key)
401Missing or invalid API key
402The agent is blocked by a usage or spending limit
403The key is not authorized for this agent
404The thread does not exist for this agent
409AGENT_BUSY (an ifIdle dispatch to a busy agent), IDEMPOTENCY_KEY_REUSED (a key reused with a different request), IDEMPOTENCY_KEY_IN_PROGRESS (the key's first request is still being processed - retry shortly), or AGENT_PAUSED (the agent is paused)