Rate limits
Two rolling budgets per workspace — per minute and per day — with headers on every response so a client can slow down before it is told to.
Budgets by plan
| Plan | Per minute | Per day |
|---|---|---|
| Free | 60 | 10,000 |
| Pro | 300 | 50,000 |
| Scale | 1,000 | 500,000 |
| Organization | 600 | 200,000 |
| Organization (Enterprise) | 1,200 | 2,000,000 |
The budget belongs to the workspace: every key it mints, and the console session, draw from the same pool. A key can be narrowed below its plan when it is minted. Your effective numbers are on GET /flow/v1/auth/me under current_key.
/auth/me, /auth/account/*) and AI Connect calls are exempt from the daily budget — BYOK is billed by the provider and managed calls by credits, and a request quota would be a second charge. They are still throttled per minute. Open WebSocket connections consume no REST budget; they count against a separate cap of 3 concurrent connections per key.Headers
Every REST response, including a 429, carries the current state:
X-RateLimit-Limit-Minute: 60
X-RateLimit-Remaining-Minute: 42
X-RateLimit-Reset-Minute: 1745021580 # epoch seconds when the minute window resets
X-RateLimit-Limit-Day: 10000
X-RateLimit-Remaining-Day: 9847
X-RateLimit-Reset-Day: 1745049600
A well-behaved client reads X-RateLimit-Remaining-Minute and slows down before it reaches zero.
Handling 429
Over either budget, the API answers HTTP 429:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Minute rate limit (60) exceeded",
"details": {
"retry_after_seconds": 23,
"window": "minute"
}
}
}
The response also carries a standard Retry-After header with the same value in seconds. Wait that long before retrying; add exponential backoff on top for cascading overload.
Example: backoff
async function flowFetch(path, init = {}) {
const url = `https://api.openmarkets.ai/flow/v1${path}`;
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(url, {
...init,
headers: {
'X-API-Key': process.env.OPENMARKETS_API_KEY,
...init.headers,
},
});
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get('Retry-After') ?? 1);
await new Promise((r) => setTimeout(r, retryAfter * 1000 * (attempt + 1)));
}
throw new Error('Rate limited after retries');
}
Windows
- Fixed 60-second windows for the minute budget; fixed UTC-midnight windows for the day.
- No bursting across a boundary: at the start of each minute the budget resets to the full limit.
- Need more? Higher budgets come with the plan, or a per-key limit above the plan default on request. Plans are compared in the console.
Streams have their own limits — 200 contests per connection per channel, a 25-second heartbeat — on the Streaming page.