Map the Sume error envelope to RFC 9457 problem details in 15 lines
RFC 9457 defines type, status, title, detail and instance. Sume's error.code, message and request_id map onto them; keep the retry fields as extensions.

If your gateway or your clients speak RFC 9457 problem details, you can translate a Sume error in a few lines. error.code becomes the problem type, message becomes detail, the HTTP status becomes status, request_id becomes instance, and the retry fields ride along as extension members. Sume's own body is a JSON {"error": {...}} envelope, and this post does not claim that Sume sends application/problem+json itself.
What the RFC defines
RFC 9457 names the media type application/problem+json and five standard members: type (a URI reference that identifies the problem type), status (the HTTP status code generated by the origin server), title (a short summary of the problem type), detail (an explanation specific to this occurrence) and instance (a URI reference that identifies the specific occurrence). Problem types may add extension members, and status, if present, is only advisory.
The mapping
Every Sume field fits somewhere, and the actionability fields fit best as extensions, because they are specific to this API:
| Sume field | RFC 9457 member | Note |
|---|---|---|
| error.code | type | Wrap as a URI, for example urn:sume:error:<code> |
| HTTP status | status | Advisory per the RFC; keep the real response status |
| error.public_reason | title | A short stable label for the problem type |
| error.message | detail | Specific to this occurrence |
| error.request_id | instance | Wrap as a URI, for example urn:sume:request:<id> |
| category, stage, retryable, retry_after_seconds, next_action, details | extensions | Keep the names unchanged |
The converter
This runs as is, and it keeps unknown fields out, so you never leak more than the envelope carried:
import json
EXT = ("category", "stage", "retryable", "retry_after_seconds", "next_action", "details")
def to_problem(status: int, body: dict) -> dict:
e = body["error"]
p = {
"type": f"urn:sume:error:{e['code']}",
"status": status,
"title": e.get("public_reason") or e["code"],
"detail": e.get("message", ""),
}
if e.get("request_id"):
p["instance"] = f"urn:sume:request:{e['request_id']}"
p.update({k: e[k] for k in EXT if k in e})
return p
body = {"error": {"code": "queue_full", "message": "Queue is full.", "request_id": "req_1", "retryable": True, "retry_after_seconds": 30}}
print(json.dumps(to_problem(429, body)))Why bother
A shared problem shape lets one error middleware serve many upstreams. Your logs, alerts and front end can read type and instance for every vendor, and read the Sume extensions only when type begins with your Sume prefix. The cost is small: fifteen lines and a table. Keep the original request_id as the instance, because it is the string support will ask for first.
Limits
The type URIs above are yours, not Sume's, and they do not have to resolve. Choose a namespace you control if you publish them. The authoritative field list is on the Errors and credits page, and the RFC is at rfc-editor.org.
Keep the converter next to your HTTP client, not inside each call site, and cover it with a table test over the codes you handle. Because retryable and next_action are copied unchanged, your retry logic can read the same names whether the error arrived raw or as problem details.
Sources
Related posts
More in Developers
- Format run stuck queued? Sume waiting vs runtime_unavailable
A queued Sume Format run reports queue.state waiting or runtime_unavailable, with position always null. A bash and jq check that reads the status route.
- Sume job webhooks are terminal-only: drive a queue from three events
Sume sends job.completed, job.failed and job.canceled only, with no progress events. Publish on completed, alert on failed, and poll events for progress.
- Sume MCP assets_create vs upload: registered URLs are unverified
On Sume MCP, assets_create registers unverified metadata for a remote URL. For bytes you own, use assets_upload_url, a client PUT, then assets_complete.
- Sume MCP conflicting_model: top-level model vs payload.model
avatar-image-to-video_create lifts payload.model to the top level. If the two differ you get conflicting_model plus supported_models. Send one, or match them.
Written by Sume