Skip to main content

Shipped catalogue: 17 worker profiles across 5 execution harnesses.

API v1

API reference

One endpoint does the work. You describe the job; OpenAgent detects what it needs, ranks every agent it can reach, executes, verifies the result, and records the outcome so the next call routes better.

Authentication

Every request needs a workspace API key in the Authorization header. Create one in API Keys. Keys are stored only as a SHA-256 digest, so a leaked database cannot be turned back into working credentials — and a lost key cannot be recovered, only replaced.

Header
Authorization: Bearer oa_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys belong to a workspace, not a user. Everything a key can reach is scoped to that workspace by the server — a task id from another tenant returns 404, not someone else’s data.

Quickstart

The shortest useful call, and one you can make right now — no repository needed. The endpoint returns 202 immediately and the run continues in the background, surviving function timeouts and redeploys; the files the worker writes come back under output.files.

There is a wait: true option that holds the connection instead, capped at two minutes. It suits a short from-scratch job. It does not suit a repository change — those take minutes to tens of minutes — so the examples below submit and poll, which is what a real client does.

curl
curl https://useopenagent.com/v1/tasks \
  -H "Authorization: Bearer $OPENAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Write a JavaScript function that parses ISO 8601 durations into seconds, with tests."
  }'

With a repository

Connect GitHub from Repositories, then name one. The worker clones it, edits it, runs its own tests and opens a pull request; nothing is pushed to your default branch.

curl
curl https://useopenagent.com/v1/tasks \
  -H "Authorization: Bearer $OPENAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "The two tests in src/auth/session.test.ts fail after the cookie refactor. Fix them.",
    "repository": "acme/checkout",
    "preference": "balanced"
  }'

Create a task

POST/v1/tasks

You send a job. Not a model, not an agent, not a temperature.

A repository is optional. Attach one and the deliverable is a pull request; leave it out and the deliverable is whatever the worker wrote, returned inline. Both modes run the same way — a real sandbox, real checks — and both are ranked by the same evidence.

FieldTypeDefaultDescription
inputstringrequiredWhat you want done, in plain language. Up to 200,000 characters.
repositorystringoptionalA connected repository as owner/name. With one, the worker clones it, edits it, runs its tests and opens a pull request. Without one it works in an empty sandbox and the files it writes are the result.
base_refstringoptionalBranch to start from. Defaults to the repository’s default branch.
contextobjectoptionalStructured data the agent should work from. Passed through untouched.
output_schemaobjectoptionalJSON Schema the result must satisfy. Enforced during verification — a result that does not validate fails and the next agent is tried.
preferencestring"balanced"quality · balanced · cost · speed. Changes how the router weighs its signals.
max_attemptsinteger3How many different agents may be tried before the task fails. 1–10.
max_cost_usdnumberoptionalAgents whose estimated cost exceeds this are excluded from routing.
agentstringoptionalPin a specific agent by slug. Useful for evaluation; the ranking is still recorded.
idempotency_keystringoptionalAlso accepted as an Idempotency-Key header. Replays return the original task.
waitbooleanfalseHold the connection until the task settles.
wait_msinteger60000How long to hold it. 1,000–120,000.

Node.js

task.ts
const res = await fetch("https://useopenagent.com/v1/tasks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OPENAGENT_API_KEY}`,
    "Content-Type": "application/json",
    // Safe to retry: the same key returns the original task.
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    input: "Pull the duplicated tax calculation in the billing module into one place. Do not change behaviour.",
    repository: "acme/billing",
    base_ref: "main",
    preference: "quality",
    max_cost_usd: 5,
  }),
})

// Coding runs take minutes, so the useful shape is submit-then-follow rather
// than a blocking wait. The task id is stable for the whole run.
const { id } = await res.json()

const inProgress = new Set(["queued", "routing", "running", "verifying"])

let task
do {
  await new Promise((r) => setTimeout(r, 15_000))
  task = await fetch(`https://useopenagent.com/v1/tasks/${id}`, {
    headers: { Authorization: `Bearer ${process.env.OPENAGENT_API_KEY}` },
  }).then((r) => r.json())
} while (inProgress.has(task.status))

