Read a Sume job's events to time the wait before generation

Read GET /v1/jobs/:id/events, find generation.submitted, and compute how long a job sat between created, queued and started. A Python helper with tested math.

4 min readSume
All posts

Call GET /v1/jobs/:id/events, find the generation.submitted event, and subtract timestamps. The gap between job.created and job.started is time your job spent in Sume's queue, and the gap between job.started and generation.submitted is the hand-off. Everything after generation.submitted is the generation itself. This tells you whether a slow job is waiting for a seat or rendering.

What an event contains

The OpenAPI spec defines each event with seven fields. The data object holds sanitized details; raw provider task ids, raw provider URLs, callback signatures, API key metadata and idempotency keys are left out on purpose.

Job event shape from the OpenAPI spec, read 2026-10-08
FieldValues or type
typejob.created, job.queued, job.started, generation.submitted, job.completed, job.failed, job.canceled, webhook.delivery
sourcesume or webhook
statusinfo, pending, processing, succeeded, failed, canceled, retrying
created_atdate-time
id, message, datastring, string, object

A script that prints the timeline

The helper below keeps the last event of each type and returns the gaps in seconds. It needs no third-party package. Set JOB_ID and SUME_API_KEY, then run it. The gaps function is pure, so you can test it with a hand-written list of events.

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

def gaps(events):
    at = {e["type"]: datetime.fromisoformat(e["created_at"]) for e in events}
    pairs = [("job.created", "job.queued"), ("job.queued", "job.started"),
             ("job.started", "generation.submitted")]
    return {f"{a} -> {b}": (at[b] - at[a]).total_seconds()
            for a, b in pairs if a in at and b in at}

if __name__ == "__main__":
    job_id = os.environ["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:
        events = json.load(r)["data"]["events"]
    for e in events:
        print(e["created_at"], e["type"], e["status"], e["message"])
    print(gaps(events))

Reading the numbers

Events are a debugging tool. For the normal path, poll the status endpoint and follow next_action, and use events only when a job looks wrong.

  • A long job.created to job.started gap means the job waited for a processing seat. Check your plan's concurrency in the admission docs before you add more load.
  • A short wait and a long run after generation.submitted means the render is the slow part. Waiting more or polling less is the right response.
  • A webhook.delivery event with status retrying means your receiver is slow or returning a non-2xx. Fix the receiver before you blame the render.
  • No generation.submitted event on a failed job means it failed before it reached generation, so read the error in the job record.

Where to put this in a pipeline

Run the events read once per job, after it reaches a terminal state, and store the gaps next to the job record. A week of those numbers shows whether your waits come from your own burst size or from render time. If the first gap grows when you submit in large bursts, submit smaller waves. The admission docs describe a wave_size_hint in the generation_limits snapshot that you can use as a starting size.

The event list is a read, so it counts toward your read budget, which is 40 times the write budget on every plan. One read per finished job is far below any limit.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume