Reference
Rate limits
Limits keep the platform fast for everyone. They are per API key, generous for normal integrations, and every response tells you where you stand.
Limits
| What | Limit |
|---|---|
| Public API, per API key | 600 requests per minute by default; higher on larger plans (up to 10 000/min) |
| Signed-in admin using the public API | Same budget, counted per admin and organization |
Join-token exchange (POST /join-tokens/exchange) | 300 per minute per IP address — a whole class behind one school network fits |
| Meeting session refresh | 120 per minute per IP address |
| Webhook “Send test event” | 10 per minute |
Bulk user import (POST /public/users/bulk) | Up to 1 000 users per request, processed as a background job |
Response headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 600 # requests allowed per minute for this key
X-RateLimit-Remaining: 597 # left in the current window
X-RateLimit-Reset: 1792058460 # unix seconds when the window resetsWhen the budget is used up the API answers 429 with a Retry-After header (seconds):
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{
"error": {
"code": "rate_limited",
"message": "Rate limit of 600 requests per minute exceeded. Retry in 12s.",
"details": { "limit": 600, "retryAfterSeconds": 12 }
},
"requestId": "req_…"
}Retrying well
- On
429, waitRetry-Afterseconds. On5xxand network errors, back off exponentially with jitter. - Send an
Idempotency-Keyon every POST so retries never create duplicates (how it works). - Don't retry
4xxerrors other than 408/409idempotency_in_progress/429 — fix the request instead.
async function gurukullive(path, init = {}, attempt = 0) {
const res = await fetch(`https://gurukullive.example.com/api/v1${path}`, {
...init,
headers: { Authorization: `Bearer ${process.env.VMEET_API_KEY}`, 'Content-Type': 'application/json', ...init.headers },
});
const retryable = res.status === 429 || res.status >= 500;
if (retryable && attempt < 5) {
const retryAfter = Number(res.headers.get('retry-after'));
const delay = retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 500 * 2 ** attempt) * (0.8 + Math.random() * 0.4);
await new Promise((r) => setTimeout(r, delay));
return gurukullive(path, init, attempt + 1);
}
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.error.message), { code: body.error.code, requestId: body.requestId });
return body;
}Use fewer requests
- Prefer webhooks to polling meeting status or recordings.
- Use
limit=100and the cursor when listing; filter server-side (status, class, date range,externalId). - Provision rosters with
/public/users/bulkand poll/public/jobs/:idinstead of one call per student.