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.

4 min readSume
All posts

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.

Headers on every /v1 response (read 2026-10-04)
HeaderMeaningSent on
ratelimit-limitRequests allowed in the windowEvery response
ratelimit-remainingRequests left in the windowEvery response
ratelimit-resetSeconds until the window resetsEvery response
retry-afterSeconds to wait429 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 wait

Keep 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

All Developers posts

Written by Sume