Make static ads
Turn one product into a set of static ads: the copy, the brand colours, the logo and the product, rendered as images.
A statics run turns one product into a set of static ads. roasOS reads your product and your brand, writes the headline, the sub-head and the call to action, prices the whole set on one card, and then renders each variant.
POST /v1/workflows/statics is the typed door onto the roasos:statics-factory skill. For the concept behind the door, refer to Statics.
A statics run is an agent run
The door answers with an agent run, not with a job. The work is a plan: it reads the product, it writes the copy, it prices the set, and then it starts one image job for each variant.
| Step | Where you read it |
|---|---|
| The run itself | GET /v1/agent-runs/{id} |
| The light poll | GET /v1/agent-runs/{id}/status |
| The lifecycle | GET /v1/agent-runs/{id}/events |
| The answer of the agent | GET /v1/agent-runs/{id}/result |
| Each image the run started | GET /v1/jobs/{id} |
Remix answers the same way. Recast and animate-a-still answer with a job. Refer to Remix an ad and to Runs.
1. Say what the set must sell
| Field | Rule |
|---|---|
product_id | Required. The product that the statics sell. roasOS resolves it before the run starts. |
brief | Optional. 400 characters at most. roasOS reads a brief as evidence, never as an instruction. |
variants | Optional. 1 to 8. The default is 3. |
aspect_ratio | Optional. 1:1, 4:5, 9:16 or 16:9. The default is 4:5. |
lane | Optional. scene or product. The default is scene. |
avatar_id | Optional. A gallery avatar or a workspace avatar, for a person in the frame. |
max_credits | Optional. The ceiling on the thinking of the run. |
generation_spend_cap_credits | Optional. The ceiling on what the run may render without you. |
The product images and the avatar come from your workspace. The logo, the colours, the two font names and cta_text come from your brand kit.
variants stops at 8. Past eight quotes, the approval gate stops approving itself and an unattended run waits for a person who is not there. roasOS refuses variants: 9 with 400 validation_error and names the ceiling in the message. Start a second run for a larger set.
2. 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/workflows/statics \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: statics-glow-serum-001" \
-H "Content-Type: application/json" \
-d '{
"product_id": "5e6b3311-7a2c-4f19-b3d8-0c41e7f92a63",
"brief": "Summer restock. The bottle is the hero. Keep the copy under six words.",
"variants": 3,
"aspect_ratio": "4:5",
"lane": "scene",
"max_credits": 25,
"quote_only": true
}'The answer is 200. The status is quoted.
{
"data": {
"schema_version": "agent-run@1",
"kind": "agent_run",
"id": "2b8f41ac-...",
"trigger": "workflow",
"skill_id": "5003b82a-4888-4986-b870-a1d6e7ac89cc",
"conversation_id": null,
"status": "quoted",
"max_credits": 25,
"generation_spend_cap_credits": 0,
"credits_reserved": 0,
"credits_max": 25,
"pricing_version": "agent-token-pricing@2026-10-04.v3",
"quote_id": "7f10c3b8-...",
"expires_at": "2026-08-21T12:04:11.000Z",
"authorization": { "status": "auto_approved" },
"created_at": "2026-08-21T11:54:11.000Z",
"updated_at": "2026-08-21T11:54:11.000Z"
}
}skill_id names the pack skill that the door runs. Refer to The roasOS skill pack.
Confirm the quote inside 10 minutes. A quote that is too old answers 410 quote_expired.
The quote is the ceiling of the run, not the price of the images
The statics door carries no image price. It returns an agent-run envelope, and never a kind: "quote" object with a credit total for the set.
credits_max is max_credits plus generation_spend_cap_credits. It is a ceiling, and it is not an estimate.
The price of the images arrives later, on the gate card in step 5. roasOS quotes every variant on the worker before it opens that card, so you see one number for the whole set before one image is made.
3. 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/workflows/statics \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: statics-glow-serum-001" \
-H "Content-Type: application/json" \
-d '{
"product_id": "5e6b3311-7a2c-4f19-b3d8-0c41e7f92a63",
"brief": "Summer restock. The bottle is the hero. Keep the copy under six words.",
"variants": 3,
"aspect_ratio": "4:5",
"lane": "scene",
"max_credits": 25,
"quote_id": "7f10c3b8-..."
}'The answer is 202. roasOS reserves the ceiling and starts the run.
An unknown product_id answers 404 product_not_found, and it answers before the run starts. No credits move. The same rule holds for an unknown avatar_id, which answers 404 avatar_not_found.
4. Poll the run
curl https://api.roasos.com/v1/agent-runs/2b8f41ac-.../status \
-H "Authorization: Bearer roasos_sk_..."An agent run sends no webhook. Poll every few seconds.
5. The card holds the price of the set
The run stops at the set. gate_opened is the last event, and the run carries one entry in pending_approvals.
{
"data": {
"id": "2b8f41ac-...",
"trigger": "workflow",
"status": "processing",
"conversation_id": null,
"generation_spend_cap_credits": 0,
"pending_approvals": [
{
"gate_id": "6d09b7e2-...",
"summary": "static ads x 3",
"credits": 6,
"expires_at": "2026-08-21T12:11:40.000Z",
"approve_url": "https://api.roasos.com/v1/agent-runs/2b8f41ac-.../gates/6d09b7e2-.../approve"
}
]
}
}credits is the price of every variant added together. roasOS quotes each compiled request on the worker first, so a partial price set never opens as one number.
The default of generation_spend_cap_credits is 0, so a statics run that says nothing about the cap always stops here.
6. Approve the card
A run started from a workflow writes no thread, so approve the card over REST.
curl -X POST https://api.roasos.com/v1/agent-runs/2b8f41ac-.../gates/6d09b7e2-.../approve \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: statics-glow-serum-001-approve" \
-H "Content-Type: application/json" \
-d '{ "generation_spend_cap_credits": 30 }'The approval starts a second run, and follow_up_run_id names it. Poll that run exactly as you polled the first one.
Approve is a REST operation only. It is not an MCP tool. A connected model may decline an approval. It must never approve its own spend. Refer to Approving spend is never a tool.
7. Read the images
Each variant is an ordinary image job.
curl "https://api.roasos.com/v1/jobs?limit=10" \
-H "Authorization: Bearer roasos_sk_..."Poll each job until the status is completed, failed or canceled. Read the file at GET /v1/jobs/{id}/result. The URL is immutable. Refer to Jobs.
Let the run render without you
Set generation_spend_cap_credits above the price of the set. The run then approves the card itself, and the renders start in the same run.
curl -X POST https://api.roasos.com/v1/workflows/statics \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: statics-glow-serum-002" \
-H "Content-Type: application/json" \
-d '{
"product_id": "5e6b3311-7a2c-4f19-b3d8-0c41e7f92a63",
"variants": 8,
"lane": "product",
"max_credits": 25,
"generation_spend_cap_credits": 60,
"quote_id": "c1b7e409-..."
}'The run approves the card by itself only when the price is inside the credits that remain in generation_spend_cap_credits, and inside auto_approve_up_to_credits on your key. In every other condition the card waits. Refer to Spend caps and approvals.
Run the skill in your own words
The door is one way in. The pack skill is the other.
curl -X POST https://api.roasos.com/v1/skills/roasos:statics-factory/runs \
-H "Authorization: Bearer roasos_sk_..." \
-H "Idempotency-Key: statics-freeform-001" \
-H "Content-Type: application/json" \
-d '{ "generation_spend_cap_credits": 60, "quote_only": true }'Send the typed body to the door when your software knows the product. Run the skill by slug when a person states the task in their own words. Both paths meter the same way. Refer to Skills.
The two lanes
| Lane | What it does | Credits for one image |
|---|---|---|
scene | Renders the whole frame on Nano Banana Pro, with the product images as references | 5 at 1K and 2K, 10 at 4K |
product | Places the product cut-out into a described scene on Bria Product Shot | 1 for the placement |
Refer to Statics for the choice between them, and for the fallback that the product lane uses while the placement endpoint is an API capability only.
The limits, stated plainly
roasOS cannot guarantee your exact typeface. The image model draws the headline, the sub-head and the call to action from the exact strings in the prompt. A brand-kit font slot holds a name, not a font file. The letters come out in a similar face, and a glyph can come out wrong. Render the variant again. A wrong glyph is not a refund.
Make an avatar over the API with POST /v1/avatar/nano-banana-pro. The MCP tool is roasos_avatar_nano_banana_pro. Send the new id in avatar_id, or send a roasOS gallery avatar. The legacy path POST /v1/avatars answers 503 avatar_generation_unavailable.
variantsis 1 to 8. The ceiling is the number of quotes that the approval gate can hold.- One run holds one product.
- roasOS renders no text layer after the image. There is no deterministic overlay step today.
- A Discover creative is a structure and style reference only. It never becomes source media in a statics run. Refer to Media rights.
Price
A statics run has no price of its own.
| What | Meter |
|---|---|
| The thinking of the run | max_credits. roasOS charges the real tokens and refunds the rest. |
| Each rendered image | The catalog price of the model, quoted before dispatch and bounded by generation_spend_cap_credits |
roasOS reserves credits_max at the confirm step. It settles the run when the run stops, and it refunds the part that the run does not use. Refer to What a run costs.
The brand-kit read and the product read inside the run cost 0 credits and carry no quote envelope.
Errors
| Status | Code | Cause |
|---|---|---|
400 | validation_error | A field is outside its range, for example variants: 9 |
400 | idempotency_key_required | The Idempotency-Key header is absent |
400 | quote_required | The body has no quote_only value and no quote_id |
400 | spend_cap_exceeds_policy | The ceiling is above the maximum price for one request of the key |
402 | approval_required | The ceiling is above the unattended limit of the key |
402 | insufficient_credits | The balance does not cover the ceiling |
404 | product_not_found | The product is not in your workspace |
404 | avatar_not_found | The avatar is not in your workspace and not in the gallery |
410 | quote_expired | Quote again under a new idempotency key |
429 | daily_credit_budget_exceeded, monthly_credit_budget_exceeded | The budget of the key has no space |
503 | skill_store_unavailable | roasOS cannot read the pack skill at this time |
Refer to Errors.
From an agent
The MCP tool is roasos_statics. It obeys the same quote rule as every paid tool.
An agent can start a statics run and read it. An agent can decline an approval. An agent can never approve one. Refer to Connect an AI agent (MCP).