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.

5 min readSume
All posts

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.

Errors from starting a schedule run and what to do (read 2026-10-03)
StatusCodeAction
400invalid_request or output_schema_invalidFix the request
403insufficient_scopeUse a key with actions:write; not a service-account key
409action_run_in_progress, action_inactive, action_api_trigger_disabled, idempotency_conflictDepends on the code; do not blind-retry
429rate limitedBack off
503studio_agent_upstream_unavailableBounded 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

All Developers posts

Written by Sume