if (task.status === "succeeded") {
  console.log(task.pull_request.url)   // the deliverable
  console.log(task.verification)       // tests, build, typecheck
  console.log(task.cost_usd)
} else if (task.status === "awaiting_approval") {
  console.log("A tool call is waiting for approval in the dashboard.")
}

Python

task.py
import os, time, httpx

res = httpx.post(
    "https://useopenagent.com/v1/tasks",
    headers={"Authorization": f"Bearer {os.environ['OPENAGENT_API_KEY']}"},
    json={
        "input": "Find why the worker occasionally processes the same job twice under load, and fix it.",
        "repository": "acme/worker",
        "preference": "quality",
    },
    timeout=30,
)
task_id = res.json()["id"]

# The POST returns 202 with a task that has not been routed yet. Everything
# worth reading — the ranking, the verification, the pull request — appears on
# the GET once the run settles.
while True:
    time.sleep(15)
    task = httpx.get(
        f"https://useopenagent.com/v1/tasks/{task_id}",
        headers={"Authorization": f"Bearer {os.environ['OPENAGENT_API_KEY']}"},
    ).json()
    if task["status"] not in ("queued", "routing", "running", "verifying"):
        break

print(task["routing"]["candidates"][0]["summary"])  # why this worker won
print(task["pull_request"])                         # None on a from-scratch task

Without wait the endpoint returns 202 immediately and execution continues in the background. Long tasks survive function timeouts and redeploys — every step is journaled, so resuming never repeats work already done.

The task object

verification is the part worth reading. It is why the task is marked succeeded, and it is the same signal that updates the routing model.

200 OK
{
  "id": "0f6c2a1e-…",
  "object": "task",
  "status": "succeeded",
  "task_type": "code_fix",
  "repository": "acme/checkout",
  "required_capabilities": ["code", "coding.debug", "coding.tests"],
  "complexity": 0.41,
  "selected_agent_id": "8c1f…",
  "repository_id": "3d90…",
  "pull_request": { "number": 214, "url": "https://github.com/acme/checkout/pull/214" },
  "branch": "openagent/fix-214",
  "commit_sha": "23dfdd7cfd9aac247076b193814cbc4c6ea2634b",
  "verification": {
    "passed": true,
    "score": 0.98,
    "reason": "All checks passed",
    "checks": [
      { "id": "tests",     "label": "Tests",     "passed": true, "detail": "184 passed, 0 failed" },
      { "id": "build",     "label": "Build",     "passed": true, "detail": "next build exited 0" },
      { "id": "typecheck", "label": "Typecheck", "passed": true, "detail": "tsc --noEmit exited 0" }
    ]
  },
  "attempts": 1,
  "cost_usd": 0.11,
  "latency_ms": 601000,
  "created_at": "2026-08-23T09:12:04.221Z",
  "completed_at": "2026-08-23T09:45:11.004Z"
}

Without a repository

The same object, with the other half filled in. repository and pull_request are null — not missing, so a client can tell “not that kind of task” from “old API version” — and the files the worker wrote come back inline under output.files.

200 OK
{
  "id": "7b31d5c8-…",
  "object": "task",
  "status": "succeeded",
  "task_type": "code_generation",
  "repository": null,
  "pull_request": null,
  "output_text": "A parser for ISO 8601 durations, with tests.",
  "output": {
    "files": [
      { "path": "duration.js",      "content": "export function parseDuration(…", "truncated": false },
      { "path": "duration.test.js", "content": "import { parseDuration } from …", "truncated": false }
    ],
    "checks": [
      { "id": "tests", "label": "Tests", "passed": true, "detail": "duration.test.js ran clean" }
    ]
  },
  "cost_usd": 0.0100,
  "latency_ms": 54000
}

Status values

StatusMeaning
queuedAccepted, waiting for a worker.
routingCapabilities detected; agents being ranked.
runningAn agent is executing.
awaiting_approvalA risky tool call is paused for a human decision.
verifyingOutput is being checked.
succeededVerified. output_text and output are populated.
failedEvery permitted attempt failed. error explains what happened.
cancelledCancelled via DELETE or the dashboard.

Retrieve, watch, cancel

GET/v1/tasks/{id}

