Skip to content

Pagination, Rate Limits, and Errors

Collection endpoints use cursor pagination. Every successful page uses the same envelope:

{
"items": [],
"nextCursor": null,
"asOf": "2026-09-11T15:30:00.000Z"
}
  1. Make the first request with your filters and optional limit.
  2. Process items.
  3. If nextCursor is not null, repeat the same request and pass that exact value as cursor.
  4. Stop when nextCursor is null.

Treat cursors as opaque secrets. Don’t decode, edit, or concatenate them, and don’t store one as a permanent resume token. Each cursor is cryptographically bound to its organization, filters, asOf window anchor, and position. If you change a filter and reuse the cursor, you get 400 INVALID_CURSOR.

Terminal window
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 upper bound of the time series steady while you page. It anchors pagination and the time window. It is not a transactional snapshot, so the current roster, course relationships, current progress, and deployment status can change between pages. A membership change can invalidate a roster-bound cursor, and other changes to current state can affect later pages. If that happens, discard the cursor and restart the export. If you need to show which window a completed export read from, store asOf with it.

Endpoint familyDefaultMaximum
Members and compact progress100200
Answer attempts100100
Deployments and events100500

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 hold more entries than limit. It can also be empty with a non-null nextCursor when none of the evaluated members have 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, pass userId and export one member at a time.

Responses deliberately leave out a total count. Keep paging until nextCursor is null.

Answers, deployments, and events accept RFC 3339 from and to filters with an explicit UTC designator or offset. Query timestamps can have at most millisecond precision, for example 2026-09-11T15:30:00.123Z or 2026-09-11T10:30:00.123-05:00, and the API converts them to UTC. from is inclusive and to is exclusive. If you leave them out, event and history queries use a bounded recent window. A single request can’t span more than 366 days, so split longer exports into adjacent windows.

Response rows and opaque cursors can keep microsecond precision, even though query inputs stop at milliseconds. Don’t truncate a cursor or rebuild one from a returned timestamp. Always pass nextCursor back unchanged.

Normal calls are limited to 60 requests per minute per key and 120 requests per minute per organization. Fresh progress calls have deliberately tighter limits: 6 per minute per key and 12 per minute per organization.

The edge gateway also applies a coarse flood ceiling per source. Whether a 429 comes from that guard or from the key and organization budgets, it uses the same error envelope and includes Retry-After.

A 429 response includes Retry-After. Wait that many seconds, add a small random delay, and then retry. Don’t retry right away in a tight loop. Using separate keys doesn’t get around the organization-wide budget.

Progress is live data, and full responses aren’t cached. Use the compact endpoint for scheduled exports, avoid polling each user individually, and fetch detailed progress only when you drill into one user.

Errors use a stable JSON shape:

{
"error": {
"code": "RATE_LIMITED",
"message": "Request rate limit exceeded",
"requestId": "…",
"retryAfterSeconds": 42
}
}
HTTP statusTypical codeAction
400BAD_REQUEST, INVALID_CURSORCorrect the parameter or restart pagination without a cursor
401UNAUTHORIZEDCheck the bearer header, expiry, and whether the key was revoked
403FORBIDDENCreate or use a key with the required permission
404NOT_FOUNDConfirm the user ID belongs to the key’s current organization
409CONFLICTThe current state doesn’t allow the action, for example the deployment isn’t running. Read the message and resolve that state before you retry; on deploy, read the admission object for the reason
409DEPLOYMENT_NOT_READYThe deployment is still deploying. Poll it until readiness is ready, then request the VPN config again
409VPN_GATEWAY_FAILEDThe deployment’s VPN gateway couldn’t be prepared; the message says why. Request again once the deployment is ready, or start or revert it to retry
409VPN_CONFIG_NOT_READYThe VPN config is still being created, or its creation failed. Poll the config list until it’s ready; if it failed, request a new config
429RATE_LIMITEDHonor Retry-After
500INTERNAL_ERRORRecord the request ID and retry cautiously; contact Rogue Labs if it persists
503UPSTREAM_UNAVAILABLERetry with exponential backoff; the API never substitutes empty training data for an upstream failure

Retry 429 and transient 503 responses. Don’t automatically retry authorization or validation failures.