Pagination, Rate Limits, and Errors
Collection endpoints use cursor pagination. A successful page has the same envelope everywhere:
{ "items": [], "nextCursor": null, "asOf": "2026-09-11T15:30:00.000Z"}Follow the cursor
Section titled “Follow the cursor”- Make the first request with your filters and optional
limit. - Process
items. - If
nextCursoris notnull, repeat the same request and pass that exact value ascursor. - Stop when
nextCursorisnull.
Treat cursors as opaque secrets. Do not decode, edit, concatenate, or persist one as a permanent resume token. A cursor is cryptographically bound to its organization, filters, asOf window anchor, and position. Changing a filter while reusing it returns 400 INVALID_CURSOR.
curl --get --fail-with-body \ --header "Authorization: Bearer $ROGUE_ARENA_TEAM_API_KEY" \ --data-urlencode "types=userAnsweredQuestion" \ --data-urlencode "cursor=$NEXT_CURSOR" \ "https://arena.roguelabs.io/api/v1/team/events"asOf holds the time-series upper bound stable while paging. It is a pagination and window anchor, not a transactional snapshot of the current roster, course relationships, current progress, or deployment status. Those can change between pages. A membership change may invalidate a roster-bound cursor, and other current-state changes can affect subsequent pages. If that happens, discard the cursor and restart the export. Store asOf with a completed export if you need to explain the window through which records were read.
Page sizes
Section titled “Page sizes”| Endpoint family | Default | Maximum |
|---|---|---|
| Members and compact progress | 100 | 200 |
| Answer attempts | 100 | 100 |
| Deployments and events | 100 | 500 |
For compact progress, limit is the number of members evaluated on that page.
A member can have several course, scenario, or curriculum rows, so the items
array can contain more entries than limit. It can also be empty while
nextCursor is non-null when the evaluated members have no active progress.
Always follow nextCursor; only a null cursor marks the end of the export.
Team-wide event exports support organizations with up to 5,000 current members. For a larger roster, supply userId and export one member at a time.
Responses intentionally do not calculate a total count. Continue until nextCursor is null.
Time windows
Section titled “Time windows”Answers, deployments, and events accept RFC 3339 from and to filters with an explicit UTC designator or offset. Query timestamps may include at most millisecond precision, for example 2026-09-11T15:30:00.123Z or 2026-09-11T10:30:00.123-05:00; the API canonicalizes them to UTC. from is inclusive and to is exclusive. When omitted, event and history queries use a bounded recent window. A single request cannot span more than 366 days; divide longer exports into adjacent windows.
Response rows and opaque cursors can preserve microsecond precision even though query inputs stop at milliseconds. Do not truncate or reconstruct a cursor from a returned timestamp; always pass nextCursor back unchanged.
Rate limits
Section titled “Rate limits”Normal calls are limited to 60 requests per minute per key and 120 requests per minute per organization. Fresh progress calls are deliberately tighter: 6 per minute per key and 12 per minute per organization.
The edge gateway also applies a coarse per-source flood ceiling. Whether a 429 comes from that guard or the key and organization budgets, it uses the same error envelope and includes Retry-After.
A 429 response includes Retry-After. Wait for that many seconds, add a small random delay, and retry. Do not immediately retry in a tight loop. Separate keys do not bypass the organization-wide budget.
Progress is live data and is not cached as a full response. Prefer the compact endpoint for scheduled exports, avoid polling every user individually, and fetch detailed progress only for drill-downs.
Error envelope
Section titled “Error envelope”Errors use a stable JSON shape:
{ "error": { "code": "RATE_LIMITED", "message": "Request rate limit exceeded", "requestId": "…", "retryAfterSeconds": 42 }}| HTTP status | Typical code | Action |
|---|---|---|
| 400 | BAD_REQUEST, INVALID_CURSOR | Correct the parameter or restart pagination without a cursor |
| 401 | UNAUTHORIZED | Check the bearer header, expiry, and whether the key was revoked |
| 403 | FORBIDDEN | Create or use a key with the required permission |
| 404 | NOT_FOUND | Confirm the user ID belongs to the key’s current organization |
| 429 | RATE_LIMITED | Honor Retry-After |
| 500 | INTERNAL_ERROR | Record the request ID and retry cautiously; contact Rogue Labs if it persists |
| 503 | UPSTREAM_UNAVAILABLE | Retry with exponential backoff; the API never substitutes empty training data for an upstream failure |
Retry 429 and transient 503 responses. Do not automatically retry authorization or validation failures.