Exams you create through the API also appear in your institute’s Vacademy dashboard, tagged Source: API. Teachers can review and override marks there too, and their changes show in the API result.
Setup for the code samples
Setup for the code samples
The Python and Node samples on this page assume this setup. The Node samples use top-level
await, so run them as ES modules (a .mjs file, or "type": "module" in package.json).Read a result
GET /submissions/{submission_id}/result (scope evaluation:read).
status: "pending". Results of replaced submissions stay readable; a deleted submission’s result answers 404 submission_not_found.
The include parameter
Combine values with commas:
?include=model_answer,annotations.
Result fields
string
The submission this result belongs to.
string
The exam.
object
{ "id", "external_id", "name", "roll_number" }. name is null on exams created with blind: true. Results never include an email address.boolean
true when any question needs review, or the submission has its own review reason.string[]
Submission-level reasons, such as
pages_beyond_vision_limit.boolean
Whether the result is final.
string | null
When it was finalized.
integer | null
The rubric version the copy was graded with.
boolean | null
Reserved. Currently always
null.object
object[]
One entry per question of the paper, in paper order. See Question fields.
object
{ "available", "download_path" }. download_path is relative to the API base URL and is null when no checked copy exists. See Checked copy.string | null
When grading completed.
number | null
What this submission was charged. See Quotes and credits.
object | null
{ "code", "message" } when the submission failed.Question fields
string
The question id.
string
The question label, such as
21 or 4(b).string | null
The section name, such as
B.string
graded, failed (the AI could not grade this answer), pending (still being graded), cancelled, or not_answered (no answer found; scores 0).boolean
Whether the marks count towards the total. Always
true unless the exam uses choice groups (“attempt any N of M”), which are in beta and enabled on request.number | null
Marks awarded.
null while pending, and for failed questions.number
The question’s maximum marks.
string | null
Who set the marks:
ai: the AI.ai_reviewed: the AI, then a teacher overrode them (through the API or the dashboard).auto: marked automatically against the answer key (typed objective answers).
number | null
The AI’s confidence in its marks, from 0 to 1.
null for automatic marks.boolean
Whether a teacher should check this question. See Needs review.
string[]
Why the question needs review.
string | null
The text the grader worked from: the transcription of the handwriting, or the typed text. For typed answers, spacing and line breaks may differ from what you sent; angle brackets and markup are kept as plain text. Keep your own copy if you need it verbatim. Included unless you pass
include=-extracted_answer.string | null
Feedback on the answer, in plain text.
object[]
Marks per rubric criterion:
[{ "name", "awarded", "max", "reason" }]. Empty when the question has no criteria (for example an MCQ).string | null
Only with
include=model_answer.object[]
Only with
include=annotations.object
Present once a teacher has overridden or approved the question:
{ "reviewer": { "ref", "name" }, "reason", "edited", "edited_at", "approved" }. reviewer and reason appear only when they were sent.object | null
{ "code", "message" } for a failed question. Review it by hand.Needs review
Evalezy flags what the AI was unsure about, so teachers check a few answers instead of every copy. A question needs review when one of these holds, unless a teacher has already overridden or approved it:
A submission needs review when any of its questions does, or when it has a submission-level reason (
pages_beyond_vision_limit for copies of 41 to 80 pages).
To find work for your teachers, filter on the flag:
Override marks
PATCH /submissions/{submission_id}/questions/{question_id} (scope evaluation:review) sets a question’s marks and feedback, for example after a teacher’s review.
number
required
The new marks: from 0 to the question’s maximum, in steps of 0.5. Anything else is refused with
422 invalid_marks.string
New feedback, plain text up to 4,000 characters. Leave it out to keep the current feedback.
object
Your reference for the teacher:
{ "ref", "name" }, each up to 120 characters. It is shown in the result and in the dashboard.string
Why the marks changed, up to 500 characters.
200 with the updated question: source becomes ai_reviewed, needs_review becomes false, and review.edited is true. The totals are recalculated.
You can override graded and failed questions. Overriding is refused with:
409 evaluation_in_progresswhile the question is still being graded,409 submission_finalizedonce the result is finalized,422 validation_failed(field codenot_ai_graded) for a question with no AI result, such as a typed objective question that was marked automatically,404 question_not_foundfor a question that is not on the exam.
Approve a result
POST /submissions/{submission_id}/approve (scope evaluation:review) records that a teacher checked the AI’s marks and accepts them unchanged. Every question is marked approved, submission-level reasons are cleared, and needs_review becomes false. The body is optional:
200 with the submission object. Approval is refused while grading is running (409 evaluation_in_progress) and after finalize (409 submission_finalized). A re-evaluation clears the approval.
Download the checked copy
For handwritten copies, Evalezy renders a checked copy: the candidate’s PDF with the marks and comments written on it. Whenchecked_copy.available is true, download it with GET /submissions/{submission_id}/checked-copy (scope evaluation:read).
Content-Type: application/pdf, sent as an attachment named checked-copy-{submission_id}.pdf), authenticated by your API key. You do not need to store a copy: you can fetch it again whenever a parent or student asks for it.
No checked copy yet (not graded, or the copy could not be rendered) answers
404 checked_copy_not_found.
Finalize and unfinalize
Finalizing turns draft results into final marks. Finalized results cannot change: overrides, approvals, re-evaluations, replacements and deletes are all refused with409 submission_finalized until the result is unfinalized.
Finalizing only changes the result’s state. Evalezy sends no emails, report cards or notifications to candidates: what you do with final marks is up to your system.
Finalize
POST /exams/{exam_id}/finalize (scope evaluation:finalize). Send exactly one of submission_ids or all_graded.
string[]
Up to 500 live submissions of this exam. Any id that is not a live submission of the exam fails the whole call with
404 submission_not_found, listing the ids in details.submission_ids.boolean
true finalizes every graded submission of the exam that is not finalized yet, oldest first, up to 500 per call. Submissions in any other status are left out entirely and do not appear in skipped. When more remain, the response has has_more: true: call again.boolean
default:"false"
Also finalize
partially_graded and failed submissions. Questions that failed score 0. Use this only after a teacher has handled them.skipped lists submissions you named in submission_ids that could not be finalized. With all_graded, only eligible submissions are selected, so skipped is normally empty; to find the ones left out, list GET /exams/{exam_id}/submissions?finalized=false.
A draft exam answers
409 exam_not_open. Once every live submission of an exam is finalized, the exam’s status becomes finalized; a new submission makes it open again.
Unfinalize
POST /submissions/{submission_id}/unfinalize (scope evaluation:finalize) puts one finalized result back into draft, for example for a revaluation request. A reason of up to 500 characters is required and is recorded in the audit log.
200 with the submission object. You can then override, re-evaluate and finalize again. Unfinalizing a result that is not finalized answers 409 submission_not_finalized.
All results of an exam
GET /exams/{exam_id}/results (scope evaluation:read) returns full results for the exam’s live submissions, in the same shape as GET /submissions/{submission_id}/result, ordered by (updated_at, id).