Skip to main content
Guide

Create quizzes programmatically with the QuizMachine API

~4 min read · August 2026

The quiz creation API turns a quiz you can export from the dashboard into a workflow you can automate—from content creation through custom links and publishing—without maintaining a second data format.

Available to confirmed Trainer-or-higher accounts on Premium, Enterprise, and Enterprise+ plans. Every created quiz is owned by the authenticated user in that user's organization; ownership cannot be supplied in JSON.

From dashboard export to API request

Create quizzes programmatically with the same schema-version 3 JSON package produced by QuizMachine export.

  1. Export a representative quiz from the dashboard and save the JSON as your schema-version 3 starting point.
  2. POST /api/v1/auth/token
  3. POST /api/v1/quizzes
jq -n --arg email "$QUIZMACHINE_EMAIL" --arg credential "$QUIZMACHINE_CREDENTIAL" \
  '{email:$email,password:$credential,scopes:["quiz:read","quiz:write","topic:read","topic:write"]}' | \
curl -X POST https://dev-quizmachine.covecode.net/api/v1/auth/token -H "Content-Type: application/json" --data-binary @-
# With images
jq -n --arg email "$QUIZMACHINE_EMAIL" --arg credential "$QUIZMACHINE_CREDENTIAL" \
  '{email:$email,password:$credential,scopes:["quiz:read","quiz:write","topic:read","topic:write","media:write"]}' |
curl -X POST https://dev-quizmachine.covecode.net/api/v1/auth/token -H "Content-Type: application/json" --data-binary @-