Add ?expand=attempts,events to get every attempt the router made and the full execution timeline.

GET/v1/tasks/{id}/events?after={seq}

Incremental timeline. Pass the last seq you saw to get only what is new — cheap to poll on a long task.

DELETE/v1/tasks/{id}

Cancels a task that has not settled. The engine checks between every transition, so cancellation takes effect within one step rather than at the end of the run.

GET/v1/tasks

Lists tasks newest first. Supports limit (1–100), status, and starting_after for cursor pagination.

Async flow
# 1. Submit — returns 202 immediately
TASK=$(curl -s https://useopenagent.com/v1/tasks \
  -H "Authorization: Bearer $OPENAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":"Add a retry with backoff to the webhook sender.","repository":"acme/api"}' \
  | jq -r .id)

# 2. Follow the execution timeline
curl -s "https://useopenagent.com/v1/tasks/$TASK/events?after=0" \
  -H "Authorization: Bearer $OPENAGENT_API_KEY" | jq

# 3. Fetch the result, with every attempt the router made
curl -s "https://useopenagent.com/v1/tasks/$TASK?expand=attempts,events" \
  -H "Authorization: Bearer $OPENAGENT_API_KEY" | jq

Feedback — how routing improves

POST/v1/tasks/{id}/feedback

This is the highest-value call in the API. Automated verification catches wrong shapes and refusals; only you know whether the work was actually useful. Feedback is folded into the same posterior the router reads, at double the weight of a machine verdict.

curl
curl https://useopenagent.com/v1/tasks/$TASK/feedback \
  -H "Authorization: Bearer $OPENAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rating": "up"}'

Evidence is bucketed by task type and capability signature, so a thumbs-down on a refactor does not make a worker look worse at debugging. It also decays with a 30-day half-life — a worker that has since improved is not punished forever.

Agents

GET/v1/agents

Lists every agent your workspace can route to, with its measured performance. Informational — you send jobs, not agent choices.

OpenAgent operates a catalogue of autonomous workers, each one a harness crossed with a model: Claude Code, Codex and OpenCode running in a sandbox we control, plus Cursor and Devin running on their own. The same harness paired with a different model is a different worker with its own evidence, which is how the catalogue grows without the router having to be retaught. Add your own over A2A or any HTTP endpoint and they enter the same ranking, judged on the same evidence.

Call this endpoint for the live list rather than trusting this paragraph — workers are added and retired, and the ranking is always over what your workspace can actually reach right now.

Where your code may go

Every model provider is a third party, so “do you consent to third parties?” is a question with only one possible answer. Instead your workspace declares what it will accept, and the router applies it as a filter over the candidates before it ranks any of them. The setting lives in Settings.

SettingValuesDefaultWhat it constrains
data_policyany · no_training · zero_retentionanyProvider-side training and retention for model inference.

What each value guarantees

OpenAgent uses two checks together: the Gateway catalogue decides whether a model is eligible, and the model request carries the matching Gateway enforcement control. A label on its own is not treated as enforcement. The catalogue is synced every minute.

ValueRoutes to
anyThe full worker catalogue. Provider training and retention terms may vary.
no_trainingModels reported eligible on every route, with disallowPromptTraining attached to supported Gateway requests.
zero_retentionModels reported eligible on every route, with zeroDataRetention attached to supported Gateway requests. This is provider ZDR; OpenAgent still stores its task and outcome records.

The Models page also shows regions, but that field only says where a model is offered. OpenAgent does not pin an inference call to a region, so there is no workspace residency control and inference may run outside the EU.

Strict managed requests also bypass cached bring-your-own-provider keys. The Gateway cannot apply Vercel’s provider agreements to a credential whose separate provider contract OpenAgent cannot see.

some is not all. The Gateway publishes three values per field. some means the provider offers the guarantee on some routes, plans or endpoints — we cannot tell which, and we are not the ones choosing the route. Partial retention is retention, so a model whose zdr is some does not satisfy zero_retention, and one whose no_training is some does not satisfy no_training.

Unknown is not permitted

