Create Test

Generate an AI-powered test (quiz/assessment) from a prompt and receive a shareable test link.

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.

Pricing

The Test API is billed per generated item (per question). One unit on the catalog = one item.

UnitPrice
Per item$0.015

A test request generates itemCount items (default 10, hard cap 30). The billable amount on the request equals unitPrice × actual generated items, not the requested itemCount. The AI may produce slightly fewer, and billing follows what was actually delivered.

Legacy tier field: The tier field is still accepted for backwards compatibility but has no effect on price. Omit it in new integrations. If you send it, use one of the legacy request values: basic, standard, or advanced. The default tier appears only in response priceSnapshot and the pricing catalog.

The priceSnapshot returned with the test response carries the full billing record:

{
  "category": "test_creation",
  "tier": "default",
  "unit": "item",
  "unitPrice": 0.015,
  "units": 10,
  "amountUsd": 0.15,
  "currency": "USD",
  "source": "platform_pricing_catalog_v2"
}

Item Types

The AI generates a mix of the following item types based on the prompt:

TypeDescriptionAuto-graded
selectMultiple choice (one correct answer from options)Yes
true-falseTrue/false questionYes
blankFill-in-the-blank (exact-match grading)Yes
open-endedFree-form written responseNo (manual review)

select items always include an options array. true-false, blank, and open-ended items have options: null.

Request Body

FieldTypeRequiredDescription
promptstringYesPrompt describing the test to generate
titlestringNoOverride AI-generated title
descriptionstringNoOverride AI-generated description
languagestringNoTest language (default: en)
levelstringNoDifficulty hint (e.g. easy, medium, hard)
subjectstringNoSubject area (e.g. mathematics, physics, programming)
itemCountnumberNoTarget number of items for AI to generate (1–30, default: 10)
timeLimitnumberNoTime limit in minutes (default: 60, min: 1)
classroomIdstringNoClassroom ID to create the test in. Uses the default classroom if omitted
tierstringNoLegacy request value only: basic, standard, or advanced. Omit for new integrations. Do not send default; default is a response/catalog tier.
idempotencyKeystringNoPrevents duplicate processing. Can also be sent via Idempotency-Key HTTP header
modestringNosync (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

FieldTypeDescription
idstringPlatform test request ID
statusstringPENDING, PROCESSING, COMPLETED, or FAILED
classroomTestIdstring | nullActual test ID in TutorFlow (null until generation completes)
titlestring | nullTest title
descriptionstring | nullTest description
levelstring | nullDifficulty level (easy, medium, or hard)
timeLimitnumber | nullTime limit in minutes
totalScorenumber | nullSum of all item scores
itemCountnumber | nullNumber of items in the generated test
tierstringPricing tier used
priceSnapshotobject | nullPricing details captured at request time
itemsarray | nullItem summaries with sequence, title, type, question, options, score
shareTokenstring | nullToken used to construct preview and public URLs
previewUrlstring | nullEditor URL, /{locale}/platform/tests/{shareToken}
publicUrlstring | nullPublic test-taking URL, /{locale}/platform/tests/take/{shareToken}
isTerminalbooleanWhether the request has reached a final state
pollAfterMsnumber | nullRecommended polling interval for async requests
idempotencyKeystring | nullEchoed idempotency key
idempotentReplayboolean | nulltrue if this response reused an existing request, false for a fresh request, null when no idempotencyKey was provided
createdAtstringISO 8601 timestamp
completedAtstring | nullISO 8601 timestamp when generation completed

Example Response

{
  "id": "a1c2d3e4-f5a6-4789-8b0c-d1e2f3a4b5c6",
  "status": "COMPLETED",
  "classroomTestId": "c7d8e9f0-1a2b-43c4-9e6f-7a8b9c0d1e2f",
  "title": "Basic Algebra Quiz",
  "description": "A 10-question quiz covering linear equations and inequalities.",
  "level": "medium",
  "timeLimit": 30,
  "totalScore": 100,
  "itemCount": 10,
  "tier": "default",
  "priceSnapshot": {
    "category": "test_creation",
    "catalogKey": "test_creation.default",
    "tier": "default",
    "unit": "item",
    "unitPrice": 0.015,
    "units": 10,
    "amountUsd": 0.15,
    "currency": "USD",
    "source": "platform_pricing_catalog_v2"
  },
  "items": [
    {
      "sequence": 1,
      "title": "Solve 2x + 3 = 11",
      "type": "select",
      "question": "What is the solution to 2x + 3 = 11?",
      "options": ["x = 3", "x = 4", "x = 5", "x = 6"],
      "score": 10
    },
    {
      "sequence": 2,
      "title": "Inequality boundary",
      "type": "true-false",
      "question": "The inequality x > 5 includes the value 5.",
      "options": null,
      "score": 10
    },
    {
      "sequence": 3,
      "title": "Solve for y",
      "type": "blank",
      "question": "Solve for y: 3y = 21. y = ___",
      "options": null,
      "score": 10
    },
    {
      "sequence": 4,
      "title": "Equation vs inequality",
      "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 vs Edit URL

The response includes two URL fields plus an implicit edit URL pattern:

URLPurposeAuthNotes
publicUrlPublic test-taking page for learnersPublic, no loginLives at /{locale}/platform/tests/take/{shareToken}, the link you share with test-takers
previewUrlReserved for a future server-rendered editor previewPublic, no loginCurrently returned in API responses but not all deployments expose a frontend page at this URL. For reliable editor access, prefer minting an edit token (below).
Editor URLAuthoring editor (title, description, items, answers)Public, no loginBuild from editToken: /{locale}/platform/tests/edit/{editToken}

To get an editor URL, mint a fresh edit token via POST /v1/platform/tests/:id/edit-token. Edit tokens expire after 1 hour; minting a new one is cheap and idempotent.

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-5678-4abc-9def-0123456789ab/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). Two addressing modes coexist for backwards compatibility:

  • By UUID (/items/by-id/:itemId), preferred for editor surfaces. Stable even if items are reordered.
  • By sequence (/items/:sequence), legacy / read paths. Re-numbers when items are inserted, deleted, or reordered.
MethodPathDescription
GET/v1/platform/tests/edit/:editTokenGet test summary
GET/v1/platform/tests/edit/:editToken/fullGet full test with all items, correct answers, and explanations
GET/v1/platform/tests/edit/:editToken/items/:sequenceGet a single item (with answer)
PATCH/v1/platform/tests/edit/:editTokenUpdate test title or description
PATCH/v1/platform/tests/edit/:editToken/fullBulk update, accepts the full items[] array; performs upsert + delete
POST/v1/platform/tests/edit/:editToken/itemsCreate a single item
PATCH/v1/platform/tests/edit/:editToken/items/by-id/:itemIdUpdate one item by UUID
DELETE/v1/platform/tests/edit/:editToken/items/by-id/:itemIdDelete one item by UUID
PATCH/v1/platform/tests/edit/:editToken/items/reorderReorder items, accepts [{ id, sequence }]
POST/v1/platform/tests/edit/:editToken/items/generateGenerate additional items via AI (synchronous)
PATCH/v1/platform/tests/edit/:editToken/items/:sequenceUpdate one item by sequence (legacy path)

Update Test

curl -X PATCH https://api.tutorflow.io/v1/platform/tests/edit/{editToken} \
  -H "Content-Type: application/json" \
  -d '{ "title": "Algebra Mid-Term", "description": "Updated copy" }'
FieldTypeRequiredDescription
titlestringNoUpdated test title
descriptionstringNoUpdated test description

Use PATCH /v1/platform/tests/edit/:editToken/full when you also need to change level, timeLimit, or the items array. The title-only endpoint above does not accept those fields.

Bulk Update (full)

Used by the editor's bulk-save / drag-reorder paths. Accepts the same payload shape the classroom admin PATCH accepts. The server diffs items[] against the persisted set: items with an existing id are updated, items without id are created, and any persisted item not present in the payload is deleted.

curl -X PATCH https://api.tutorflow.io/v1/platform/tests/edit/{editToken}/full \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Algebra Mid-Term",
    "description": "Updated copy",
    "level": "medium",
    "timeLimit": 45,
    "items": [
      { "id": "...", "sequence": 1, "type": "select", "title": "...", "question": "...", "options": ["..."], "correctAnswers": ["..."], "explanation": "...", "score": 10 }
    ]
  }'

Returns { "success": true } on completion.

Create Item

curl -X POST https://api.tutorflow.io/v1/platform/tests/edit/{editToken}/items \
  -H "Content-Type: application/json" \
  -d '{
    "type": "select",
    "title": "Solve 2x + 3 = 11",
    "question": "What is x?",
    "options": ["3", "4", "5", "6"],
    "correctAnswers": ["4"],
    "explanation": "Subtract 3, divide by 2",
    "score": 10
  }'

Returns the created item including its assigned id and sequence (appended to the end of the items list).

Update Item (by UUID)

Preferred for editor surfaces, survives reorders. Returns { "success": true }.

curl -X PATCH https://api.tutorflow.io/v1/platform/tests/edit/{editToken}/items/by-id/{itemId} \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Solve for y",
    "question": "Solve for y: 3y = 21",
    "correctAnswers": ["7"],
    "score": 15
  }'
