Skip to main content
This guide is for online test platforms: the student types answers in your test player, presses Submit, and sees marks and feedback shortly after. Objective questions are marked instantly; long answers are graded by the AI, usually in well under a minute. The example is a Class X Science chapter test with three objective questions and one long answer.
1

Create the test once

Create a typed exam with "open": true when the teacher publishes the test.
2

Submit on student submit

Post the student’s answers from your backend.
3

Wait for the result

Poll the submission until it is graded.
4

Show marks and feedback

Read the question-wise result and render it in your player.
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).

1. Create the test

Create one exam per test, with "mode": "typed" and "open": true so it accepts submissions straight away. Use your test ID as external_ref.
Question types for typed tests: negative_marks applies to objective questions on typed tests. Marks are in steps of 0.5, up to 1,000 per question, and an exam has at most 200 questions. A long answer with neither a rubric nor a model answer gets an auto_rubric warning; send at least a model answer.
Once a test is open, you cannot add or remove questions. You can still correct a question’s text, model_answer and rubric. To change the paper itself, create a new exam (for example lms-test-90311-v2).

2. Submit on student submit

When the student presses Submit, your backend posts their answers. Refer to questions by question_label (the label you sent) or question_id. Each answer uses the field that matches the question type:
The response is 202 Accepted with the submission’s status and a fixed-price quote. A student is created the first time you send their external_id, so you don’t need a separate sign-up call. Things to know about answers:
  • Blank answers are “not answered”. An empty text or empty option_labels scores 0, never reaches the AI and is never billed. You can leave unanswered questions out.
  • Only English. A long or one-word answer in which more than 20% of the letters are Devanagari is refused with 422 language_not_supported (details.question_label says which one). A few Hindi words in an English answer are fine.
  • Mistakes are refused together. A wrong field for the question type, an unknown label or a duplicate answer returns 422 validation_failed with every problem in details.errors[]. An option label the question doesn’t have returns 422 unknown_option_label.
  • One live submission per student per test. A second submission returns 409 submission_exists. For a retake, send "replace": true: the old submission becomes replaced and the new one is graded and charged. If you keep every attempt, create one exam per attempt instead.

What a submission costs

Contract prices per institute are possible; the quote then shows "rate_source": "contract". See Pricing and credits.

3. Wait for the result

Typed submissions run in their own lane, separate from scanned copies, and a short test usually comes back in well under a minute. Times are not guaranteed and grow at peak hours. queue.estimated_ready_at is a deliberately cautious estimate (never less than 30 seconds away), so poll instead of sleeping until it. Poll GET /submissions/{id} from your backend every 2 to 5 seconds, backing off to 10 seconds, until status is final:
Polling one submission is right for a student waiting on screen. For everything else, such as students who closed the tab, run one sync worker over GET /submissions?updated_since= instead of a poll per student.
Every poll counts against your rate limits. On the standard tier a key may make 600 reads per minute, and an institute 40 reads per second across all its keys. Per-student polling suits a handful of students waiting at the same time. When a whole class submits at once, for example at the end of a timed test, have one sync worker read the feed and push updates to your players, or ask hello@evalezy.com for the high tier.

4. Show marks and feedback

Fetch the question-wise result. Add include=model_answer to show the model answer next to the student’s.
Response (abridged)
What to render for each question:
number
Marks for the question. Objective questions have source: "auto". AI-graded questions have source: "ai", or "ai_reviewed" once a teacher overrode the marks. Approval leaves source as "ai" and sets review.approved to true.
string
Written feedback for the student, in plain text. Render it as text, not HTML.
array
The per-criterion breakdown from your rubric: name, awarded, max and a short reason. Shows the student exactly where marks were lost.
string
The text the grader worked from. For typed answers this is the text you sent, but spacing and line breaks may differ; angle brackets and markup are kept as plain text. Show the student’s original answer from your own records if you need it verbatim.
string
Only with include=model_answer.
boolean
true when the AI could not grade the answer or had low confidence. Consider showing such marks as provisional until a teacher checks them.

Drafts and final marks

AI marks are drafts until you finalize them. For a practice test, showing draft feedback straight away is usually fine. For a test that counts towards a grade, label marks as provisional, let teachers review on the dashboard (the test appears there tagged Source: API) or through PATCH /submissions/{id}/questions/{question_id}, then finalize:
After finalizing, overrides and re-grading are refused with 409 submission_finalized until you unfinalize the submission with a reason.

Next steps

Syncing results

Catch every result, including the ones nobody waited for.

Going live

Retries, error handling and credit alerts.