Ask the agent
Start one unattended agent run with POST /v1/agent/completions.
POST /v1/agent/completions starts one agent run from free text. The run plans, calls the tools, and can start paid work up to the caps in your request.
The call is quote-first, like every paid call in roasOS. Quote the run, confirm the quote, poll the run, then read the answer.
1. Quote
Set quote_only to true. A quote is free, and it reserves nothing.
Send an Idempotency-Key header. Use 16 to 255 characters. Use the same key for the quote and for its confirm.
curl -X POST https://api.roasos.com/v1/agent/completions \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: glow-serum-ugc-001" \
-H "Content-Type: application/json" \
-d '{
"text": "Look at my product Glow Serum. Plan a 15 second UGC ad, then render the first shot.",
"max_credits": 25,
"generation_spend_cap_credits": 40,
"quote_only": true
}'The body has six fields. Only text is required.
| Field | Rule |
|---|---|
text | Required. 1 to 8,000 characters. |
conversation_id | Optional. The UUID of a thread to continue. Omit it to start a thread. |
finished_ad | Optional. The default is false. Set it to true to stitch the generations of the run into one deliverable. Refer to Assembly. |
max_credits | Optional. 1 to 100,000. The default is 25, or your key's unattended limit if that limit is lower. The value 0 is refused, because a run with no token budget cannot work. |
generation_spend_cap_credits | Optional. 0 to 100,000. The default is 0. |
quote_only / quote_id | Send one of the two. Never send both. |
The answer is 200. The status is quoted.
{
"data": {
"schema_version": "agent-run@1",
"kind": "agent_run",
"id": "3d9c17aa-...",
"trigger": "api",
"skill_id": null,
"conversation_id": null,
"status": "quoted",
"max_credits": 25,
"generation_spend_cap_credits": 40,
"credits_reserved": 0,
"credits_max": 65,
"pricing_version": "agent-token-pricing@2026-10-04.v3",
"quote_id": "b0f4e2c9-...",
"expires_at": "2026-08-21T10:17:44.000Z",
"authorization": { "status": "auto_approved" },
"created_at": "2026-08-21T10:07:44.000Z",
"updated_at": "2026-08-21T10:07:44.000Z"
}
}Read credits_max. It is the ceiling of the run: max_credits + generation_spend_cap_credits. It is not an estimate of the true cost.
The id in a quote is already the id of the run. roasOS makes it from your idempotency key, so the quote and its confirm always name the same run.
A quote is always auto_approved. A ceiling that needs a person is refused before a quote exists, with 402 approval_required. Refer to Spend caps and approvals.
Read expires_at and confirm before that time. roasOS accepts a quote in the ten-minute window that made it, and in the window after it. A quote that is too old answers 410 quote_expired. Quote again under a new idempotency key.
2. Confirm
Send the same body with the quote_id. Use the same idempotency key. Remove quote_only.
curl -X POST https://api.roasos.com/v1/agent/completions \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: glow-serum-ugc-001" \
-H "Content-Type: application/json" \
-d '{
"text": "Look at my product Glow Serum. Plan a 15 second UGC ad, then render the first shot.",
"max_credits": 25,
"generation_spend_cap_credits": 40,
"quote_id": "b0f4e2c9-..."
}'The answer is 202. roasOS reserves the ceiling and starts the run.
{
"data": {
"schema_version": "agent-run@1",
"kind": "agent_run",
"id": "3d9c17aa-...",
"trigger": "api",
"skill_id": null,
"conversation_id": null,
"status": "processing",
"max_credits": 25,
"generation_spend_cap_credits": 40,
"credits_reserved": 65,
"pricing_version": "agent-token-pricing@2026-10-04.v3",
"started_at": "2026-08-21T10:08:02.000Z",
"pending_approvals": [],
"created_at": "2026-08-21T10:07:44.000Z",
"updated_at": "2026-08-21T10:08:02.000Z"
}
}The id is the run id. Keep it. Every later read uses it.
A run that does not name a thread makes one. The thread id then appears as conversation_id in the run and in the result.
3. Poll the run
curl https://api.roasos.com/v1/agent-runs/3d9c17aa-.../status \
-H "Authorization: Bearer roasos_sk_..."{ "data": { "id": "3d9c17aa-...", "status": "processing", "updated_at": "2026-08-21T10:08:31.000Z" } }The status changes from processing to completed, failed, or canceled. Poll every few seconds. Refer to Runs.
4. Read the result
curl https://api.roasos.com/v1/agent-runs/3d9c17aa-.../result \
-H "Authorization: Bearer roasos_sk_..."{
"data": {
"schema_version": "agent-run-result@1",
"kind": "agent_run_result",
"run_id": "3d9c17aa-...",
"conversation_id": "1f0c8b52-...",
"trigger": "api",
"skill_id": null,
"status": "completed",
"text": "I analyzed Glow Serum and wrote a three shot script. Shot 1 is rendered.",
"url": "https://www.roasos.com/home/1f0c8b52-...",
"completed_at": "2026-08-21T10:11:09.000Z",
"usage": {
"model": "anthropic/claude-sonnet-5",
"pricing_version": "agent-token-pricing@2026-10-04.v3",
"input_tokens": 18422,
"cached_input_tokens": 12800,
"output_tokens": 2140,
"steps": 6,
"tool_calls": 4,
"credits_reserved": 65,
"credits_charged": 2,
"credits_refunded": 63,
"generation_credits_charged": 40
}
}
}The text field is the answer of the agent. The url field opens the thread in roasOS. The thread holds the cards: the storyboard, the jobs, and the approvals.
/result returns 409 run_not_completed while the run is not terminal. This is not an error in your code. Poll /status first.
Spend caps and approvals
The two caps of a run compose with the spend policy of your API key. The policy always wins.
| Condition | Answer |
|---|---|
| The ceiling is more than the maximum price for one request of the key | 400 spend_cap_exceeds_policy |
The ceiling is more than the unattended limit of the key (auto_approve_up_to_credits) | 402 approval_required |
| The daily budget or the monthly budget of the key has no space | 429, with a Retry-After header |
| Your workspace does not have sufficient credits for the ceiling | 402 insufficient_credits |
The whole reservation of a run is unattended spend. roasOS measures the sum of the two caps against the unattended limit. It does not measure each cap on its own. A key with an unattended limit of 0 credits therefore starts no run at all. Raise the limit of the key, or lower the caps of the request.
Set these limits for each key in your workspace settings. Refer to Quotes and credits.
What the run approves by itself
The agent starts paid work as a generation. Each generation quotes and reserves on its own. The run cap sits above those generations. It never replaces them.
The run approves a generation by itself when both conditions are true:
- The price is inside the credits that remain in
generation_spend_cap_credits. - The price is inside
auto_approve_up_to_creditson your key.
The run then writes a gate_auto_approved event and continues.
In all other conditions the approval stays open. It appears in pending_approvals on the run. A person opens the card in roasOS and approves it there, or your software approves it over REST.
A storyboard render has three more conditions
A storyboard renders several clips under one card, so it must satisfy the two rules above and three more.
- roasOS priced every compiled request on the worker. A partial price set never approves itself. One clip would run at the approved price, and another would ask for a new price.
- The board compiles to 8 requests or fewer. One approval card carries at most eight quotes. Count the requests as shots, multiplied by variants, multiplied by matrix cells.
- The board does not use the first-frame pipeline. That pipeline stops for a person to approve each frame, which is the reason it exists.
A board that misses one of the five conditions waits in pending_approvals. Refer to Remix an ad for the whole loop.
Approve an approval
An API caller can release an open approval. Use this door for a run that has no thread, such as a skill run or a workflow run.
curl -X POST https://api.roasos.com/v1/agent-runs/3d9c17aa-.../gates/44b1d0e7-.../approve \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: hook-test-2026-08-21-approve" \
-H "Content-Type: application/json" \
-d '{ "generation_spend_cap_credits": 120 }'The answer names the follow-up run in follow_up_run_id. An Idempotency-Key is required, and the spend policy of the key bounds the amount. Refer to Approve an approval.
Approve is a REST operation only. It is never an MCP tool. A connected model may decline an approval. It must never approve its own spend.
Storyboard costs and retries
A storyboard's local compile cost is an estimate. A rendering stage starts only after a worker quote fits its saved credit limit. Its approved input stays fixed. First-frame and chained renders enforce this rule for each stage; a separately requested frame regeneration has its own displayed limit.
Retry an uncertain storyboard submission with the same approval. Keep its stored quote and payment key. A previously accepted request can replay after quote expiry; an expired, unsubmitted quote needs a fresh approval. Changes to the saved storyboard invalidate an unconsumed approval for its older version.
Decline an approval
An API caller can refuse an open approval. The work behind it then never starts, and roasOS charges nothing for it.
curl -X POST https://api.roasos.com/v1/agent-runs/3d9c17aa-.../gates/44b1d0e7-.../decline \
-H "Authorization: Bearer roasos_sk_..." \
-H "Content-Type: application/json" \
-d '{ "note": "Use the other product." }'{
"data": {
"gate_id": "44b1d0e7-...",
"run_id": "3d9c17aa-...",
"status": "declined",
"decided_at": "2026-08-21T10:10:02.000Z"
}
}The note field is optional. Its limit is 400 characters.
A decline is final. Approve the same card at /approve while it is still open. A person can also approve it on its card in roasOS. There is no approve tool on MCP, and there will not be one.
What the run costs
roasOS reserves the ceiling at the confirm step. It settles the run from the tokens that the run truly used. It refunds the rest. You therefore never pay more than the number in the quote.
Refer to Runs for each field of the usage object.
Errors at the door
| Status | Code | Cause |
|---|---|---|
400 | idempotency_key_required | The Idempotency-Key header is absent |
400 | invalid_idempotency_key | The header is not 16 to 255 characters of A-Z a-z 0-9 . _ : - |
400 | validation_error | The body is not valid, max_credits is 0, or the body holds quote_only and quote_id together |
400 | quote_required | The body has no quote_only value and no quote_id |
400 | spend_cap_exceeds_policy | The ceiling is more than the maximum price for one request of the key |
402 | approval_required | The ceiling is more than the unattended limit of the key |
402 | insufficient_credits | Your workspace does not have sufficient credits |
403 | — | The key does not have the api:write scope |
409 | — | The idempotency key was used again with different data |
410 | quote_expired | Quote the run again under a new idempotency key |
429 | rate_limit_exceeded | Too many requests |
429 | daily_credit_budget_exceeded, monthly_credit_budget_exceeded | The budget of the key has no space. Read Retry-After. |
503 | agent_runner_unavailable, agent_run_unavailable, spend_policy_unavailable | roasOS could not record or start the run |
A 503 charges you nothing. If a reservation was already made and roasOS could not confirm the outcome, a cron finds the run, fails it, and refunds it.
From an agent
The same operation is the MCP tool roasos_agent_ask on https://mcp.roasos.com/mcp. The quote rule is the same, and the tool answers with a run id. The client must then poll roasos_get_agent_run_status. Refer to Connect an AI agent (MCP).