Skip to content
GurukulLiveDevelopers
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

WhatLimit
Public API, per API key600 requests per minute by default; higher on larger plans (up to 10 000/min)
Signed-in admin using the public APISame 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 refresh120 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
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 resets

When the budget is used up the API answers 429 with a Retry-After header (seconds):

http
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, wait Retry-After seconds. On 5xx and network errors, back off exponentially with jitter.
  • Send an Idempotency-Key on every POST so retries never create duplicates (how it works).
  • Don't retry 4xx errors other than 408/409 idempotency_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=100 and the cursor when listing; filter server-side (status, class, date range, externalId).
  • Provision rosters with /public/users/bulk and poll /public/jobs/:id instead of one call per student.