Skip to content

Submissions

A submission is one student’s answer script for one paper. We read the handwriting, mark every question against the marking scheme and write feedback on each answer.

queued ──▶ processing ──▶ graded ──▶ released
│ │
├────────────┴──────▶ failed
└──(cancel)─────────▶ canceled
status Meaning
queued Accepted. We’re fetching and checking the files and placing the credit hold.
processing We’re reading and marking the script.
graded Every question is marked and the credits are charged. On an automatic paper the submission moves straight on to released, unless it has no readable answers. On an after_review paper it waits here for a reviewer.
released The result is final, and the result and report are available.
failed Not marked, and nothing charged. failure.code says why.
canceled You cancelled it before marking started. Nothing charged.

A script counts as marked only when every question has a mark. If we can’t mark every question, even after our automatic retries, the submission fails with grading_failed. You never get a partial score.

Each student can have one active submission for each paper: one that is queued, processing, graded or released. A second is refused with 409 submission_exists, and resource_id holds the existing submission’s ID. So resending a whole class after a network failure can’t mark or charge anyone twice.

  • After a submission failed or was canceled, you can create a new one for that student.
  • To replace a script that was marked from the wrong file, or for a resit of the same paper, delete the earlier submission first. The new one is marked and charged as new work.

POST /v1/submissions. Requires an Idempotency-Key.

Parameter Required Description
paper_id Yes A paper that is ready.
student.external_id Yes Your stable ID for the student, up to 128 characters.
student.name No Printed on the report, up to 100 characters. Without it, the report shows student.external_id.
files Yes The script: one PDF, or 1 to 50 images in page order. Each item is { "file_id": "file_…" } or { "url": "https://…" }. A mix of PDFs and images, or more than one PDF, gets 400 parameter_invalid; when that only shows once links are downloaded, the submission fails with invalid_file_combination.
metadata No See Your own IDs and metadata.
test_outcome No Test mode only. See Simulating outcomes.
{
"paper_id": "pap_3fT9qLx2Wd",
"student": { "external_id": "STU-0042", "name": "Ayesha K." },
"files": [ { "file_id": "file_As2Vb6Nc8T" } ],
"metadata": { "class": "10B" }
}

Response 201 returns the Submission with status: "queued".

{
"id": "sub_7Hk2Pz9QaN",
"object": "submission",
"livemode": true,
"paper_id": "pap_3fT9qLx2Wd",
"student": { "external_id": "STU-0042", "name": "Ayesha K." },
"status": "released",
"failure": null,
"no_readable_answers": false,
"files": [ { "file_id": "file_As2Vb6Nc8T", "pages": 12 } ],
"billing": { "status": "charged", "pages": 12, "credits": 12 },
"score": { "awarded": 68, "available": 90, "percentage": 75.6 },
"graded_at": "2026-10-06T09:21:40Z",
"released_at": "2026-10-06T09:21:40Z",
"metadata": { "class": "10B" },
"created_at": "2026-10-06T09:15:30Z",
"updated_at": "2026-10-06T09:21:40Z"
}
Field Type Description
status string See Submission statuses.
failure object { "code", "message" } when status is failed, otherwise null.
no_readable_answers boolean true when we found no answers in the script. See below.
files array The script’s files in page order, with their page counts. For a file sent as a link, file_id and pages are null until we’ve downloaded it.
billing object See The billing object.
score object The total once graded, otherwise null. Provisional until released, because a reviewer may still change marks. Questions that the paper’s choice rules leave out are not counted.
graded_at, released_at timestamp When the script was marked, and when its result became final.

Sometimes a script is readable but contains no answers: blank pages, the wrong document or another student’s paper. We still read and mark it, so it is charged, but every question gets 0 and its feedback says why.

Because a zero sent to a student by mistake is hard to undo, such a submission is never released automatically. It stays graded with no_readable_answers: true, even on an automatic paper, and submission.graded tells you about it. Check the files, then either:

  • release it, if the student really did hand in a blank script; or
  • delete it and submit the right file.
Endpoint Description
GET /v1/submissions/{id} Retrieve a submission.
GET /v1/submissions List submissions. Filter by paper_id, student_external_id or status, plus the list parameters.

PATCH /v1/submissions/{id} changes student.name or metadata, and returns the submission. A new name appears in the report the next time it is requested.

POST /v1/submissions/{id}/cancel stops a submission that is still queued. Its hold is released, its files are released for reuse, and it becomes canceled. It returns the submission. Cancelling a submission that is already canceled returns it unchanged; any other status gets 409 submission_not_cancelable.

A graded result waits for a reviewer when the paper was created with "results_release": "after_review", or when the script has no readable answers. A reviewer can release it:

  • in the review workspace, opened with the paper’s review link, after adjusting marks and feedback if needed; or
  • through the API: POST /v1/submissions/{id}/release moves a graded submission to released, sends submission.released and returns the submission.

Releasing a submission that is already released returns it unchanged. Any other status gets 409 submission_not_releasable. Once released, a result doesn’t change.

DELETE /v1/submissions/{id} permanently deletes the submission with its files, result and report, and returns 204. A submission that is queued or processing can’t be deleted (409 submission_in_progress); cancel it first if it is still queued. Credits already charged are not returned.