Resources
Generate Tests, Modules, Courses, and Slides

Generate Tests, Modules, Courses, and Slides

Ask TutorFlow to write a test, a module, a whole course, or a slide deck from a topic, follow the run until it finishes, and read what it made and what it charged.

On this page

Four routes write content for you from a topic, the way the TutorFlow editor does: a test with its questions, one module, a course with its chapters and lessons, or a slide deck with its pages and presenter notes. Each request starts a run that keeps going on TutorFlow's servers after the 202. You follow it with a webhook or by polling the run.

Games and simulations are generated differently, in brief, build, and revise steps; see Generation: Brief, Build, Revise.

Every path below is under /v1/content/classrooms/{classroomId}. The generate routes need content:generate and spend AI Credits; the run routes need content:read.

MethodPathScopeSuccess
POST/tests/generatecontent:generate202 with the run
POST/modules/generatecontent:generate202 with the run
POST/courses/generatecontent:generate202 with the run
POST/slides/generatecontent:generate202 with the run
GET/runscontent:read200 with a page of runs
GET/runs/{runId}content:read200 with one run

Start a run

bash
curl -i -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/courses/generate" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: course-generate:customer-onboarding:v1" \
  -d '{
    "topic": "Onboarding for new customer success managers: our product, the first call, and escalations",
    "level": "beginner",
    "lessonCount": 6,
    "language": "en"
  }'
HTTP
HTTP/1.1 202 Accepted
Location: /v1/content/classrooms/00000000-0000-4000-8000-000000000010/runs/00000000-0000-4000-8000-000000000b01
JSON
{
  "runId": "00000000-0000-4000-8000-000000000b01",
  "status": "queued",
  "kind": "course",
  "estimatedCredits": 21,
  "statusUrl": "/v1/content/classrooms/00000000-0000-4000-8000-000000000010/runs/00000000-0000-4000-8000-000000000b01"
}

The generate routes always answer 202; they never stream. The Location header repeats statusUrl. Hanging up after the 202 has no effect: the run keeps going. A body that fails validation answers 422 with error.details before a run exists, and unknown fields are dropped.

Request bodies

Test

POST /tests/generate takes the options of the TutorFlow test editor, with the same defaults.

FieldRequiredNotes
topicYesWhat the test covers. Up to 4,000 characters.
instructionsNoWho takes the test and what to stress. Up to 4,000 characters.
levelNoEASY, MEDIUM, or HARD. Default MEDIUM.
itemCountNo1 to 30 questions. Default 20.
itemTypesNoOne type per question, in order: select, blank, open-ended, true-false, or submission. Its length must equal itemCount, otherwise 422. Without it, the editor's mix is used: 3 true or false, then multiple choice, 3 fill in the blank, and 2 open-ended last.
totalScoreNoAn integer of at least 1. Default 100.
passingValueTypeNoSCORE or PERCENTAGE. Default SCORE.
passingValueNo0 or more. Default 80.
languageNoSee Output language.

Module

POST /modules/generate writes one module.

FieldRequiredNotes
topicYesWhat the module teaches. Up to 4,000 characters.
typeNomarkdown, ai-tutor, chat-ai, coding-lesson, coding-test, notebook, web, exam, flashcard, dictation, reading-comprehension, translation, shadowing, or roleplay. Without it, the type is chosen from the topic, as in the editor.
referenceTextNoPlain text or Markdown, up to 100,000 characters, used the way an attached file is used in the editor. File uploads are not accepted on this route.
languageNoSee Output language.

Course

POST /courses/generate plans an outline, creates the course, then writes each lesson.

FieldRequiredNotes
topicYesWhat the course is about and who it is for. Up to 4,000 characters. prompt is accepted as a deprecated name for the same field, read only when topic is absent.
title, descriptionNoUsed as given, up to 255 and 4,000 characters. Otherwise the outline writes them.
levelNobeginner, intermediate, or advanced.
lessonCountNo1 to 30. Without it, the outline plans 12 lessons in 3 chapters of 4.
languageNoSee Output language.

Slides

POST /slides/generate writes an outline, creates the deck, then writes its pages four at a time, with a presenter script on every page. It takes the slide editor's options, except attached files.

