Python asyncio loop for a Sume job: next_poll_after_seconds

A runnable httpx and asyncio loop for GET /v1/jobs/{id}/status that honors next_poll_after_seconds, backs off otherwise, and leaves the job running on timeout.

5 min readSume
All posts

Polling a Sume job in Python is a loop over GET /v1/jobs/{id}/status that stops on terminal, sleeps for next_poll_after_seconds when the server sends it, and otherwise backs off. The deadline belongs to your client. The job keeps running and billing if you stop waiting, so a timeout must not trigger a new paid submit.

The sample uses httpx and wraps the awaits in asyncio.run(main()). It reads a job id from the command line, so you can resume a wait for a job you stored earlier.

import asyncio, os, sys, httpx

BASE = "https://api.sume.com/v1"

async def wait_job(client, job_id, deadline=1200.0):
    loop = asyncio.get_running_loop()
    end = loop.time() + deadline
    delay = 2.0
    while loop.time() < end:
        r = await client.get(f"{BASE}/jobs/{job_id}/status")
        r.raise_for_status()
        s = r.json()
        if s.get("terminal"):
            return s
        hint = s.get("next_poll_after_seconds")
        delay = max(2.0, float(hint)) if hint else min(delay * 1.5, 30.0)
        await asyncio.sleep(delay)
    raise TimeoutError(f"still running; job {job_id} was not canceled")

async def main():
    key = os.environ["SUME_API_KEY"]
    async with httpx.AsyncClient(headers={"Authorization": f"Bearer {key}"}, timeout=30) as c:
        s = await wait_job(c, sys.argv[1])
        print(s.get("sume_status"), s.get("result_ready"))

asyncio.run(main())

What the loop relies on

Per the Jobs and results page, you can poll on the booleans terminal and result_ready, or on sume_status. The same payload has a queue-shaped status field (IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED) that maps one to one onto sume_status. Pick one and do not mix them.

After the loop returns, fetch the result only if the job completed. GET /v1/jobs/{id}/result answers 409 job_not_completed for failed and canceled jobs, so read the failure from GET /v1/jobs/{id} instead.

  • queued and processing are normal non-terminal states.
  • A 429 on the status read is poll backpressure: wait for retry-after when present.
  • Never resubmit the paid request because this function raised TimeoutError.
  • Store the job id before you start waiting, so a restart can resume.

Tuning

The 2-second floor and 30-second cap are choices, not API rules. The documented rules are to obey next_poll_after_seconds when present, use backoff when not, and stop on a terminal state or your own application deadline. For video, 20 minutes is the deadline the TypeScript SDK uses by default, and it is a reasonable starting value here too.

Running it

Run SUME_API_KEY=... python wait_job.py job_123. The script prints the final sume_status and whether result_ready is true. Because the id is a command-line argument, the same file resumes a wait after a restart, which is the reason to store job ids as soon as the submit returns.

To wait for many jobs, wrap wait_job in asyncio.gather, but keep the total below the accepted-capacity numbers of your plan, and give each task its own delay state. Add a semaphore so a Free workspace with 6 accepted jobs does not poll 600 at once.

Treat this as a habit, not a one-time fix. Write the rule down next to the code that calls the API, add a test that exercises it, and review it whenever the docs change. Check the linked documentation pages in the sources list for the current wording before you rely on any number here, because limits and field names can be revised, and a short test run costs far less than debugging a production incident.

When something does not match what you read here, capture the x-sume-request-id response header and the job or run id, and send those to support. Do not paste API keys, signing secrets or full request bodies into a ticket or a chat; the ids are enough for the team to find the request.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume