The submission flow lets learners take a test via the public shareToken without
needing an API key. Submissions are uniquely identified per (test, email) pair, so
returning to the test with the same email resumes any in-progress submission.
Flow Overview
- Load the test for taking —
GET /v1/platform/tests/public/:shareToken/take - Start a submission —
POST /v1/platform/tests/public/:shareToken/submissions - Save answers (optionally repeatedly) —
PATCH /v1/platform/tests/submissions/:submissionToken - Submit final answers — same
PATCHendpoint withisDone: true - Retrieve the graded result —
GET /v1/platform/tests/submissions/:submissionToken/result - (Workspace) List all submissions —
GET /v1/platform/tests/:id/submissions
GET /v1/platform/tests/public/:shareToken/take
Loads the test in taking mode. Item answers and explanations are not included.
Example Response
{
"title": "Basic Algebra Quiz",
"description": "A 10-question quiz covering linear equations and inequalities.",
"level": "medium",
"timeLimit": 30,
"itemCount": 10,
"totalScore": 100,
"items": [
{
"sequence": 1,
"type": "select",
"question": "What is the solution to 2x + 3 = 11?",
"options": ["x = 3", "x = 4", "x = 5", "x = 6"],
"score": 10
},
{
"sequence": 2,
"type": "true-false",
"question": "The inequality x > 5 includes the value 5.",
"options": null,
"score": 10
}
]
}POST /v1/platform/tests/public/:shareToken/submissions
Starts a new submission, or resumes an existing in-progress submission for the same email address.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Learner email — used to identify and resume submissions |
name | string | No | Optional learner display name (≤ 200 chars) |
Example Request
curl -X POST https://api.tutorflow.io/v1/platform/tests/public/{shareToken}/submissions \
-H "Content-Type: application/json" \
-d '{ "email": "alice@example.com", "name": "Alice" }'Response
| Field | Type | Description |
|---|---|---|
submissionId | string | Internal submission ID |
submissionToken | string | Token used to submit answers and fetch the result |
startedAt | string | ISO 8601 timestamp when the submission was first started |
resumed | boolean | true if an existing in-progress submission was resumed |
savedAnswers | array | null | Previously saved answers (only present when resumed: true) |
test | object | Same shape as GET /public/:shareToken/take |
Example Response
{
"submissionId": "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c",
"submissionToken": "sub_b3f1a2c4d5e6f7890abcdef1234567890",
"startedAt": "2026-03-24T11:00:00.000Z",
"resumed": false,
"test": {
"title": "Basic Algebra Quiz",
"description": "A 10-question quiz...",
"level": "medium",
"timeLimit": 30,
"itemCount": 10,
"totalScore": 100,
"items": [
{ "sequence": 1, "type": "select", "question": "...", "options": ["..."], "score": 10 }
]
}
}PATCH /v1/platform/tests/submissions/:submissionToken
Saves answers for an in-progress submission. Call multiple times to save progress
incrementally; call once with isDone: true to finalize and trigger grading.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
items | array | Yes | Array of { sequence, answers } objects |
items[].sequence | number | Yes | Item sequence number |
items[].answers | string[] | Yes | Learner's answer(s) for the item |
isDone | boolean | No | Set to true to finalize the submission. Defaults to false |
Example Request
curl -X PATCH https://api.tutorflow.io/v1/platform/tests/submissions/{submissionToken} \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "sequence": 1, "answers": ["x = 4"] },
{ "sequence": 2, "answers": ["false"] },
{ "sequence": 3, "answers": ["7"] },
{ "sequence": 4, "answers": ["An equation states two expressions are equal..."] }
],
"isDone": true
}'Auto-Grading
When isDone: true, items are graded as follows:
| Item Type | Grading |
|---|---|
select | Auto-graded — exact match against correctAnswers |
true-false | Auto-graded — exact match against correctAnswers |
blank | Auto-graded — exact match against correctAnswers |
open-ended | Marked PENDING — requires manual review |
Tests containing open-ended items will return a totalScore reflecting only the
auto-graded portion until the open-ended items are reviewed.
Response
When isDone: true, the response includes the enriched item list with correctAnswers
and explanation for each item, plus per-item grading results.
{
"submissionId": "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c",
"isDone": true,
"finishedAt": "2026-03-24T11:25:42.000Z",
"totalScore": 30,
"maxScore": 40,
"items": [
{
"sequence": 1,
"type": "select",
"question": "What is the solution to 2x + 3 = 11?",
"options": ["x = 3", "x = 4", "x = 5", "x = 6"],
"correctAnswers": ["x = 4"],
"explanation": "Subtract 3 from both sides, then divide by 2.",
"score": 10,
"submittedAnswers": ["x = 4"],
"isCorrect": true,
"earnedScore": 10,
"status": "GRADED"
},
{
"sequence": 4,
"type": "open-ended",
"question": "Explain the difference between an equation and an inequality.",
"options": null,
"correctAnswers": null,
"explanation": "An equation uses =, an inequality uses <, >, ≤, or ≥.",
"score": 10,
"submittedAnswers": ["An equation states two expressions are equal..."],
"isCorrect": null,
"earnedScore": null,
"status": "PENDING"
}
]
}When isDone: false, the response simply confirms the save:
{ "submissionId": "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c", "isDone": false }GET /v1/platform/tests/submissions/:submissionToken/result
Fetches the result of a finalized submission. Returns the same enriched item list
as the finalize call. Returns a 4xx error if the submission is not yet isDone.
Example Request
curl https://api.tutorflow.io/v1/platform/tests/submissions/{submissionToken}/resultGET /v1/platform/tests/:id/submissions
Lists all submissions for a given test request. Requires Platform API key auth.
Example Request
curl https://api.tutorflow.io/v1/platform/tests/a1c2d3e4-.../submissions \
-H "Authorization: Bearer tf_platform_..."Response
| Field | Type | Description |
|---|---|---|
items[].submissionId | string | Submission ID |
items[].submissionToken | string | Submission token (for fetching result) |
items[].email | string | Learner email |
items[].name | string | null | Learner display name |
items[].isDone | boolean | Whether the submission has been finalized |
items[].startedAt | string | ISO 8601 start timestamp |
items[].finishedAt | string | null | ISO 8601 finalize timestamp |
items[].totalScore | number | null | Earned score (auto-graded portion) |
items[].maxScore | number | null | Maximum possible score |
Example Response
{
"items": [
{
"submissionId": "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c",
"submissionToken": "sub_b3f1a2c4d5e6f7890abcdef1234567890",
"email": "alice@example.com",
"name": "Alice",
"isDone": true,
"startedAt": "2026-03-24T11:00:00.000Z",
"finishedAt": "2026-03-24T11:25:42.000Z",
"totalScore": 30,
"maxScore": 40
}
]
}Public Read-Only Endpoints
These additional endpoints serve the test editor preview (previewUrl):
| Method | Path | Description |
|---|---|---|
GET | /v1/platform/tests/public/:shareToken | Get test summary (no items) |
GET | /v1/platform/tests/public/:shareToken/full | Get full test with items, correct answers, and explanations |
GET | /v1/platform/tests/public/:shareToken/items/:sequence | Get a single item with answer |
/full and /items/:sequence automatically refresh the underlying edit token if it
has expired, so editor previews always work without manual token refresh.