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.
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).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.
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 byquestion_label (the label you sent) or question_id. Each answer uses the field that matches the question type:
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
textor emptyoption_labelsscores 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_labelsays 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_failedwith every problem indetails.errors[]. An option label the question doesn’t have returns422 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 becomesreplacedand 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:
4. Show marks and feedback
Fetch the question-wise result. Addinclude=model_answer to show the model answer next to the student’s.
Response (abridged)
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 throughPATCH /submissions/{id}/questions/{question_id}, then finalize:
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.