Errors & conventions
Every response uses the same envelope, every error has a stable machine-readable code, and every POST can be retried safely.
Envelope
{
"data": [ { "id": "cm2…", "externalId": "student-1001", "fullName": "Aarav Patel", "role": "STUDENT" } ],
"meta": { "nextCursor": "eyJ0Ijoi…", "hasMore": true }
}{
"error": {
"code": "insufficient_scope",
"message": "This API key is missing the required scope(s): meetings:write.",
"details": { "required": ["meetings:write"], "missing": ["meetings:write"] }
},
"requestId": "req_01JA7Z…"
}Branch on error.code, not on the message (messages may be reworded). Include requestId when you contact support.
Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | validation_failed | The body or query is invalid; details lists each problem. |
| 401 | unauthenticated | No credentials were sent. |
| 401 | invalid_api_key | The key is malformed or unknown. |
| 401 | api_key_revoked / api_key_expired | The key no longer works — create a new one. |
| 401 | invalid_join_token / join_token_revoked | A join token is wrong or was revoked. |
| 401 | session_expired / session_revoked | A meeting session can no longer be refreshed — mint a new join link. |
| 402 | plan_limit_reached | The plan does not include this feature or limit (API access, participants, storage …). |
| 403 | forbidden | The credentials are valid but not allowed to do this. |
| 403 | insufficient_scope | The API key lacks a scope; details.missing lists it. |
| 403 | ip_not_allowed | The request came from outside the key’s IP allowlist. |
| 403 | wildcard_scope_forbidden | Only the organization owner can create * keys. |
| 403 | user_deactivated / not_a_member / not_enrolled / not_invited | The user may not join this meeting. |
| 404 | not_found | The resource does not exist in your organization. |
| 409 | conflict / external_id_conflict | A unique value (e.g. externalId, e-mail) is already used. |
| 409 | idempotency_in_progress | A request with the same Idempotency-Key is still running — retry shortly. |
| 409 | too_many_api_keys | The organization reached its active API key limit. |
| 410 | join_token_expired / join_token_used | Mint a new join link. |
| 409 | meeting_cancelled | The meeting was cancelled. |
| 410 | meeting_ended | The meeting is over. |
| 422 | idempotency_key_reused | The Idempotency-Key was used with a different request. |
| 429 | rate_limited | Too many requests — see Retry-After. |
| 500 | internal_error | Something failed on our side; safe to retry with the same Idempotency-Key. |
| 503 | service_unavailable | Temporarily unavailable (e.g. media service); retry with backoff. |
New codes may be added; treat unknown codes by their HTTP status.
Pagination
List endpoints take limit (default 50, maximum 100) and cursor. Pass meta.nextCursor back as cursor until meta.hasMore is false. Cursors are opaque.
let cursor;
do {
const qs = new URLSearchParams({ limit: '100', ...(cursor ? { cursor } : {}) });
const { data, meta } = await gurukullive(`/public/users?${qs}`);
data.forEach(syncUser);
cursor = meta.hasMore ? meta.nextCursor : undefined;
} while (cursor);Idempotency
Send Idempotency-Key: <unique string> on POST requests. For 24 hours, repeating the same request with the same key returns the original response (with Idempotent-Replayed: true) instead of acting twice. Reusing a key for a different request fails with idempotency_key_reused; a retry while the first request is still running gets idempotency_in_progress. Good keys come from your own ids, e.g. create-meeting-<your class id>-<date>.
Other conventions
- JSON and camelCase everywhere; send
Content-Type: application/json. - Timestamps are ISO-8601 in UTC (
2026-10-14T09:30:00.000Z);timeZonefields are IANA names (Asia/Kolkata). - Your ids: users, classes, courses, batches and meetings accept an
externalIdunique per organization; most paths accept either the GurukulLive id or yourexternalId. - CSV: attendance endpoints return CSV with
Accept: text/csv. - Versioning: everything here is
/api/v1; see the changelog for the compatibility promise.