FieldRequiredNotes
topicYesWhat the deck teaches. Up to 4,000 characters.
instructionsNoWho the deck is for and what to stress. Up to 4,000 characters. It shapes the outline and is kept on the deck as its context (metadata.target, where the editor keeps it).
slideCountNo5 to 20 pages, counting the title, table of contents, and summary pages. Default 10.
textLengthNoconcise or detailed: how much text each page carries. Default detailed.
themeIdNodefault, breeze, sunset, forest, rose, alien, aurora, velvet-tides, or midnight. Default default.
languageNoSee Output language.
bash
curl -sS -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/slides/generate" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: slides-generate:safety-week:v1" \
  -d '{ "topic": "Workplace safety for new warehouse staff", "slideCount": 10, "textLength": "concise", "themeId": "forest", "language": "en" }'

The answer is 202 with "kind": "slide" and "estimatedCredits": 16. How the deck is made:

  • The outline is written as the editor writes it. If it has more sections than slideCount, content sections are dropped and the title, table of contents, and summary stay. If it has fewer, the deck has fewer pages and costs less.
  • The deck is a classroom slide deck, PUBLIC like a deck made in the editor, titled after the outline's first section. resourceId is its id as soon as it exists, and it carries metadata.generationRunId.
  • Pages are written in batches of four. Every fourth page from the first may carry a generated picture, a layout image or an inline infographic, where the editor puts them. A picture that fails to generate is left out, and its page stays.
  • The deck is saved after every batch, so it can be opened while later pages are still being written. It is the slide editor's own document, so it opens in the editor, in the classroom, and on GET /slides/{slideId} like a deck made in the editor.

Two things look different from a deck made in the editor:

  • No thumbnail at first. The deck's list thumbnail is drawn by the editor when a teacher saves the deck, so a generated deck has none until a teacher opens and saves it.
  • Deck-wide layout can vary between batches. Each batch of four pages is written on its own, so a layout rule meant for the whole deck, such as alternating image sides, can slip where one batch ends and the next begins.

Output language

The language is decided once, when the run is accepted, and kept for the whole run:

  1. language, if you send it. A BCP 47 tag of up to 35 characters, such as ko, pt-BR, or zh-TW.
  2. Otherwise the language the request is written in: the topic (then instructions) of a test or a slide deck, the topic of a module, or the topic (then title) of a course.
  3. Otherwise English.

For Chinese, name the written form: zh-TW for Traditional or zh-CN for Simplified. A bare zh, or Chinese text with no tag, takes the form the text is written in, and Simplified when the text does not tell. Every lesson of a course is written in the course's language, and every page, presenter note, and image prompt of a deck in the deck's language.

Credits

Generation charges what the TutorFlow editor charges. Prices are in Pricing; the table shows how estimatedCredits is worked out.

KindWhat is charged, and whenestimatedCredits
Test3 credits once, when the test is saved.3
Module3 credits once, when the module is saved.3
Course3 credits for the outline, then 3 credits for each lesson as it is saved.3 + 3 x lessonCount, or 39 without lessonCount
Slide deckThe slide editor's price: 1 credit per text page and 3 per image page, every fourth page from the first being an image page. The outline is free. Charged per batch of four pages as it is written.The price of slideCount pages: 16 without slideCount

A deck costs the same as in the editor; only the moment of charging differs. The editor charges the whole deck when the teacher confirms the outline. The API charges each batch when it is written: a full batch of four is one image page and three text pages, 6 credits, and a shorter last batch of k pages costs k + 2. The images are included.

PagesImage pagesCreditsBatches
5296 + 3
103166 + 6 + 4
154236 + 6 + 6 + 5
205306 + 6 + 6 + 6 + 6
  • The request is refused with 402 before any work starts when estimatedCredits is more than the key's remaining monthly credit limit (content_key_budget_exceeded), or more than the organization's available credits (content_payment_required), or when the organization has a failed payment (content_payment_failed).
  • A course checks the balance and the key limit again before each lesson, and a deck before each batch. If either runs out partway, the run fails with a 402 error and keeps what it wrote, which stays charged.
  • Without lessonCount, the outline decides the number of lessons, and a deck outline with fewer sections than slideCount makes a smaller deck, so the final cost can differ from the estimate. The run's creditsCharged is the exact amount.
  • Charges are recorded under your key in the credit history and count toward its monthly limit. A deck's rows have type CREATE_SLIDE and the note Content API generation run {runId}: slide pages {first}-{last}.
  • Nothing is charged twice, whether you retry with the same Idempotency-Key or a server restarts during the run.

