Sume job 404 for an id you created: check which member's key made it
A 404 on GET /v1/jobs/{id} can mean the job belongs to another member or workspace. A Python helper separates unknown from not visible.

If GET /v1/jobs/{id} returns 404 for a job id you know exists, the likely cause is that you are reading with a different key than the one that created the job. A Sume API key reads only the jobs that its own member created, in the workspace of the key. Everything else answers 404 not_found, the same as an id that never existed, so the response does not reveal whether the job is real.
This shows up in teams: one person submits a batch, a second person's key tries to poll it, and the poller sees nothing. The fix is to read with the creating member's key, or to keep results in your own store.
The visibility rule
A thread_id filter on the list makes it smaller. It never increases what a key can read.
| Reader | Can read | Gets 404 for |
|---|---|---|
| API key | Jobs created by the key's own member, in the key's workspace | Other workspaces, and jobs of other members |
| Studio Agent turn | Each job in the thread it runs on, whoever created it | Jobs in unrelated threads of other members |
| Anyone, to cancel | Only the member who created the job | Jobs of other members |
Telling unknown from not visible
The API gives the same 404 for both, so your own ledger has to tell them apart. If the id is in your table of submitted jobs, a 404 means the reader cannot see it; if it is not, the id is unknown or mistyped. The helper below encodes that. It takes the HTTP call as an argument, so you can test it offline, and the run shows the three outcomes.
class NotVisible(Exception):
pass
def read_job(get, job_id, ledger):
status, body = get(f"/v1/jobs/{job_id}")
if status == 200:
return body
if status == 404 and job_id in ledger:
raise NotVisible(f"{job_id} is ours, but this key cannot read it: check the key's member and workspace")
if status == 404:
raise KeyError(f"{job_id}: unknown id")
raise RuntimeError(f"unexpected {status}")
ledger = {"job_a"}
same_key = lambda path: (200, {"id": "job_a", "status": "completed"})
teammate_key = lambda path: (404, {"error": {"code": "not_found"}})
print(read_job(same_key, "job_a", ledger)["status"])
for jid in ("job_a", "job_zzz"):
try:
read_job(teammate_key, jid, ledger)
except (NotVisible, KeyError) as e:
print(type(e).__name__, e)What to do next
- Store the id of the key's member (or a label for the key) next to each job in your ledger.
- Poll with the same key that submitted. Do not share job ids between services that use different keys.
- Use a webhook so the result is delivered to your endpoint without a read.
- Do not retry a submit because a read returned 404. The job is probably running.
Cancel follows the same rule
Only the creating member can cancel a job. A cancel from another key will not find it. Cancellation also works only before generation starts; after that the API answers 409 job_generation_already_started and the job runs to completion.
Checks to run first
Before you suspect the job, check three things. First, the key: print the last four characters of the key in use and compare them to the key that submitted. Second, the workspace: a key is issued in one workspace, and a job from another workspace is not visible. Third, the id: copy it again, because a stray character gives the same 404.
If all three match and the job is still not found, read GET /v1/jobs with the same key and look for the id in the list. A job that appears there but not in the direct read would be worth reporting with its request id.
Sources
Related posts
More in Developers
- job.canceled: the third Sume terminal event your handler forgets
Sume sends job.completed, job.failed and job.canceled and nothing else. A handler that covers only two leaves canceled jobs open. Route all three, 204 the rest.
- Sume MCP first call: mcp_health must say mcp_oauth, then tools_list
After adding the Sume connector, call mcp_health and check authenticated.auth_source is mcp_oauth. Then call tools_list to see which tools your grant exposes.
- Limit an agent's paid MCP calls with script_run max_paid_calls
script_run runs a short program on the Sume side with max_calls, max_paid_calls and a 5-55 second timeout. What it can bound, and what it does not cap.
- result_ready vs terminal vs completed: gate the Sume result fetch
Poll a Sume job until terminal, fetch the result only when result_ready is true. Failed and canceled jobs answer 409 job_not_completed on /result.
Written by Sume