Generated client for Sume schedule runs: handle the undeclared 503
The Sume run-a-schedule route declares 200, 202, 400, 401, 403, 404, 409, 413, 429 and 500 in OpenAPI, but can also return 503. Handle it in generated clients.

The Sume route that starts a schedule run declares 200, 202, 400, 401, 403, 404, 409, 413, 429 and 500 in its OpenAPI document, but it can also return 503 studio_agent_upstream_unavailable. Because that response comes from an upstream layer and is not declared, a client generated from the spec may not model it. Handle it yourself, with a bounded retry on the same Idempotency-Key.
From Run a schedule via API, read on 2026-10-03.
What the 503 means
The code is returned when the agents control plane is unconfigured, unreachable, or answered with something that was not JSON. When the cause is a configuration gap, details.missing reports action_control_plane. The docs note an oddity: the envelope says retryable: false and next_action: contact_support, even though the cause is an upstream condition. A bounded retry is still reasonable, and you should escalate if it persists.
| Status | Code | Action |
|---|---|---|
| 400 | invalid_request or output_schema_invalid | Fix the request |
| 403 | insufficient_scope | Use a key with actions:write; not a service-account key |
| 409 | action_run_in_progress, action_inactive, action_api_trigger_disabled, idempotency_conflict | Depends on the code; do not blind-retry |
| 429 | rate limited | Back off |
| 503 | studio_agent_upstream_unavailable | Bounded retry with the same key, then escalate |
Why the same key matters
Replaying the same Idempotency-Key with the same payload returns the original receipt with idempotency_hit: true and starts no second run. Without a key there is no replay protection, so every retry would start a new run. For a 503 that may or may not have created work, keeping the key constant is what makes the retry safe.
Reusing a key with a different payload is a 409 idempotency_conflict, so build the key per logical trigger, not per attempt.
A wrapper that models the gap
Wrap the generated call. On a 503 with that code, retry a small fixed number of times with a delay, reusing the key. After the last attempt, raise an error that includes the request_id from the envelope. The docs say to always log request_id, because it is the fastest way to get a run investigated.
Do not treat the 200 response as success automatically either. 200 can mean an idempotency replay or a skipped run, and 202 is the only status that means a new run was accepted and started. Read the receipt's status.
Testing the path you cannot trigger
You are unlikely to be able to make the upstream layer fail on demand, so test the handler with a stub. Have a fake server return a 503 with the studio_agent_upstream_unavailable code and a JSON envelope, and assert three things: the wrapper retries with the same key, it stops after its limit, and the final error carries the request_id.
Also add a case for a 503 with a non-JSON body. The code describes the control plane returning a non-JSON response as one of the causes, and a wrapper that assumes JSON on every error will crash on exactly that response.
Sources
Related posts
More in Developers
- Genkit createMcpHost: connect the hosted Sume MCP server
Genkit's createMcpHost takes a url and requestInit headers for remote servers. Wire https://mcp.sume.com/mcp into ai.generate and close the host after.
- Get one Sume video model by id: GET /v1/video-router/models/{id}
Look up a single Sume video model's limits and price with GET /v1/video-router/models/{id}. Response fields, the 404 for unknown ids, and a short script.
- Why GET /v1/jobs/:id returns 404 for a job that exists: the owner rule
An API key reads only jobs its own member created in its workspace. Other members' jobs and other workspaces answer 404 not_found, which is how Sume hides them.
- GitHub App ghs_ tokens are now ~520 characters: check Sume calls
GitHub's new installation tokens are about 520 characters, not 40. What breaks in a workflow that also calls Sume, and why Sume takes one credential header.
Written by Sume