FieldTypeRequiredDescription
titlestringNoUpdated short item label
questionstringNoUpdated question text
optionsarrayNoUpdated options (for select items)
correctAnswersarrayNoUpdated correct answer(s)
explanationstringNoUpdated explanation shown after submission
scorenumberNoUpdated point value for the item

Update Item (by sequence)

Same body as the by-UUID variant. Use for legacy integrations only, sequence numbers shift when other items are inserted, deleted, or reordered.

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 }'

Delete Item (by UUID)

curl -X DELETE https://api.tutorflow.io/v1/platform/tests/edit/{editToken}/items/by-id/{itemId}

Returns { "success": true }. Remaining items are NOT renumbered server-side; if the caller needs contiguous sequences, follow up with /items/reorder.

Reorder Items

curl -X PATCH https://api.tutorflow.io/v1/platform/tests/edit/{editToken}/items/reorder \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "id": "abc...", "sequence": 1 },
      { "id": "def...", "sequence": 2 },
      { "id": "ghi...", "sequence": 3 }
    ]
  }'

Persists new sequence values atomically. Returns { "success": true }.

Generate Items (AI)

Synchronous (non-streaming) item generation. The new items are persisted to the test before the response returns; the response contains the final array.

curl -X POST https://api.tutorflow.io/v1/platform/tests/edit/{editToken}/items/generate \
  -H "Content-Type: application/json" \
  -d '{
    "itemCount": 5,
    "itemTypes": ["select", "select", "true-false", "blank", "open-ended"],
    "language": "en"
  }'
FieldTypeRequiredDescription
itemCountnumberYesNumber of new items to generate
itemTypesstring[]YesPer-slot type list. Length should equal itemCount
languagestringNoGeneration language (default: test language)

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.