Follow a run

When the run ends, TutorFlow sends one webhook: test.generation.completed or test.generation.failed, and the same for module, course, and slide. Subscribe to them on an endpoint; see Generation events. There are no progress events.

To poll instead, read the statusUrl every 10 to 30 seconds:

HTTP
GET /v1/content/classrooms/{classroomId}/runs/{runId}
Authorization: Bearer tf_content_...
JSON
{
  "runId": "00000000-0000-4000-8000-000000000b01",
  "kind": "course",
  "classroomId": "00000000-0000-4000-8000-000000000010",
  "status": "running",
  "stage": "generating_lessons",
  "progress": { "completed": 4, "total": 6, "unit": "lesson" },
  "resourceId": "00000000-0000-4000-8000-000000000201",
  "creditsCharged": 15,
  "estimatedCredits": 21,
  "error": null,
  "createdAt": "2026-10-01T09:00:00.000Z",
  "finishedAt": null
}
FieldMeaning
kindtest, module, course, or slide.
statusqueued, running, completed, or failed.
stagenull while queued. Test: generating. Module: creating, then generating. Course: outlining, creating_course, then generating_lessons. Slides: outlining, creating_deck, then generating_pages. New stages may be added; display unknown ones as they are.
progresscompleted of total, counted in unit: test (total 1), step for a module (create, then write: total 2), lesson for a course, or page for a deck (for both, total 0 until the outline exists).
resourceIdThe test, module, course, or deck id, set as soon as it exists. A course or deck can be opened while it is still being written.
creditsChargedCredits charged so far, for finished steps.
errorWhy the run failed, or null.
finishedAtWhen the run ended, or null while it runs.

List runs

GET /runs lists the classroom's runs, newest first, with a cursor:

ParameterNotes
kindtest, module, course, or slide.
statusqueued, running, completed, or failed.
limitDefault 20. Values above 100 are clamped to 100.
cursormeta.nextCursor from the previous page. Send the same filters with it.
JSON
{
  "data": [{ "runId": "00000000-0000-4000-8000-000000000b01", "kind": "course", "status": "running" }],
  "meta": { "limit": 20, "hasNextPage": false, "nextCursor": null }
}

The list shows runs started through the Content API in that classroom, by any key of your organization. A classroom outside your key's allowlist answers 403 content_classroom_not_allowed, and a runId from another classroom or organization answers 404 content_not_found. A cursor that is malformed, or from another classroom or organization, answers 422. Runs are kept, and there is no route to delete one.

Retries and failures

  • A step that fails for a passing reason, such as a model timeout, is tried again. Each step gets 3 attempts in all, counting attempts cut short by a server restart. After that the run fails with an error like Stopped after 3 attempts at generating_lessons: <the last error>.
  • Errors that retrying cannot fix fail the run at once, with their own message in error: 402 (credits or the key's monthly limit), 401 or 403 (the key or the classroom is no longer allowed), 404, and 422.
  • If the server running a generation stops, another server continues it from the last finished step, usually within two to three minutes. Nothing already made is made or charged again.
  • Time limits: 20 minutes for a test, 30 minutes for a module, 15 minutes plus 8 minutes per lesson for a course, and 10 minutes plus 8 minutes per batch of four pages for a deck (50 minutes for 20 pages). A run past its limit fails with "The generation ran out of time and was stopped. Start it again."
  • A failed course or deck run keeps the course or deck and what it already wrote, and resourceId points to it. A retry makes a new one.

Idempotency

Send an Idempotency-Key with every generate request. A retry with the same key and body within 24 hours returns the first 202, with Idempotent-Replayed: true, and starts nothing. The same key with a different body answers 409 content_conflict.

When a run fails, its key is released before the *.failed webhook is sent. A retry with the same key after a failure starts a new run with a new runId, so on *.generation.failed you can retry with the key you already have.

What the run makes

  • Tests are saved PUBLIC, as the test editor saves them.
  • Modules are published when the run completes, and carry metadata.generationRunId with the run id.
  • Courses have their lessons published as each is written.
  • Slide decks are saved PUBLIC, as the slide editor saves them, after every batch.

Review generated content in TutorFlow before learners see it. Change it afterwards with the normal resource routes.

Was this page helpful?