dashboard_url links straight to it. API keys only see exams created through the API. Exams that teachers created in the dashboard are not visible to the API.
Lifecycle
finalized is worked out from the exam’s submissions; you don’t finalize an exam directly. POST /exams/{id}/finalize finalizes submissions, and the exam shows finalized only once all of its live submissions are final. Finalizing some of them leaves the exam open. A new submission, or unfinalizing one, makes the exam open again. Finalizing changes state only: it publishes nothing and sends nothing to students. See Finalize.
Create an exam
POST /exams (scope evaluation:write) creates the exam in draft. You can send its questions and candidates in the same call, or add them later.
201 response is the exam, plus a compact list of its questions with the ids you will need later:
quote is the price of the exam’s billing unit (a page for handwritten exams, an answer for typed ones). It is left out if the price could not be read in time. Creating the exam never fails because of it.
If you sent rubrics or model answers, rubric.version shows the rubric version once they are saved. If saving is delayed, rubric shows "sync": "pending" instead, and delivery is retried automatically. See Sync status.
Exam fields
string
required
Up to 255 characters. Cannot contain
< or >.string
required
handwritten (you upload scanned answer booklets as PDFs) or typed (you send each candidate’s answers as text). Can be changed only while the exam is a draft.string
Your own id for the exam, up to 128 characters. Unique per institute: a second exam with the same value returns
409 exam_exists with the existing exam’s id in details.exam_id. Deleting an exam frees its external_ref for reuse.string
default:"today"
The date the paper was sat, as
YYYY-MM-DD.string
Up to 120 characters, for example
Science or Financial Accounting. Sent to the grader as context.string
default:"school"
One of
school, ug, pg, upsc. Sent to the grader as context.string
Up to 64 characters, for example
CBSE.string
Up to 32 characters, for example
10.string
Examiner instructions for the whole paper, up to 4,000 characters. Sent to the grader as context.
string
default:"en"
Only
en is supported today. hi returns 422 language_not_supported.string
default:"en"
The language of the grader’s feedback. Only
en is supported today. hi returns 422 language_not_supported.boolean
default:"false"
When
true, candidate names are left out: candidates are registered under their external_id, and results return name: null. Can be changed only while the exam is a draft.boolean
default:"false"
When
true, the open checks run in the same call and the exam is created already open. If a check fails, nothing is created and you get 422 exam_not_ready.object[]
Up to 20 sections, each
{"name": "A", "order": 1}. Names are up to 64 characters, unique ignoring case, and cannot contain < or >. If you declare sections, every question must name one of them. If you don’t, sections are created from the questions’ section values in the order they first appear, or a single section A.object[]
Up to 2,000 candidates, created or updated and then registered on the exam. See Candidates.
object[]
Internal choice (“attempt any N of M”). Beta, enabled on request; see Internal choice.
Questions
Each question carries its printed label, its type, its marks and, depending on the type, an answer key, a model answer or a rubric.Question types
How each type is marked depends on the exam’s mode:
- Typed exams. Objective questions (
mcq_single,mcq_multi,true_false,numeric,one_word) are marked against your answer key. Onlylong_answerquestions go to the AI. - Handwritten exams. The AI reads the scanned booklet and marks every question, including MCQs.
Question fields
string
required
The number printed on the paper, up to 16 characters:
1, 3(a), Q11, 33-OR. Unique within the exam, ignoring case. Cannot contain < or >. Results come back keyed by both label and question id.string
The label of the parent question, for sub-parts. For example,
3 for 3(a). Stored and returned.string
The section name. If left out, the question goes to the first section.
string
required
One of the question types. It cannot be changed later: delete the question and add it again.
string
required
The question as printed, as plain text up to 20,000 characters.
number
required
Greater than 0, at most 1000, in steps of 0.5 (
1, 2.5, 15).number
default:"0"
Marks deducted for a wrong answer, 0 or more. Use it on objective questions in typed exams. Handwritten exams don’t apply negative marking: the value is set to 0 and the response carries a
negative_marks_ignored warning.object[]
For
mcq_single, mcq_multi and true_false only: 2 to 26 options, each {"label": "A", "text": "..."}. Labels are your own (A-D, 1-4, i-iv), up to 16 characters, unique, and cannot contain < or >. Option text is up to 2,000 characters.string[]
The labels of the correct options. Each must match one of
options, or the request fails with 422 unknown_option_label.number | string | array
For
numeric: a number, a numeric string, or a list of accepted numbers (for example [0.5, 0.50]). For one_word: a string. Not allowed on other types.string
For
long_answer only. The answer an examiner would accept, up to 20,000 characters. See Rubrics and model answers.object
For
long_answer only. Criteria with marks that add up to max_marks. See Rubrics and model answers.integer
Greater than 0. Stored and returned with the question.
boolean
Marks a question that asks for a diagram. Stored and returned with the question.
boolean
Marks a language-paper question. Stored and returned with the question.
object
Any JSON object up to 2 KB, such as
{"co": "CO2", "bloom": "apply", "topic": "Respiration"}. Stored and returned.string
Your own id for the question, up to 128 characters.
All text you send is plain text. Markup is stored and shown literally, never rendered. Short identifiers (
title, question and option labels, section names, one_word answers, criterion names, candidate names and roll numbers) cannot contain < or >. Longer text (question text, option text, instructions, model answers, guidance) can. A NUL character anywhere in a request body is refused with 422 validation_failed (field code invalid_characters).Examples by type
Numeric with accepted values (B.Com)
Numeric with accepted values (B.Com)
Multi-correct MCQ with negative marks (typed test-prep)
Multi-correct MCQ with negative marks (typed test-prep)
Long answer with a rubric (UPSC GS2)
Long answer with a rubric (UPSC GS2)
Manage questions
PATCH merges your fields onto the question and checks the result with the same rules as create. In a draft you can change any field except type and section; to change those, delete the question and add it again. Unknown fields are refused with 422 validation_failed (field code unknown_field), so a typo never looks like a successful edit.
Open an exam
POST /exams/{id}/open moves a draft to open, which means it can accept submissions. It runs these checks first:
- The exam has at least one question.
- Every question has
max_marks. - Every rubric’s criteria add up to its question’s
max_marks.
422 exam_not_ready with every problem listed:
no_questions, max_marks_missing and rubric_marks_mismatch. Opening an exam that is already open changes nothing and returns 200. Opening a finalized exam returns 409 exam_finalized.
What is frozen after open
Copies are marked against the scheme as it stood when the exam opened, so after open:
A
409 exam_open response lists the refused fields in details.fields. While the exam is finalized, every change to it is refused with 409 exam_finalized; new submissions are still accepted and reopen it.
Read, list and find exams
GET /exams/{id}returns the exam. Add?include=with any ofquestions,candidates(the first 200 registrations),choice_groups,statsandrubric.GET /examslists the institute’s API exams, including those created by its other keys. It takesstatus(draft,open,finalizedordeleted),updated_since,cursorandlimit(1 to 200, default 50).POST /exams/searchwith{"external_refs": ["ERP-EXAM-88213"]}(1 to 500 values) finds exams by your own ids. Deleted exams are left out. The lookup takes a request body rather than a query string, so your ids stay out of URLs and access logs.
include=stats returns counts for the exam:
Edit an exam
PATCH /exams/{id} takes any of the editable fields:
- Any time before finalize:
title,external_ref,conducted_on,subject,board,class,level,instructions,feedback_language. - Draft only:
sections,blind,mode. - Never:
answer_languageandstatus. These return422 validation_failedwith field codenot_editable.
conducted_on cannot be cleared. Sending sections replaces the list: sections are matched by name, and a section you leave out is removed, unless it still has questions (field code section_not_empty). Switching a draft to handwritten sets any negative marks to 0 and returns a negative_marks_ignored warning.
Delete an exam
DELETE /exams/{id} deletes a draft at any time. You can also delete an open exam, as long as it has no submissions; otherwise you get 409 exam_has_submissions. Deletion frees the exam’s external_ref. ?purge=true (which would also erase the files) is not available yet and returns 422 feature_not_available.
Warnings
A successful response can carrywarnings: [{"code", "message", "field"}] for input that was accepted but deserves a second look:
Internal choice (beta)
Many papers offer a choice: “attempt any 5 of 8” or “33 OR 33-OR”. Choice groups tell the grader which questions count, and setpaper_max to match:
attempt is at least 1 and fewer than the group’s questions, and policy is first (the first questions attempted, in answer order) or best (the highest-marked). paper_max is the marks of every question outside a group, plus the top attempt maxima of each group.