Mobidoo

Create a run

One POST starts a run. This is the shape production integrations send: your data in input, a schema for the result, a per-run spend cap, and a webhook so you do not have to poll.

202 Accepted:

Two status codes mean success. 202 is a fresh run. 200 is an idempotent replay, the same Idempotency-Key with the same body, and returns the original run with idempotency_hit: true. Both carry the full receipt: store data.id and follow the URLs on it. Runs and results covers what happens next.

Build your own call

Fill in the fields to rewrite the cURL, TypeScript, JavaScript and Python snippets. Operations in the TypeScript SDK resolve with { data, error, response } rather than throwing, so check error before reading data.

Create a Format run

POST /v1/formats/{handle}/{slug}/runs

Required

Address a Format

ShapeExampleUse it when
{handle}/{slug}POST /v1/formats/acme/live-commerce/runsEvery new integration. It is the address the Format detail page shows, and the shape production callers use.
{format_id}POST /v1/formats/skl_…/runsA stored URL that must survive a handle or slug rename. Permanent, identical behaviour.
sume/{slug}POST /v1/formats/sume/sume-product-promo/runsThe Formats by Sume catalog. Any key with the scopes may call it; the run belongs to that key.

The two shapes resolve to the same Format and run the same pipeline: same body, headers, idempotency, caps and receipt. The receipt's format.id is always the opaque skl_… id, whichever shape you used. A renamed handle keeps resolving for 90 days.

A team Format lives at the team's handle, and any key created in that workspace with the right scopes may POST it. See Team Formats need a team key. An unknown handle, an unknown slug, and a handle you cannot see all answer the same 404 format_not_found.

GET /v1/formats/{handle}/{slug} and GET /v1/formats/{handle}/{slug}/runs take the same address.

Request body

Every field is optional on its own, but the body must name at least one of instruction, input, previous_run_id or attachments. {} or {"input": {}} is 400 invalid_request, not a run on the Format's default. Unknown top-level fields are 400 unknown_parameter, with a suggestion when the name is close (webook_urlwebhook_url).

FieldNotes
instructionThe task in your words, up to 8000 characters. Omit it to run the Format's own default instruction. Composed after the Format body, so it wins where they disagree. See What is accepted and what is carried.
inputA JSON object of caller data: at most 64 top-level keys and 2 MiB (2097152 UTF-8 bytes, compact). You choose the shape; the Format reads the keys it knows. Media URLs inside it share the run's attachment budget. See input caller data.
attachmentsUp to 30 images the agent can see: { "type": "input_image", "image_url": … } or { …, "asset_id": … }. See Attachments.
output_schemaBind a JSON Schema and output comes back in that shape. response_format is the OpenAI-shaped alias; sending both is 400 invalid_request. Rules and failure modes: Structured output.
primary_output_keyThe key in output whose URL becomes primary_output_url. Up to 64 characters.
generation_spend_cap_usdThis run's generation ceiling, up to the platform maximum of $500. Omit it to inherit the Format's cap; a number above the Format's cap is honored, not clamped; null runs at $500; 0 is rejected. See Spend caps.
communication.webhook_urlPublic HTTPS URL that receives one signed format.run.terminal POST when the run completes or fails. callback_url is an accepted alias; top-level webhook_url / callback_url / mode are normalized into communication. Delivered on both hosts. See Webhook.
communication.modeasync (default) or webhook. Descriptive only: supplying a URL is what arms delivery.
previous_run_idContinue an earlier run of this Format as another turn of the same conversation instead of starting fresh. See Continue a run.
on_active_runWhat to do when a run of this Format is already in flight. Default allow (runs concurrently; workspace generation concurrency still applies). skip records a skipped run instead; reject answers 409 format_run_in_progress. Scheduled Actions default to skip, so do not copy their bodies here.
modelAgents catalog id for the LLM that orchestrates the run, for example gpt-5.6-sol, the default. Selects the orchestrator only; image, video and audio models are chosen by the Format's tools. An id outside the catalog is 400 invalid_request. The receipt echoes the id that ran.
idempotency_keyBody spelling of the Idempotency-Key header. The header wins when both are sent.

Headers: Authorization: Bearer $SUME_API_KEY or x-api-key: $SUME_API_KEY (one, never both), Content-Type: application/json, and Idempotency-Key on every create. The request body is capped at 4 MiB (413 payload_too_large).

caller data

