Split a Sume job's queue wait from its run time with job events

Read GET /v1/jobs/{id}/events and subtract timestamps: job.queued to job.started is queue wait, job.started to the terminal event is run time. Python sample.

5 min readSume
All posts

Fetch GET /v1/jobs/{id}/events, find job.queued, job.started and the terminal event, and subtract their created_at timestamps. The first gap is queue wait, the second is run time. A long queue wait usually points at your plan's concurrency rather than the model.

Events to read

Each event has id, type, source, status, message, created_at and a sanitized data object. The documented types are below.

Job event types (read 2026-10-04)
TypeMarks
job.createdAccepted by the API
job.queuedWaiting for a slot
job.startedA worker picked it up
generation.submittedHanded to the generation service
job.completed / job.failed / job.canceledTerminal
webhook.deliveryA webhook attempt, not part of the run

Python

Queue capacity per plan is max(3, concurrency * 5), and concurrency is 1 on Free, 4 on Pro, 8 on Startup and 20 on Scale or Enterprise. With a Pro plan, a fifth simultaneous job waits for a slot, so a long queue wait is expected.

import asyncio, json, os, sys, urllib.request
from datetime import datetime

def get_events(job_id):
    req = urllib.request.Request(f"https://api.sume.com/v1/jobs/{job_id}/events",
                                 headers={"x-api-key": os.environ["SUME_API_KEY"]})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)["data"]["events"]

def ts(e):
    return datetime.fromisoformat(e["created_at"].replace("Z", "+00:00"))

async def main(job_id):
    events = await asyncio.to_thread(get_events, job_id)
    first = {}
    for e in events:
        first.setdefault(e["type"], e)
    queued, started = first.get("job.queued"), first.get("job.started")
    end = next((first[t] for t in ("job.completed", "job.failed", "job.canceled") if t in first), None)
    if queued and started:
        print("queue wait s:", (ts(started) - ts(queued)).total_seconds())
    if started and end:
        print("run time s:", (ts(end) - ts(started)).total_seconds())
    if not (started and end):
        print("job has not both started and finished:", sorted(first))

asyncio.run(main(sys.argv[1]))

Caveats

Skip webhook.delivery events in this arithmetic. They record delivery attempts that can happen after the job is already terminal.

Missing events

A job canceled early may have no job.started, in which case only the queue wait prints, or nothing. See which stage a failed job stopped at for the failure side.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume