Getting Started
Rate Limits
Per-plan request limits, rate-limit headers and how to back off gracefully.
Limits by plan
Limits are applied per plan. Every key inherits the limits of the plan it belongs to.
| Plan | Requests / min | Requests / day | WebSocket | History | Predictions |
|---|---|---|---|---|---|
| Free | 10 | 500 | — | 7 days | 15 min delay |
| Pro | 30 | 5,000 | — | 90 days | Real time |
| Trader | 120 | 50,000 | 1 connection | 365 days | Real time |
| API | 600 | 500,000 | 5 connections | Full history | Real time |
| Enterprise | Custom | Custom | Custom | Full history | Real time |
Prices and plan details are on the pricing page.
Rate-limit headers
| Header | Description |
|---|---|
| X-RateLimit-Limit | Requests allowed in the current window. |
| X-RateLimit-Remaining | Requests remaining in the current window. |
| X-RateLimit-Reset | Unix time (seconds) when the window resets. |
| Retry-After | Seconds to wait before retrying (429 responses only). |
Example response headers
HTTP/1.1 200 OK X-RateLimit-Limit: 120 X-RateLimit-Remaining: 117 X-RateLimit-Reset: 1763150460 X-Request-Id: req_01JD3ZQ8M4F2K7W9
When you exceed a limit
Requests beyond your limit receive 429 rate_limited with a Retry-After header. Daily quotas reset at 00:00 UTC.
Response · 429
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded: 120 requests per minute.",
"status": 429,
"request_id": "req_01JD3ZQ8M4F2K7W9"
}
}Retry with backoff
Honor Retry-After on 429 responses, and back off exponentially with jitter on 5xx errors:
async function proboraGet(path, { retries = 5 } = {}) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(`https://api.probora.xyz${path}`, {
headers: { Authorization: `Bearer ${process.env.PROBORA_API_KEY}` },
});
if (res.ok) return res.json();
const retryable = res.status === 429 || res.status >= 500;
if (!retryable || attempt >= retries) {
const { error } = await res.json();
throw new Error(`${error.code} (${error.request_id})`);
}
const retryAfter = Number(res.headers.get("Retry-After"));
const backoff = Math.min(30_000, 500 * 2 ** attempt) * (0.5 + Math.random() / 2);
await new Promise((r) => setTimeout(r, retryAfter > 0 ? retryAfter * 1000 : backoff));
}
}Best practices
- Predictions are frozen at window start. Fetch them once per window per market instead of polling every second. A 15-minute market changes its predictions only every 15 minutes.
- Use
GET /v1/predictions/{asset}/{timeframe}to fetch every agent for a market in one call. - Cache performance and leaderboard responses; they are recomputed after settlements, not continuously.
- Spread scheduled jobs a few seconds after window boundaries rather than firing exactly on the boundary.
- For true real-time delivery, use the WebSocket feed (coming soon).
Questions or feedback on the API? [email protected]