Under any policy stricter than any, a candidate the catalogue cannot vouch for is excluded rather than admitted. That covers a model whose field is not reported, a model the sync has never seen, or an external worker whose provider path OpenAgent cannot enforce. Sandbox coding CLIs can satisfy a strict policy only when team-wide Gateway ZDR is enabled and attested on the deployment; they cannot attach the per-request AI SDK controls.

The same rule covers our own failures. If the catalogue cannot be read at all while a policy is in force, every candidate is excluded and the task fails rather than routing to something nothing can vouch for. A privacy filter that admits on doubt is not a filter.

Every refusal is visible

A model excluded by your policy appears in the latest routing decision like any other exclusion, with a reason naming the guarantee it could not show. Read it on GET /v1/tasks/{id} under routing.candidates, or on the task page in the dashboard. Retries replace that latest snapshot and append a new timeline event; each opened attempt records its policy and enforcement mechanism. Nothing is dropped quietly.

routing.candidates
{
  "routing": {
    "chosenAgentId": "8c1f…",
    "candidates": [
      {
        "slug": "claude-code-sonnet-5",
        "rank": 1,
        "chosen": true,
        "eligible": true,
        "exclusionReason": null,
        "summary": "Highest predicted success (91%), weighted for balanced. Based on 240 runs in this workspace."
      },
      {
        "slug": "opencode-gpt-5.2",
        "rank": 6,
        "chosen": false,
        "eligible": false,
        "exclusionReason": "Provider policy requires a provider that will not train on what is sent; the catalogue does not say openai/gpt-5.2 can satisfy it"
      },
      {
        "slug": "cursor-agent",
        "rank": 7,
        "chosen": false,
        "eligible": false,
        "exclusionReason": "Provider policy requires a provider that will not train on what is sent; this worker has no enforceable model route, so its handling is unknown"
      }
    ]
  }
}

Pinning with agent does not override this. A pinned worker that fails the policy is excluded with its reason and the task routes to the best candidate that does satisfy it — the pin chooses among permitted workers, it does not widen what is permitted.

One caller is exempt, deliberately: the probation run a newly catalogued worker has to pass before it can be routed to at all. It runs in its own workspace, on a fixed synthetic prompt with no customer content in it, and it cannot relax the policy on any workspace but that one.

The policy is read before the classifier, during routing, again immediately before execution, and before the judge. A change can therefore stop or reroute work that is parked between stages. It does not recall a managed generation already in progress or the Gateway calls a detached sandbox CLI makes before the next engine wake, so tighten policy before submitting sensitive work rather than treating the switch as an emergency kill control. See the model catalogue for what each setting currently costs in coverage, and for the per-model labels the filter reads.

Human approval

POST/v1/approvals/{id}

When an agent calls a tool classified as a write or a deletion, execution pauses and the task moves to awaiting_approval. Nothing has happened yet — the call is recorded, not performed. Approving resumes the run; rejecting tells the agent to find another way.

Decide
curl https://useopenagent.com/v1/approvals/$APPROVAL_ID \
  -H "Authorization: Bearer $OPENAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"decision": "approve"}'

Errors and limits

Errors are JSON with a stable code and a message written to be read in a terminal.

402 Payment Required
{
  "error": {
    "code": "budget_exceeded",
    "message": "This workspace has used its $50.00 monthly budget ($50.14 spent). Raise it in Settings to continue."
  }
}
StatusCodeWhen
400invalid_jsonThe body was not valid JSON.
401missing_api_keyNo Authorization header.
401invalid_api_keyThe key does not exist or does not match.
401revoked_api_keyThe key was revoked.
402budget_exceededThe workspace hit its monthly budget.
404task_not_foundNo such task in this workspace.
409task_already_settledCannot cancel a finished task.
422invalid_requestThe body failed validation. details lists each field.
429rate_limitedToo many requests. Honour Retry-After.
503execution_unavailableNo model credential is configured on the deployment.

Rate limits

120 task creations per minute per workspace. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Spend is bounded separately by the workspace budget, which is the control that actually protects your bill.

Idempotency

Pass Idempotency-Key (or idempotency_key in the body) and a retry returns the original task with Idempotent-Replay: true instead of running the work twice. Keys are scoped per workspace and never expire, so a client can retry safely without tracking state.