Errors and limits

What failure looks like, and how to stay inside the boundaries.

ProposedNot yet callable

This documents a design under review. Endpoints, payloads and field names may change before release.

Error shape

Every error shares one shape.

{
  "error": {
    "type": "invalid_request",
    "code": "session_closed",
    "message": "Session ses_55ab20 closed at 2026-07-19T10:05:00Z.",
    "param": "session_id"
  }
}

Branch on code, not on message. Messages are written for humans and will be reworded.

Status codes

CodeMeaning
400Malformed request — bad JSON, unknown field, wrong type.
401Missing or unrecognised API key.
403Valid key without the required scope.
404No such record, session or student.
409Conflict — usually a write against a closed session.
422Well-formed but rejected, e.g. a status outside the enum.
429Rate limited. Honour Retry-After.
5xxOur fault. Retry with backoff; writes are idempotent.

Rate limits

  • 120 requests per minute per key.
  • Responses carry X-RateLimit-Remaining and X-RateLimit-Reset.
  • Exceeding it returns 429 with Retry-After in seconds. Back off; do not spin.

Working at scale

For a nightly full sync, prefer /sessions/{id}/roll over paging every record — one request per session instead of thousands, and it includes students with no record.

Better still, take the live feed from webhooks and use this API only for reconciliation and corrections. A busy institution that polls this API all day will spend most of its rate limit being told nothing changed.