Core concepts
Rate limits
Two independent limits protect the service: requests per minute per key, and jobs running at once per account.
Limits by plan
| Limit | Influencer | Agency |
|---|---|---|
| Sustained generations per minute, per account | ~5 | ~10 |
| Requests per minute, per key | 120 | 240 |
| API jobs running at once, per account | 10 | 20 |
| Active API keys | 5 | 20 |
| Webhook endpoints | 5 | 20 |
| Social accounts included | 5 | 15 |
| Characters / products | 10 / 20 | 30 / 50 |
Request rate
Each key has a token bucket that refills continuously at its per-minute rate, so short bursts are fine. Every response carries X-RateLimit-Limit. Over the limit you receive 429 RATE_LIMITED with a Retry-After header in seconds.
Concurrent jobs
A job is a generation that is queued or processing. When the account already has its maximum running, new image and video requests return 429 CONCURRENCY_LIMIT (Retry-After: 15) without charging. The slot frees as soon as a generation finishes, fails or is cancelled.
See your live numbers at GET /account (limits.concurrent_jobs and usage_now.active_jobs) or in Dashboard → Developers.
limits.concurrent_jobs − usage_now.active_jobs.Throughput
Throughput per account is set by the job limit and how long generations take:
| Typical | At busy times | |
|---|---|---|
| Image, prompt only | ~60–90 s | up to ~4 min |
| Image with character + scene | ~1.5–2 min | up to ~5 min |
| Video | 1–6 min | longer for 12–15 s clips |
So an account with N parallel jobs sustains roughly N ÷ 2 images per minute — about 5/min on Influencer and 10/min on Agency. Capacity is shared with other customers; limits are sized so everyone keeps these rates.
Need more? Email support@oriorai.com with your expected volume — higher limits are set per account, and GET /account reflects them immediately.
Handling 429s
async function withRetry(send, attempts = 5) {
for (let attempt = 0; ; attempt++) {
const res = await send()
if (res.status !== 429 || attempt === attempts - 1) return res
const wait = Number(res.headers.get('retry-after')) || 2 ** attempt
await new Promise(resolve => setTimeout(resolve, wait * 1000))
}
}
// Reuse the same Idempotency-Key across retries of one logical request.
const key = crypto.randomUUID()
const res = await withRetry(() => fetch(url, { method: 'POST', headers: { ...auth, 'Idempotency-Key': key }, body }))Rate-limited responses are never stored against the Idempotency-Key, so retrying with the same key is safe.