Bannerbear 60 POSTs per 10 seconds vs Sume's per-minute budgets

Bannerbear allows 60 POST requests per 10-second window. Sume budgets writes and reads per minute by plan: 120 to 1200 writes, reads 40 times higher.

5 min readSume
All posts

Bannerbear's rate limit is 60 POST requests per 10-second window. Sume has no 10-second window: it gives each API key a per-minute budget set by plan, with separate budgets for writes (120 on Free, up to 1200 on Scale) and reads, which get forty times the write number.

The Bannerbear figures are from its API reference; the Sume figures are from the authentication and generation admission docs.

What does Bannerbear limit?

Bannerbear's reference says the API rate limit is 60 POST requests per 10 second window, and that the API is primarily asynchronous: a POST returns 202 Accepted and the result arrives by webhook or polling. Separately, exceeding the monthly quota returns 402 Payment Required, and usage resets monthly with your plan.

The window is short, so a burst of 60 submits in the first second then stalls until the window moves. Retrying faster only extends the stall.

What does Sume limit?

Sume's budget is per minute and per key: Free 120 writes, Pro 300, Startup 600, Scale 1200. Reads have their own budgets of 4800, 12000, 24000 and 48000. A read is any GET or HEAD, so a status-poll loop cannot 429 your own submits. A 429 names its bucket in error.details.scope as read or write, and responses may carry ratelimit-limit, ratelimit-remaining, ratelimit-reset and retry-after.

Request rate is separate from capacity. How many paid generation jobs run at once is plan concurrency: Free 1, Pro 4, Startup 8, Scale 20, with queue capacity max(3, concurrency × 5) by default. Past that, submits fail with 429 queue_full.

Rate limits, read 2026-10-02
LimitBannerbearSume (Pro plan)
Window10 seconds1 minute
Writes60 POSTs per window300 per minute
ReadsNot stated in the page I read12000 per minute, separate bucket
Quota error402 Payment Required402 insufficient_credits
Capacity errorNot stated429 queue_full past 24 accepted jobs

How do you convert a Bannerbear throttle to Sume?

Sixty POSTs per 10 seconds is six per second, or 360 per minute at a steady rate. On a plan with 300 writes a minute you must spread the same workload slightly wider, and the real limiter will usually be concurrency, not request rate. Queue submits on your side, honour retry-after, and send an Idempotency-Key with every write so a retry after a timeout does not create a second job.

import asyncio
import random


async def submit_with_backoff(send, attempts: int = 5):
    # send() returns (status_code, headers, body)
    for n in range(attempts):
        status, headers, body = await send()
        if status != 429:
            return status, body
        wait = float(headers.get("retry-after", 2 ** n))
        await asyncio.sleep(wait + random.random())
    raise RuntimeError("still rate limited")


async def main():
    async def send():
        return 202, {}, {"ok": True}

    print(await submit_with_backoff(send))


asyncio.run(main())

What should you watch in practice?

Check ratelimit-remaining instead of counting your own requests, and read generation_limits for concurrency. Sume does not publish a per-second burst figure, so a spike can be rejected even when your per-minute average is fine; treat 429 as a signal to back off and keep the same idempotency key on the retry.

What about retries and idempotency?

Rate limits and retries interact. If a submit times out and you retry blindly, you may create two jobs and pay twice. Sume requires an Idempotency-Key on its media writes and the errors docs warn not to retry unsafe submits without one. Use a stable key per logical request, such as a hash of the source and the program.

Bannerbear's reference offers a metadata string on requests for tracking; the page I read does not describe an idempotency header, so keep your own request ledger if you stay on it.

  • Back off on retry-after when present.
  • Reads and writes are separate buckets on Sume.
  • A 429 names its bucket in error.details.scope.

Sources

Related posts

More in Comparisons

All Comparisons posts

Written by Sume