Sume 404 resource_not_found: job_not_found and model_not_found overlap
Every Sume 404 reports public_reason resource_not_found, whatever the code. Branch on error.code to tell a missing job from a bad model id or a missing asset.

On the Sume API every 404 carries public_reason: resource_not_found, category: validation, retryable: false and next_action: fix_input, whether the thing missing is a job, a model id or an asset. The public reason therefore cannot tell them apart. Branch on error.code (job_not_found, model_not_found, and so on) and use details for the specifics.
Why is the reason the same for every 404?
The error classifier treats any error with status 404, or the code not_found, as a missing resource and fills the same four fields. That keeps the envelope simple for generic clients: a 404 never means wait and retry, so retryable is false and the action is to fix the input.
A switch written on public_reason will fold a mistyped model id into the same branch as a job lookup under the wrong key, which need very different fixes. A switch on code keeps them apart.
What fixes each 404?
For a job, the usual cause is the key: a job id is visible to the member that created it, so a lookup under another member's key reads as not found. For a model, the response lists the accepted ids at the catalog_url in its details. For an asset or file, check the id belongs to your workspace and was not deleted.
| error.code | Fixed fields | Fix |
|---|---|---|
job_not_found | resource_not_found, fix_input | Use the creating member's key; check the id |
model_not_found | resource_not_found, fix_input | Pick an id from details.catalog_url |
not_found | resource_not_found, fix_input | Check path and ids |
How do I write the switch?
Keep the order: code first, then the shared fallback.
def explain_404(body: dict) -> str:
err = body.get("error") or {}
code = err.get("code")
details = err.get("details") or {}
if code == "job_not_found":
return "No such job for this key. Was it created by another member?"
if code == "model_not_found":
return f"Unknown model id. See {details.get('catalog_url', 'the model catalog')}."
if err.get("public_reason") == "resource_not_found":
return f"Not found ({code}). Check the id and your workspace."
return "Not a 404 envelope."
print(explain_404({"error": {"code": "model_not_found", "public_reason": "resource_not_found",
"details": {"catalog_url": "https://docs.sume.com/models"}}}))Sources
Related posts
More in Developers
- Sume 409 job_not_queued: the job left the queue before it started
What the Sume 409 job_not_queued means: a submit found its job no longer queued, sent nothing to the provider, and the error is not retryable by resending.
- Sume asset complete 409s: checksum, content type, wrong state
POST /v1/assets/{id}/complete checks state, expiry, content type, size and checksum in a fixed order, and deletes the object when it rejects one.
- Sume asset download URL: 300 seconds and the 409 cases
GET /v1/assets/{id}/download-url returns a signed GET link that expires in 300 seconds by default, and 409s for registered URLs or assets not yet ready.
- An uploaded Sume asset you never complete: what happens
A pending_upload asset has a 15-minute URL by default. After the window and a one-hour grace, a reaper deletes the object and marks the row failed.
Written by Sume