input is the JSON object your service hands to the run. It is not a wire schema and Sume publishes no field list for it: you choose the shape and the Format's recipe reads the keys it recognises. Two integrations calling the same Format may send completely different objects, and both are correct. The example bodies on these pages are one integrator's convenient shape, not a contract.

What the API checks, and nothing else:

CheckRuleOn failure
TypeA JSON object. Arrays, strings and numbers are refused; null and omission both mean no input.400
Property countAt most 64 top-level keys. Nested keys are not counted, so grouping is free.400
SizeAt most 2097152 UTF-8 bytes (2 MiB) on the compact serialization.400
Media referencesHTTPS URLs to image, video or audio files, at any depth, share the run's attachment budget: 30 in total, at most 30 images, 10 videos, 10 audio. See Media referenced from input.400 invalid_attachment

Keep the two JSON fields apart in your head: input is loose data going in, output_schema is a strict contract coming out. input is written whole to a file in the run's workspace and the agent is told it is caller-supplied data, not instructions. That is exactly where scraped product copy, a customer's message or a supplier's field belongs, rather than concatenated into instruction. It is a trust boundary, not a sandbox: runs are spend-capped, so the blast radius of a hostile payload is bounded by the cap, but do not pass raw untrusted text through on purpose.

input does not reach the structured output. output is produced from what the run made and said, so a value you sent, an order id or a SKU, cannot be echoed back unless the run repeats it. Keep your identifiers on your side, keyed by data.id or by your Idempotency-Key. See Where your object comes from.

What is accepted and what is carried

FieldAcceptedCarried to the run
instruction8000 charactersThe first ~4000 characters, as prompt text. Keep it well inside that and put data in input.
input2 MiBAll of it, as a file the agent reads. Never truncated.
The Format bodyNo cap beyond 100 MiB per package fileAll of it, attached as files.

An empty input ({}) adds no file and no block at all, byte-identical to omitting the field. A Format that says "read product_url from the input" then has nothing to read.

Idempotency

Send Idempotency-Key on every create and derive it from the thing being made, your order id and a version you bump when you deliberately want a re-run, not from the moment of asking. A uuidgen per request makes the header decorative.

ReplayResult
Same key, same body200 with the original receipt and idempotency_hit: true. No second run, no second charge.
Same key, different body, including a different instruction or attachment list409 idempotency_conflict. Nothing runs.
Same key, two requests at the same momentOne wins; the other gets 409 idempotency_key_in_use, which is retryable. Wait about a second and resend to receive the original run.
Same key after a create that failed (402, 503, …)The key was released. Fix the cause and retry with the same key.

Keys are scoped to one Format: the same key sent to two Formats starts two runs. A key is up to 255 characters.

Spend caps

Every Format carries a generation spend cap and a run can never spend past its own effective cap. Read the Format's cap from generation_spend_cap_usd_micros on GET /v1/formats/…; a Format that never named one reports the platform default of $400.

generation_spend_cap_usd on the request names this run's own ceiling:

You sendThe run's cap
NothingThe Format's cap.
A number up to 500That number. Above the Format's own cap is honored, not clamped.
nullThe platform maximum, $500. It lifts the ceiling; it does not remove it.
0, or above 500400. A run that cannot spend cannot deliver.

The effective cap comes back on every receipt as usage.generation_spend_cap_usd_micros, and what the run actually spent against it as usage.billable_amount_usd_micros. Production live-commerce integrations run with caps around $120; a single-scene retry on the same thread needs a fraction of that. What counts against the cap is metered at the rates on the API pricing page. Errors and spend covers what happens at the wallet.

Keys and scopes

Scopes

ScopeNeeded for
formats:readList and read Formats, read and list runs, read queues.
formats:writeCreate a run, create a bulk queue, cancel a run, redeliver a webhook.

Scopes are fixed when a key is minted. Keys created before the Formats API shipped do not carry these, and a key missing one fails every Format request with 403 insufficient_scope, never a 404. Create a new key at API keys and rotate to it.

Service-account keys cannot create Format runs. They fail with 403 insufficient_scope and details.reason of service_account_format_runs_unsupported.

Team Formats need a team key

A Format owned by a team workspace is invoked with an API key created in that workspace. Membership is not enough: a personal key held by a team member is refused with 403 workspace_key_required, and details.workspace_id names the workspace the key has to come from.

The rule follows the money. A team Format's runs bill the team wallet, count against the team's generation concurrency, and read their media back through the team workspace. A personal key would split those, and used to produce runs that made a real video and then reported output_schema_unsatisfied with nothing harvested. Create the key from the team's dashboard. Personal keys stay right for personal Formats.

