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.
Authorization: Bearer oa_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKeys 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 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 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
/v1/tasksYou 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.
| Field | Type | Default | Description |
|---|---|---|---|
| input | string | required | What you want done, in plain language. Up to 200,000 characters. |
| repository | string | optional | A 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_ref | string | optional | Branch to start from. Defaults to the repository’s default branch. |
| context | object | optional | Structured data the agent should work from. Passed through untouched. |
| output_schema | object | optional | JSON Schema the result must satisfy. Enforced during verification — a result that does not validate fails and the next agent is tried. |
| preference | string | "balanced" | quality · balanced · cost · speed. Changes how the router weighs its signals. |
| max_attempts | integer | 3 | How many different agents may be tried before the task fails. 1–10. |
| max_cost_usd | number | optional | Agents whose estimated cost exceeds this are excluded from routing. |
| agent | string | optional | Pin a specific agent by slug. Useful for evaluation; the ranking is still recorded. |
| idempotency_key | string | optional | Also accepted as an Idempotency-Key header. Replays return the original task. |
| wait | boolean | false | Hold the connection until the task settles. |
| wait_ms | integer | 60000 | How long to hold it. 1,000–120,000. |
Node.js
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
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 taskWithout 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.
{
"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.
{
"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
| Status | Meaning |
|---|---|
| queued | Accepted, waiting for a worker. |
| routing | Capabilities detected; agents being ranked. |
| running | An agent is executing. |
| awaiting_approval | A risky tool call is paused for a human decision. |
| verifying | Output is being checked. |
| succeeded | Verified. output_text and output are populated. |
| failed | Every permitted attempt failed. error explains what happened. |
| cancelled | Cancelled via DELETE or the dashboard. |
Retrieve, watch, cancel
/v1/tasks/{id}Add ?expand=attempts,events to get every attempt the router made and the full execution timeline.
/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.
/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.
/v1/tasksLists tasks newest first. Supports limit (1–100), status, and starting_after for cursor pagination.
# 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" | jqFeedback — how routing improves
/v1/tasks/{id}/feedbackThis 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 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
/v1/agentsLists 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.
| Setting | Values | Default | What it constrains |
|---|---|---|---|
| data_policy | any · no_training · zero_retention | any | Provider-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.
| Value | Routes to |
|---|---|
| any | The full worker catalogue. Provider training and retention terms may vary. |
| no_training | Models reported eligible on every route, with disallowPromptTraining attached to supported Gateway requests. |
| zero_retention | Models 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": {
"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
/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.
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.
{
"error": {
"code": "budget_exceeded",
"message": "This workspace has used its $50.00 monthly budget ($50.14 spent). Raise it in Settings to continue."
}
}| Status | Code | When |
|---|---|---|
| 400 | invalid_json | The body was not valid JSON. |
| 401 | missing_api_key | No Authorization header. |
| 401 | invalid_api_key | The key does not exist or does not match. |
| 401 | revoked_api_key | The key was revoked. |
| 402 | budget_exceeded | The workspace hit its monthly budget. |
| 404 | task_not_found | No such task in this workspace. |
| 409 | task_already_settled | Cannot cancel a finished task. |
| 422 | invalid_request | The body failed validation. details lists each field. |
| 429 | rate_limited | Too many requests. Honour Retry-After. |
| 503 | execution_unavailable | No 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.