blog/developers·Aug 31, 2026·6 min·by the eroq team

AI video API rate limits explained — caps, multipliers, retries

The per-key per-minute caps on eroq, how the plan multiplier and the per-member share change them, and how to back off correctly when you see a 429.


Rate limits are the part of an API you ignore until a Tuesday afternoon, when a batch job and a product launch land in the same minute and everything starts returning 429. Then you discover that your retry logic is a setTimeout of one second in a loop, which is not backing off — it is the same stampede at a slightly slower tempo.

This is how eroq's limits are calculated, in the order the numbers are applied, and how to write a client that behaves when it hits one.

The base caps, per key, per minute

Three numbers, counted per API key over a sliding sixty-second window:

  • Chat — 60 requests per minute. Covers POST /v1/chat/completions, streaming or not. An SSE stream counts once, at the moment you open it, not per token.
  • Images — 20 requests per minute. One call with a batch of four counts as one request.
  • Video and speech — 6 requests per minute each.

Six video requests a minute looks small until you remember what a video request is: a submission, not a render. POST /v1/videos/generations returns a job id immediately and the clip arrives minutes later, so six submissions a minute is six new renders a minute, which is a serious amount of production work. The full endpoint reference is on /docs/video.

Note the unit: per key, not per account. Two keys are two independent windows. That is the mechanism behind the advice to issue one key per pipeline — a runaway loop in staging throttles staging.

The plan multiplier

An active plan multiplies every one of those base caps, on every key in the workspace:

  • No plan and Hobby —
  • Creator —
  • Studio —
  • Team —
  • Agency —

So video submissions go from 6 per minute to 24 on Studio and 48 on Agency, and chat goes from 60 to 240 and 480. Key counts scale alongside — 10 keys on Hobby, 20 on Creator, 50 on Studio, 100 on Team, 200 on Agency — which means the real ceiling on a big plan is the multiplier times the number of keys you are willing to operate. The grid is on /pricing.

The per-member share, on top

Inside a workspace, a member's role narrows what their keys get. A Developer or Admin gets the full plan allowance; a Creator starts at half of it, on the theory that interactive studio work should not be able to starve a production pipeline sharing the same plan.

An admin can narrow this further per member, as a percentage, on the Members tab at /dashboard/workspace. The override can only reduce the role's share, never raise it above what the role grants.

The arithmetic composes in one direction, so it is easy to predict:

effective limit = base cap × plan multiplier × member share

A Creator-role teammate on a Studio plan, calling the video endpoint:

6 × 4 × 0.5 = 12 video submissions per minute, per key

Narrow that member to 25% and they get 6. Two things do not happen here, and both are deliberate. A narrowed share never throttles the free, read-only endpoints — listing jobs or reading the account balance still works. And a member who is not allowed to generate at all is refused with a clear permission error at the moment credits would move, not with a stream of mysterious 429s.

What a 429 actually looks like

You get the status, a Retry-After header in seconds, and a body that names the scope and the limit it applied:

{
  "error": {
    "message": "Rate limit reached for video (24/min per key). Retry in 37s.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded"
  }
}

The header is the useful part. Because the window slides, Retry-After is not a fixed cooldown — it is the number of seconds until the oldest request in your current window ages out and a slot opens. Sleeping exactly that long is correct; sleeping one second and trying again is not.

Backing off properly

Four rules, in priority order.

1. Honor Retry-After when it is there. It is computed from the actual window, so it beats any heuristic you invent.

2. Use exponential backoff with jitter when it is not. Doubling alone re-synchronizes every client you have into the same retry instant. Full jitter — a random delay between zero and the current ceiling — spreads them out.

3. Do not retry errors that are not transient. 402 insufficient_credits, 403 plan_required, 403 role_forbidden and a content_blocked refusal will return exactly the same answer in thirty seconds. Surface them. A 503 model_unavailable means the engine is not currently servable for your account — a different engine from /models is a better response than a retry loop.

4. Cap concurrency instead of retrying into a wall. A semaphore sized at or just under your effective limit turns rate limiting from an error path into a scheduling problem, which is where it belongs.

Here is the whole thing, small enough to paste:

const sleep = ms => new Promise(r => setTimeout(r, ms))

async function submitVideo(body, { attempts = 5 } = {}) {
  let delay = 1000
  for (let attempt = 1; attempt <= attempts; attempt++) {
    const res = await fetch('https://eroq.ai/v1/videos/generations', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    })

    if (res.ok) return res.json()          // 202 with a job id
    if (res.status !== 429) {              // 402/403/503 are not transient
      throw new Error(`${res.status} ${(await res.json()).error?.code}`)
    }

    const header = Number(res.headers.get('retry-after'))
    const wait = Number.isFinite(header) && header > 0
      ? header * 1000
      : Math.random() * delay              // full jitter
    await sleep(wait)
    delay = Math.min(delay * 2, 30_000)
  }
  throw new Error('rate limited after all attempts')
}

Five attempts is a reasonable ceiling for a user-facing path. For a batch worker, drop the retry loop entirely and let a queue with a fixed submission rate do the pacing — you will hit the limit far less often, and when you do, the queue is the natural place to wait.

Which limit you actually hit first

In practice, most teams never touch the video cap. Six submissions a minute is 360 an hour, and a wallet empties long before a rate limiter complains — credits and render wall-clock time are the binding constraints for video work, which is why the budgeting article and /pricing matter more here than this one does.

Chat is the opposite. Sixty requests a minute on a free or Hobby plan is genuinely reachable the moment a companion app or a roleplay product gets a busy evening, and each user turn is a request. If you are building on chat, plan for the multiplier, spread load across keys per surface, and read the SSE streaming guide before you design the retry path — a stream that dies mid-response needs different handling from a request that never started.

Images sit in between. Twenty a minute is ample for interactive use and tight for a bulk catalog job, which is another argument for a queue you control rather than a fan-out of parallel requests.

FAQ

Are eroq's rate limits per key or per account?

Per key. Each API key gets its own sliding one-minute window, which is why issuing one key per environment or pipeline keeps a runaway loop from throttling everything else in the workspace.

Does upgrading my plan raise the rate limits?

Yes. The documented caps are the base and the plan multiplies them on every key — 2× on Creator, 4× on Studio, 6× on Team, 8× on Agency. Key counts rise with the plan too.

What should I do when I get a 429 from the video endpoint?

Read the Retry-After header and sleep exactly that long, then retry once. If the header is missing, use exponential backoff with full jitter, and cap your concurrency so the next batch does not walk into the same wall.

Read the endpoint reference on /docs, then check what your plan multiplies on /faq.

Tagsrate-limitsapiretriesvideo-api

Make this with the models behind the post — start with 50 free credits , or browse every engine and its price .