Reading is keyed the same way: a team key lists that workspace's Formats for every member, and never your personal ones. A team handle you are not a member of is 404, indistinguishable from one that does not exist, so a 403 workspace_key_required always means "right team, wrong key".

Running a Format another workspace shared with you

A team Format can be shared with another workspace — never a user — the way a GitHub repository takes an outside collaborator. The owner workspace adds your team handle on the Format's Access tab (live immediately), or invites it with POST /v1/formats/{handle}/{slug}/grants and your admin accepts with POST /v1/format-grants/{grant_id}/accept using a key created in your workspace. From then on you call the Format at the owner's address with your own team key:

There is no workspace field on the request: the key is the actor. The run, its spend, its concurrency slot and its media are your workspace's, not the owner's, and GET …/runs with your key lists your runs only. The owner still decides whether the Format takes API calls at all — its Status and API call trigger apply to every caller — and can remove your access, after which the address is a 404 for you again. A personal key is refused the same way it is on any team Format, and membership of the owner workspace does not stand in for a grant. If you hold seats in both workspaces, the key you bring decides: each workspace keeps its own run history and its own bill, and a key from one never reads the other's runs.

Runs over the API are unattended

A Format written for chat may pause and wait for a person: "approve these stills before I make the video" is a deliberate quality gate in the Agents UI. Over the API nobody is there, so the run is told those approvals are already granted and to carry on to the paid step within its spend cap.

A run that genuinely cannot finish comes back failed, never a half-finished completed:

One qualification: a completed run always did real work and always fills artifacts[], but it can still carry output_error when the projection did not match your output_schema. Check output_error before reading output. See When output cannot be produced.

401 vs 403 vs 404

HTTP class is the first branch; error.code is the second. A missing key is 401 unauthorized. A known key missing formats:read / formats:write is 403 insufficient_scope, never 404 format_not_found. Scopes cannot be patched onto an existing key; mint a new one and rotate.

HTTPerror.codeWhen
401unauthorizedNo key, malformed key, two credentials at once, revoked, or unknown. next_action is authenticate.
403insufficient_scopeValid key missing formats:read / formats:write; details.required_scope names it. Also a service-account key on a run create or package write, with details.reason set. next_action is authenticate.
403workspace_key_requiredYou are a member of the team workspace but brought a personal key. details.workspace_id names the workspace to mint one in. A team key from another workspace is judged by the grant instead: it runs when that workspace holds an accepted grant, and is a 404 when it does not.
404format_not_foundUnknown, archived, outside this key's workspace, a team handle you are not a member of, or a shared Format whose grant is still pending or was removed. A member's team key on the right handle is never this 404.
404format_run_not_foundUnknown run id, or a run belonging to another owner.
404format_run_queue_not_foundUnknown bulk queue, or another owner's.
404previous_run_not_foundprevious_run_id is unknown or not yours.
404format_content_not_foundThe Format exists for this key but that package path does not.

Status classes follow RFC 9110; insufficient_scope is the RFC 6750 vocabulary. The 404 on someone else's Format is a deliberate tenancy hide, not a hint that the Format exists elsewhere (design record: #2393).

Errors

Everything the create call can answer with, in one table, is on Errors and spend. The ones you will meet first:

Statuserror.codeWhat to do
400invalid_requestThe body named none of instruction / input / previous_run_id / attachments, sent both output_schema and response_format, or failed a size cap. Read message.
400output_schema_invalidYour schema is outside the supported subset. details.violations[] names every problem.
401unauthorizedFix the header, not the body.
402insufficient_creditsThe workspace cannot fund the run. next_action is add_funds.
403insufficient_scope, workspace_key_requiredMint the right key. See above.
404format_not_foundCheck the address and which key you are holding.
409format_inactive, format_api_trigger_disabledThe owner workspace sets these on the Format page's API tab. They apply to every caller, including workspaces the Format is shared with.
409idempotency_conflict, idempotency_key_in_useUnstable key derivation, or a concurrent duplicate. See Idempotency.
429rate_limitedWait retry-after seconds.
503studio_agent_upstream_unavailableA Sume-side outage. Retry with the same Idempotency-Key.

error.code is a lowercase token you can switch on; message is for humans and may change.

Rate limits

Every response carries ratelimit-limit, ratelimit-remaining and ratelimit-reset, and a 429 adds retry-after. Reads and writes have separate budgets and the read budget is forty times the write one, so a poll loop cannot starve your own creates. A 429 names the budget it came from in error.details.scope. Full table: Errors and spend.

Next