Reference
Rate limits and quotas
Each API key can run 3 jobs at a time, create 500 jobs a day and upload 20 GB a day, with days counted in UTC. Over a limit, the API answers 429 RATE_LIMITED with a Retry-After header that says how many seconds to wait.
Files, durations and request fields have fixed limits too, all listed below.
- Running at once
- 3 jobs
- per key
- Jobs a day
- 500
- resets at midnight UTC
- Uploads a day
- 20 GB
- resets at midnight UTC
Rate limits#
Limits apply per API key. Each one answers 429 with its own message and Retry-After value.
| Limit | error.message | Retry-After |
|---|---|---|
| 3 jobs running | this key already has 3 jobs running | 10 seconds |
| 500 jobs a day | this key reached its 500 jobs per day | Until midnight UTC |
| 20 GB of uploads a day | this key reached its 20 GB of uploads per day | Until midnight UTC |
| 30 bad keys a minute from one IP | too many failed auth attempts from your IP, retry in a minute | 60 seconds |
- A job holds one of its key's 3 slots from the create call until it succeeds or fails, including the time it waits in the queue.
- Every job that starts counts toward the 500, including jobs that fail. Requests rejected with a
4xxdon't count. - The upload budget counts the bytes of every
POST /v1/uploads. Remote URLs you pass as inputs don't count.
Handling 429#
Wait the number of seconds in Retry-After, then send the same request again. To stay under the limit in the first place, queue work on your side and run at most 3 jobs per key at once.
{
"error": {
"code": "RATE_LIMITED",
"message": "this key already has 3 jobs running"
}
}async function createJob(path, body) {
while (true) {
const res = await fetch(`https://api.reelsdk.com${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.REELSDK_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const json = await res.json();
if (res.status !== 429) return json;
// We only wait out short limits, a daily limit can take hours
const seconds = Number(res.headers.get("Retry-After") ?? "10");
if (seconds > 60) throw new Error(json.error.message);
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
}Sizes and durations#
| Limit | Value |
|---|---|
| JSON request body | 2 MB |
Upload with POST /v1/uploads | 100 MB |
| Input file, uploaded or remote | 500 MB |
| All inputs of one job | 1,000 MB |
| Video or audio input length | 15 minutes |
| Slideshow length | 15 minutes |
| Short length | 15 to 90 seconds |
| Job run time | 10 minutes |
| Files a job writes while it runs | 8 GB |
| Outputs of one job | 2 GB |
| Slideshow width and height | 240 to 2160 px |
| Image input | 100 megapixels |
| Wait on one request | 300 seconds |
| File retention | 7 days |
Request fields#
| Field | Limit |
|---|---|
Slideshow images | 1 to 100 |
Slideshow voiceover.text | 10,000 characters |
Captions words | 50,000 words |
Captions prompt | 500 characters |
Shorts topic | 500 characters |
Shorts script | 3,000 characters and 90 seconds of narration |
FFmpeg inputs | 50 files |
FFmpeg outputs | 20 files |
FFmpeg args | 1,000 arguments of 8,192 characters, or one 65,536 character string |
FFmpeg timeout_s | 600 seconds |
| Any URL | 4,096 characters |
Higher limits#
The defaults fit most apps, and limits can be raised per key. Email kevin@bitgate.com with what you're building and the numbers you need: jobs at once, jobs a day or upload volume.
Last updated 26 October 2026