POST /v1/platform/tests
Creates a test request. The AI model generates a structured test with questions of mixed types (multiple choice, true/false, fill-in-the-blank, open-ended), creates the test in TutorFlow, and returns preview and public URLs.
Tiers
The tier field selects the quality and pricing contract for the request.
If omitted, TutorFlow defaults to standard:
| Tier | Price |
|---|---|
basic | $0.05 |
standard | $0.15 |
advanced | $0.25 |
TutorFlow stores a price snapshot at request time so billing remains consistent even if pricing is updated later.
Item Types
The AI generates a mix of the following item types based on the prompt:
| Type | Description | Auto-graded |
|---|---|---|
select | Multiple choice (one correct answer from options) | Yes |
true-false | True/false question | Yes |
blank | Fill-in-the-blank (exact-match grading) | Yes |
open-ended | Free-form written response | No (manual review) |
select items always include an options array. true-false, blank, and open-ended
items have options: null.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Prompt describing the test to generate |
title | string | No | Override AI-generated title |
description | string | No | Override AI-generated description |
language | string | No | Test language (default: en) |
level | string | No | Difficulty hint (e.g. easy, medium, hard) |
subject | string | No | Subject area (e.g. mathematics, physics, programming) |
itemCount | number | No | Target number of items for AI to generate |
timeLimit | number | No | Time limit in minutes (default: 60, min: 1) |
classroomId | string | No | Classroom ID to create the test in. Uses the default classroom if omitted |
tier | string | No | basic, standard, or advanced. Defaults to standard |
idempotencyKey | string | No | Prevents duplicate processing. Can also be sent via Idempotency-Key HTTP header |
mode | string | No | sync (default) or async |
Example Request
curl -X POST https://api.tutorflow.io/v1/platform/tests \
-H "Authorization: Bearer tf_platform_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a 10-question quiz on basic algebra covering linear equations and inequalities",
"language": "en",
"level": "medium",
"itemCount": 10,
"timeLimit": 30
}'Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Platform test request ID |
status | string | PENDING, PROCESSING, COMPLETED, or FAILED |
classroomTestId | string | null | Actual test ID in TutorFlow (null until generation completes) |
title | string | null | Test title |
description | string | null | Test description |
level | string | null | Difficulty level (easy, medium, or hard) |
timeLimit | number | null | Time limit in minutes |
totalScore | number | null | Sum of all item scores |
itemCount | number | null | Number of items in the generated test |
tier | string | Pricing tier used |
priceSnapshot | object | null | Pricing details captured at request time |
items | array | null | Item summaries with sequence, type, question, options, score |
shareToken | string | null | Token used to construct preview and public URLs |
previewUrl | string | null | Editor URL — /{locale}/platform/tests/{shareToken} |
publicUrl | string | null | Public test-taking URL — /{locale}/platform/tests/take/{shareToken} |
isTerminal | boolean | Whether the request has reached a final state |
pollAfterMs | number | null | Recommended polling interval for async requests |
idempotencyKey | string | null | Echoed idempotency key |
idempotentReplay | boolean | null | true if this response reused an existing request, false for a fresh request, null when no idempotencyKey was provided |
createdAt | string | ISO 8601 timestamp |
completedAt | string | null | ISO 8601 timestamp when generation completed |
Example Response
{
"id": "a1c2d3e4-f5g6-7h8i-9j0k-l1m2n3o4p5q6",
"status": "COMPLETED",
"classroomTestId": "c7d8e9f0-1a2b-3c4d-5e6f-7g8h9i0j1k2l",
"title": "Basic Algebra Quiz",
"description": "A 10-question quiz covering linear equations and inequalities.",
"level": "medium",
"timeLimit": 30,
"totalScore": 100,
"itemCount": 10,
"tier": "standard",
"priceSnapshot": {
"category": "test_creation",
"catalogKey": "test_creation.standard",
"tier": "standard",
"amountUsd": 0.15,
"unit": "test",
"currency": "USD",
"source": "platform_pricing_catalog_v1"
},
"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
},
{
"sequence": 3,
"type": "blank",
"question": "Solve for y: 3y = 21. y = ___",
"options": null,
"score": 10
},
{
"sequence": 4,
"type": "open-ended",
"question": "Explain the difference between an equation and an inequality.",
"options": null,
"score": 10
}
],
"shareToken": "b018172542f9a3c4d5e6f7890abcdef12345678",
"previewUrl": "https://tutorflow.io/en/platform/tests/b018172542f9a3c4d5e6f7890abcdef12345678",
"publicUrl": "https://tutorflow.io/en/platform/tests/take/b018172542f9a3c4d5e6f7890abcdef12345678",
"idempotencyKey": null,
"idempotentReplay": null,
"isTerminal": true,
"createdAt": "2026-03-24T10:32:15.108Z",
"completedAt": "2026-03-24T10:32:31.554Z"
}Preview URL vs Public URL
| URL | Purpose | Auth | Expiry |
|---|---|---|---|
previewUrl | Test editor (title, description, items, answers) | Public, no login | Resolves an edit token server-side; the underlying token expires after 1h and is auto-refreshed on access |
publicUrl | Public test-taking page for learners | Public, no login | None |
The previewUrl is a link to /{locale}/platform/tests/{shareToken}, which resolves the
latest edit token server-side and redirects into the editor. You do not need to refresh
tokens manually before sharing the preview link. You can also mint a new edit token
explicitly via POST /v1/platform/tests/:id/edit-token.
The publicUrl is the read-only link you share with learners. They land on the test
intro page and start a submission by submitting their email (and optional name).
See Submissions for the full taking flow.
Refreshing an Edit Token
curl -X POST https://api.tutorflow.io/v1/platform/tests/a1c2d3e4.../edit-token \
-H "Authorization: Bearer tf_platform_..."{
"editToken": "new-token-hex...",
"editTokenExpiresAt": "2026-03-24T13:30:00.000Z"
}Edit Token API
The edit URL provides access to these public endpoints (no API key needed):
| Method | Path | Description |
|---|---|---|
GET | /v1/platform/tests/edit/:editToken | Get test summary |
GET | /v1/platform/tests/edit/:editToken/full | Get full test with all items, correct answers, and explanations |
GET | /v1/platform/tests/edit/:editToken/items/:sequence | Get a single item (with answer) |
PATCH | /v1/platform/tests/edit/:editToken | Update test title, description, or time limit |
PATCH | /v1/platform/tests/edit/:editToken/items/:sequence | Update an item's question, options, correct answers, explanation, or score |
Update Test
curl -X PATCH https://api.tutorflow.io/v1/platform/tests/edit/{editToken} \
-H "Content-Type: application/json" \
-d '{ "title": "Algebra Mid-Term", "timeLimit": 45 }'| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Updated test title |
description | string | No | Updated test description |
timeLimit | number | No | Updated time limit in minutes |
Update Item
curl -X PATCH https://api.tutorflow.io/v1/platform/tests/edit/{editToken}/items/3 \
-H "Content-Type: application/json" \
-d '{
"question": "Solve for y: 3y = 21",
"correctAnswers": ["7"],
"score": 15
}'| Field | Type | Required | Description |
|---|---|---|---|
question | string | No | Updated question text |
options | array | No | Updated options (for select items) |
correctAnswers | array | No | Updated correct answer(s) |
explanation | string | No | Updated explanation shown after submission |
score | number | No | Updated point value for the item |
Async Mode
Set mode: "async" to queue test generation as a background job. The response
returns immediately with status: "PENDING" and a pollAfterMs value. Poll
GET /v1/platform/tests/:id until isTerminal is true.
Idempotency
Pass an idempotencyKey in the request body or Idempotency-Key header to prevent
duplicate test generation. Reusing the same key returns the original response
with idempotentReplay: true.