# PowerShell variant with image access
$tokenBody = @{
  email = 'trainer@example.com'
  password = [pscredential]::new('user',(Read-Host 'Password' -AsSecureString)).GetNetworkCredential().Password
  scopes = @('quiz:read','quiz:write','topic:read','topic:write','media:write')
} | ConvertTo-Json
$mediaToken = (Invoke-RestMethod -Method Post -Uri 'https://dev-quizmachine.covecode.net/api/v1/auth/token' `
  -ContentType 'application/json' -Body $tokenBody).access_token

Keep the returned access token secret. API tokens expire after 60 minutes, are invalidated by password changes or email-confirmation revocation, and should only be sent over HTTPS.

curl -X POST https://dev-quizmachine.covecode.net/api/v1/quizzes \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  --data-binary @quiz-export.json

Read and update the quizzes you already have

A GET returns the same schema-version 3 package the create endpoint accepts, plus publication status, custom link, and share URL. Scripts can edit, publish, close, and share a quiz without opening the browser editor.

Lists include quizzes owned by members of your organization. Reading or updating a complete quiz definition requires that you own the quiz; RootAdmin recovery is the only exception. A quiz in another organization always answers 404.
  1. GET /api/v1/quizzes
  2. GET /api/v1/quizzes/{id}
  3. PUT /api/v1/quizzes/{id}
  4. PUT /api/v1/quizzes/{id}/slug
  5. PUT /api/v1/quizzes/{id}/status
curl https://dev-quizmachine.covecode.net/api/v1/quizzes?pageSize=25   -H "Authorization: Bearer <access_token>"

curl https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>   -H "Authorization: Bearer <access_token>" > quiz-export.json

curl -X PUT https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>   -H "Authorization: Bearer <access_token>"   -H "Content-Type: application/json"   --data-binary @quiz-export.json
curl -X PUT https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>/slug \
  -H "Authorization: Bearer <access_token>" -H "If-Match: "3"" \
  -H "Content-Type: application/json" -d '{"slug":"training-basics"}'

curl -X PUT https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>/status \
  -H "Authorization: Bearer <access_token>" -H "If-Match: "4"" \
  -H "Content-Type: application/json" -d '{"status":"Open"}'

An update replaces the quiz definition: settings, pages, canvas blocks, and questions. The quiz keeps its ID, owner, publication state, custom link, AI providers, paywall, folder, and collected responses.

PUT replaces the entire quiz definition and recreates its questions and options, so their IDs change. Supplied topicIds are validated and applied to the recreated questions; an omitted or empty assignment clears that question's Topics.

Update one question field

Every read returns an ETag header and, inside the package, stable ids for each question, option, and image. Granular edits, publishing or closing, and custom-link changes require an If-Match header naming the revision you last read; a stale value is refused with 412 Precondition Failed instead of silently overwriting a concurrent change.

PATCH a single question with a JSON Merge Patch body to change its body, bodyAllowMarkdown, points, isRequired, isDisabled, randomizeOptions, caseSensitiveMatch, matchingImmediateFeedback, immediateFeedback, immediateFeedbackMaxAttempts, allowNegativeScore, showFeedback, feedbackText, feedbackAllowMarkdown, aiEvaluationEnabled, modelAnswer, rubric, aiOnlyCodeEvaluation, codeStarterCode, codeReferenceSolution, codeTestCasesJson, mathWorkingAiFeedbackEnabled, mathExpectedWorking, mathWorkingRubric, childPictureChoiceResponseMode, childPictureChoiceMaxAttempts, tilesGridSize, tilesBlankTargetPosition, tilesShuffleDepth, tilesMaximumMoves, tilesConfigurationVersion, or tilesEngineVersion field without touching anything else — its id, options, images, topics, and page placement all stay exactly as they were.

curl -X PATCH https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>/questions/<question_id> \
  -H "Authorization: Bearer <access_token>" \
  -H "If-Match: \"3\"" \
  -H "Content-Type: application/merge-patch+json" \
  -d '{"aiEvaluationEnabled":true,"modelAnswer":"Expected answer","rubric":"Key concepts"}'

Add media=references to a GET request to omit embedded image bytes and receive only metadata and a mediaAssetId where one exists. Use media=embedded, the default, for the complete portable package.

For a complete create-then-patch example with an inline base64 feedback image, see Embed images directly in question feedback.

POST /api/v1/quizzes/{id}/questions

This endpoint adds one question without replacing existing questions, options, images, or Topics. pageNumber must identify an existing page unless newPage is true. Embedded media is rejected; upload it first, then attach the returned media id.

curl -X POST https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>/questions \
  -H "Authorization: Bearer <access_token>" -H "If-Match: \"4\"" -H "Content-Type: application/json" \
  -d '{"pageNumber":1,"order":2,"type":"FreeText","body":"Explain the result","points":1}'

POST · DELETE /api/v1/quizzes/{id}/pages

Create a page at position with type Standard or SectionBreak and an optional timeLimitSeconds. Set newPage on question creation to create and use a new page. Deleting a non-empty page requires deleteContents=true.

POST /api/v1/quizzes/<quiz_id>/pages
If-Match: "5"
{"position":2,"type":"SectionBreak","timeLimitSeconds":60}

POST /api/v1/quizzes/<quiz_id>/questions
If-Match: "6"
{"newPage":true,"type":"FreeText","body":"Explain the result","points":1}

DELETE /api/v1/quizzes/<quiz_id>/pages/2?deleteContents=true
If-Match: "7"

POST · PATCH · DELETE /api/v1/quizzes/{id}/text-media-blocks

Create, patch, move or reorder, and delete Text, Image, YouTube, Mermaid, and LatexArticle canvas blocks by stable id. Use If-Match on every write; Image mediaAssetId changes also need media:write. newPage inserts a Standard page. LaTeX preview returns PDF or Problem Details, and paid-block entitlement follows the quiz owner's effective plan. Batch types are createBlock, patchBlock, and deleteBlock, reorderPage.

POST /api/v1/quizzes/<quiz_id>/text-media-blocks
If-Match: "8"
{"pageNumber":1,"order":2,"blockType":"Text","textContent":"## Context","textAllowMarkdown":true,"isHeader":true,"alignment":"Left","layoutColumns":2}

PATCH /api/v1/quizzes/<quiz_id>/text-media-blocks/<block_id>
Content-Type: application/merge-patch+json
{"pageNumber":2,"order":1,"alignment":"Center","layoutColumns":3}

POST /api/v1/quizzes/<quiz_id>/text-media-blocks/latex-preview
{"textContent":"\\documentclass{article}..."}

POST /api/v1/quizzes/<quiz_id>/text-media-blocks/latex-preview?format=pdf
{"textContent":"\\documentclass{article}..."}

DELETE /api/v1/quizzes/<quiz_id>/text-media-blocks/<block_id>

Topics

List and search your organization's Topics with topic:read; creating or assigning Topics requires topic:write. Send topicIds when creating a package or question, or topicNames to reuse existing Topics case-insensitively and create missing ones. PUT on a question's topics endpoint still replaces its Topic set atomically.

curl https://dev-quizmachine.covecode.net/api/v1/topics?search=heap -H "Authorization: Bearer <access_token>"

curl -X POST https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>/questions \
  -H "Authorization: Bearer <access_token>" -H "If-Match: \"4\"" -H "Content-Type: application/json" \
  -d '{"pageNumber":1,"type":"FreeText","body":"What is minimax?","points":1,"topicIds":["<topic_id>"],"topicNames":["Game trees"]}'

curl -X PUT https://dev-quizmachine.covecode.net/api/v1/quizzes/<quiz_id>/questions/<question_id>/topics \
  -H "Authorization: Bearer <access_token>" -H "If-Match: \"4\"" -H "Content-Type: application/json" \
  -d '{"topicIds": ["<topic_id>"]}'

/operations · /validate

Validation is read-only. Mutating batches are atomic and require an Idempotency-Key, which clients should reuse only when retrying the same request body.

POST /api/v1/quizzes/<quiz_id>/validate
If-Match: "5"

POST /api/v1/quizzes/<quiz_id>/operations
If-Match: "5"
Idempotency-Key: <client-generated-value>

{"operations":[{"type":"createBlock","blockId":"<stable_id>","block":{"pageNumber":1,"blockType":"Mermaid","textContent":"graph TD; A-->B"}},{"type":"patchBlock","blockId":"<stable_id>","patch":{"order":3}},{"type":"deleteBlock","blockId":"<old_block_id>"}]}

POST /api/v1/media

Upload one PNG, JPEG, GIF, or WebP image, up to 5 MB, in the multipart/form-data field file. Send media:write and an Idempotency-Key; then attach the returned id to a supported image placement.

# Request media:write when minting the token for this call.
curl -X POST https://dev-quizmachine.covecode.net/api/v1/media \
  -H "Authorization: Bearer <access_token>" -H "Idempotency-Key: <client-generated-value>" \
  -F "file=@diagram.png"

POST /api/v1/quizzes/<quiz_id>/questions/<question_id>/images
PATCH /api/v1/quizzes/<quiz_id>/questions/<question_id>/images/<image_id>
PUT /api/v1/quizzes/<quiz_id>/questions/<question_id>/images/order
DELETE /api/v1/quizzes/<quiz_id>/questions/<question_id>/images/<image_id>
# Feedback images: attach the returned media id instead of base64 in feedbackText.
POST /api/v1/quizzes/{quizId}/questions/{questionId}/feedback-images
{"mediaAssetId":"<returned_media_id>","alignment":"Center","altText":"Worked solution"}
PATCH /api/v1/quizzes/{quizId}/questions/{questionId}/feedback-images/{imageId}
PUT /api/v1/quizzes/{quizId}/questions/{questionId}/feedback-images/order
DELETE /api/v1/quizzes/{quizId}/questions/{questionId}/feedback-images/{imageId}

# Option images; side is required for Matching questions.
PUT /api/v1/quizzes/{quizId}/questions/{questionId}/options/{optionId}/image
{"mediaAssetId":"<returned_media_id>","side":"Left"}
DELETE /api/v1/quizzes/{quizId}/questions/{questionId}/options/{optionId}/image

PUT /api/v1/quizzes/{quizId}/questions/{questionId}/diagram-reference-image
DELETE /api/v1/quizzes/{quizId}/questions/{questionId}/diagram-reference-image
PUT /api/v1/quizzes/{quizId}/text-media-blocks/{blockId}/image

Use the interactive API reference at /api/v1/docs, or consume the complete machine-readable contract at /api/v1/openapi.json.

/api/v1/docs
/api/v1/openapi.json
/api/v1/schema.json

Responses and limits

  • 200 OK returns the requested quiz package, or the updated quiz ID, title, and editor URL.
  • 201 Created returns the quiz ID, title, and editor URL.
  • 400 rejects malformed schemas, invalid configuration, base64, or image signatures.
  • 400 also rejects question body or feedback text over 65,536 characters and option text over 1,000 characters.
  • 401 means the bearer token is missing or invalid.
  • 403 means the account, plan, question type, or effective organization subscription is not eligible.
  • 404 means no quiz with that ID exists inside your organization.
  • 409 means that user already owns a quiz with the requested title.
  • 412 Precondition Failed means If-Match is stale. Every granular and batch write can return it; fetch the current quiz, reapply the change, and retry.
  • 429 means the edge rate limit or durable account creation quota has been reached. Wait before retrying.
  • All API errors use application/problem+json Problem Details with standard title, detail, status, and instance fields.

Question body and feedback text are limited to 65,536 characters; option text is limited to 1,000. Uploading media or changing question, feedback, option, diagram-reference, or block images requires media:write; without it, media upload returns 403. Prefer feedback-image attachments to base64 data URLs, which count toward the text limit. Attachments appear below feedback in Take, Preview, Embed, results, and result PDFs, and can be managed in the question editor.

Plan for production limits

Requests are limited to 32 MB, 20 creations per minute at the edge, and 200 creations per day in durable account state. A reached creation limit returns 429.

Embedded PNG, JPEG, GIF, and WebP images are verified by content, limited to 2 MB each, and charged to the effective account's storage quota. Exceeding storage returns 403.

Mermaid diagrams in questions and feedback
Read the complete Quiz Creation API help guide