Page shape
array
The rows of this page, ordered by
updated_at, then by id.string | null
Pass this as
cursor to get the next page. null on the last page.boolean
true when another page follows.Query parameters
integer
default:"50"
Rows per page, from 1 to 200.
GET /exams/{id}/results is the exception: 1 to 50, default 20. A value out of range is 422 validation_failed.string
The
next_cursor of the previous page, exactly as received. It is opaque: do not build or edit it. Anything else is 400 invalid_cursor.string
Only rows whose
updated_at is strictly after this time. An ISO 8601 UTC timestamp, for example 2026-10-01T09:00:00Z. Fractional seconds are accepted, so you can pass back an updated_at value as is. A value that does not parse is 422 validation_failed.updated_since, status and so on) on every page of one pass and add cursor. Cursors do not expire, but they are tied to the ordering, not to a snapshot: rows that change while you are paging move to the end and are served again later.
Paginated endpoints
Filter values:
statustakes one or more submission statuses, comma-separated:queued,processing,reading,grading,graded,partially_graded,failed,cancelled.needs_reviewandfinalizedtaketrueorfalse.includeon results takesannotationsandmodel_answer;extracted_answeris included by default and-extracted_answerleaves it out.
Reading a whole list
Keeping in sync
Evalezy does not send webhooks yet (see the Roadmap). Instead, poll the submission feed,GET /submissions?updated_since=…. A submission’s updated_at moves whenever anything you can see about it changes: queued to grading to graded, a failure, a cancellation, a teacher’s override in the dashboard, approval, finalize or unfinalize, replacement or deletion. Grading progress shows up in the feed within a few seconds.
Because rows are ordered by updated_at and a changed row always moves to the end, a loop that remembers how far it got never misses a change.
1
Keep a watermark
Store the
updated_at of the last row you processed. Start with a time before your first submission, for example the start of the exam day.2
Each poll, read everything changed since the watermark
Call
GET /submissions?updated_since=<watermark minus 2 minutes>&limit=200 and follow next_cursor until has_more is false. The small overlap catches rows that were committed a moment late.3
Process idempotently
Upsert each submission by
id, and skip a row whose updated_at is not newer than the one you already stored. The overlap means you will see some rows twice; this check makes that harmless.4
Advance the watermark and sleep
Set the watermark to the newest
updated_at you processed, then wait 30 to 60 seconds before the next poll.What to do with each row
The feed returns the same submission object asGET /submissions/{id}. The fields that drive a sync:
Polling the feed once a minute uses a tiny fraction of your read limit. Prefer one feed loop for the whole institute over polling each submission.