Sume ratelimit-reset: sleep until the 60-second window ends (Python)
Every Sume /v1 response carries ratelimit-remaining and ratelimit-reset. Stop at zero and sleep the reset seconds instead of eating a 429. A Python wrapper.

You can avoid most 429 responses from the Sume API by reading the headers on the successful ones. Every /v1 response, not only a refusal, carries ratelimit-limit, ratelimit-remaining and ratelimit-reset. The reset value is the number of seconds until the current fixed 60-second window ends. When ratelimit-remaining reaches 0, sleeping for that many seconds is cheaper than sending a request that will be refused.
Fixed window, not a sliding one
The window is fixed. The count for a bucket starts again at zero when the window ends, so remaining requests are not refilled one at a time. That is why the right wait is the reset value and not a fraction of the limit. The docs advise not counting requests yourself and reading ratelimit-remaining instead, because the headers describe the bucket that the current request spent from: a write for a POST, a read for a GET.
Two buckets, two sets of numbers
A poll returns the read numbers, and a submit returns the write numbers. Keep one tracker for each scope so that a read at remaining: 4700 does not hide a write at remaining: 0.
| Header | Meaning | Sent on |
|---|---|---|
| ratelimit-limit | Requests allowed in the window | Every response |
| ratelimit-remaining | Requests left in the window | Every response |
| ratelimit-reset | Seconds until the window resets | Every response |
| retry-after | Seconds to wait | 429 only |
Wrapper
The class below sleeps only when a bucket says it is empty. It is independent of any HTTP library: you pass the response headers in after each call, and it tells you how long to wait before the next one.
import time
class Budget:
def __init__(self, sleep=time.sleep):
self.sleep = sleep
self.state = {"read": (None, 0), "write": (None, 0)}
def update(self, scope, headers):
remaining = headers.get("ratelimit-remaining")
reset = headers.get("ratelimit-reset", 0)
if remaining is not None:
self.state[scope] = (int(remaining), int(reset))
def before(self, scope):
remaining, reset = self.state[scope]
if remaining == 0 and reset > 0:
self.sleep(reset)
self.state[scope] = (None, 0)
b = Budget(sleep=lambda s: print("sleeping", s))
b.update("write", {"ratelimit-remaining": "0", "ratelimit-reset": "17"})
b.before("write") # sleeping 17
b.before("read") # no waitKeep the 429 handler too
The headers can be stale for a request that was already in flight, and other workers on the same key share the bucket. Keep honoring retry-after on a 429 as the backstop. The two together mean the common case never errors, and the rare one recovers in one window.
Sources
Related posts
More in Developers
- Does a second Sume API key raise your rate limit? No, here is why
Each Sume API key has its own bucket, but the workspace owner has an account bucket too. A second key spreads load without adding requests per minute.
- Sume write limits as submits per second: a Python pacer by plan
Free 120, Pro 300, Startup 600, Scale 1200 writes per minute is 2, 5, 10 and 20 per second. A fixed-window pacer in Python that never trips the 429.
- Authenticate the Sume CLI on a CI runner without a browser login
On CI, skip sume login: install the CLI, run sume auth setup with an API key from a secret, and confirm with sume auth status before any job step.
- sume/auto for a former Sora feature: when to pin a model
Sume's sume/auto picks a family and never says which. Good for general clips, wrong when a brand needs one look. How to choose between auto and a pinned id.
Written by Sume