POST /v1/platform/courses
Creates a course request. The AI model generates a structured curriculum with chapters and lessons, creates the course in TutorFlow, and returns edit and preview URLs.
Tiers
The tier field selects the quality and pricing contract for the request.
If omitted, TutorFlow defaults to standard:
| Tier | Price |
|---|---|
basic | $0.09 |
standard | $0.19 |
advanced | $0.29 |
TutorFlow stores a price snapshot at request time so billing remains consistent even if pricing is updated later.
Lesson Types
The AI selects interactive lesson types based on the course topic:
| Type | Description | Best for |
|---|---|---|
ai-tutor | Interactive AI tutor that discusses concepts with the student | Math, science, conceptual topics |
coding-lesson | Hands-on coding tutorial with IDE | Programming topics only |
coding-test | Coding exercise/challenge | Programming practice only |
chat-ai | AI conversation lesson | Prompt engineering, AI topics |
exam | Quiz/assessment | Practice problems, end-of-chapter review |
flashcard | Review/vocabulary/formula cards | Language learning, key concepts |
markdown | Text-only reading | Course overview, reference material |
The coding-lesson and coding-test types are reserved for programming topics. For
math, science, language, or general-knowledge courses, the AI uses ai-tutor,
chat-ai, exam, flashcard, or markdown instead.
The first lesson of every course is generated as ai-tutor so the learner has an
interactive entry point regardless of subject.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Prompt describing the course to generate |
title | string | No | Override AI-generated title |
description | string | No | Override AI-generated description |
language | string | No | Course language (default: en) |
level | string | No | beginner, intermediate, or advanced (validated enum) |
subject | string | No | Subject area (e.g. mathematics, physics, programming) |
lessonCount | number | No | Target number of lessons for AI to generate (1–50) |
classroomId | string | No | Classroom ID to create the course in. Uses the default classroom if omitted |
tier | string | No | basic, standard, or advanced. Defaults to standard |
hasQuiz | boolean | No | Whether to include quiz lessons in the generated course |
hasPractice | boolean | No | Whether to include practice exercises in the generated course |
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/courses \
-H "Authorization: Bearer tf_platform_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a beginner Python course covering variables, loops, and functions with hands-on exercises",
"language": "en",
"level": "beginner",
"lessonCount": 5
}'Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Platform course request ID |
status | string | PENDING, PROCESSING, COMPLETED, or FAILED |
courseId | string | null | Actual course ID in TutorFlow (null until generation completes) |
title | string | null | Course title |
description | string | null | Course description |
level | string | null | Difficulty level |
language | string | null | Course language (echoed from request, e.g. en) |
subject | string | null | Subject area (echoed from request) |
slug | string | null | URL-friendly course slug |
tier | string | Pricing tier used |
mode | string | null | Execution mode (sync or async) |
hasQuiz | boolean | null | Whether quizzes were requested |
hasPractice | boolean | null | Whether practice exercises were requested |
lessonCount | number | null | Number of lessons in the generated course (length of lessons). Null until generation completes. |
priceSnapshot | object | null | Pricing details captured at request time |
chapters | array | null | Chapter summaries with title, sequence, lessonCount, and slug |
lessons | array | null | Lesson summaries with title, type, sequence, chapterTitle, chapterSlug, lessonSlug, and lessonUrl |
shareToken | string | null | Token used to construct the public URL and to resolve edit tokens for the list-mode preview link |
previewUrl | string | null | Editor URL — direct link to /{locale}/platform/courses/edit/{editToken} (single-course endpoints) or /{locale}/platform/courses/{shareToken} (list endpoint, auto-resolves the latest edit token) |
publicUrl | string | null | Public read-only learner URL, opens at the first lesson |
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",
"courseId": "c7d8e9f0-1a2b-3c4d-5e6f-7g8h9i0j1k2l",
"title": "Python Quick Start",
"description": "A beginner-friendly Python course covering variables, loops, and functions.",
"language": "en",
"subject": null,
"level": "beginner",
"slug": "python-quick-start-2e5ad6",
"tier": "standard",
"mode": "sync",
"hasQuiz": true,
"hasPractice": true,
"lessonCount": 6,
"priceSnapshot": {
"category": "course",
"catalogKey": "course.standard",
"tier": "standard",
"amountUsd": 0.19,
"unit": "course",
"currency": "USD",
"source": "platform_pricing_catalog_v1"
},
"shareToken": "b018172542f9a3c4d5e6f7890abcdef12345678",
"previewUrl": "https://tutorflow.io/en/platform/courses/edit/9f3a...",
"publicUrl": "https://tutorflow.io/en/platform/courses/b018172542f9.../lessons/1",
"chapters": [
{ "title": "Introduction to Variables", "sequence": 1, "lessonCount": 2, "slug": "introduction-to-variables-cf1710" },
{ "title": "Working with Loops", "sequence": 2, "lessonCount": 2, "slug": "working-with-loops-253716" },
{ "title": "Defining Functions", "sequence": 3, "lessonCount": 2, "slug": "defining-functions-ad2b35" }
],
"lessons": [
{ "title": "Welcome to Python", "type": "ai-tutor", "sequence": 1, "chapterTitle": "Introduction to Variables", "chapterSlug": "introduction-to-variables-cf1710", "lessonSlug": "welcome-to-python-29ae64", "lessonUrl": "https://tutorflow.io/en/platform/courses/b018.../lessons/1" },
{ "title": "Variable Practice", "type": "coding-test", "sequence": 2, "chapterTitle": "Introduction to Variables", "chapterSlug": "introduction-to-variables-cf1710", "lessonSlug": "variable-practice-1b3f82", "lessonUrl": "https://tutorflow.io/en/platform/courses/b018.../lessons/2" },
{ "title": "For and While Loops", "type": "coding-lesson", "sequence": 3, "chapterTitle": "Working with Loops", "chapterSlug": "working-with-loops-253716", "lessonSlug": "for-and-while-loops-59c305", "lessonUrl": "https://tutorflow.io/en/platform/courses/b018.../lessons/3" },
{ "title": "Loop Challenges", "type": "coding-test", "sequence": 4, "chapterTitle": "Working with Loops", "chapterSlug": "working-with-loops-253716", "lessonSlug": "loop-challenges-a4b5c6", "lessonUrl": "https://tutorflow.io/en/platform/courses/b018.../lessons/4" },
{ "title": "Writing Functions", "type": "coding-lesson", "sequence": 5, "chapterTitle": "Defining Functions", "chapterSlug": "defining-functions-ad2b35", "lessonSlug": "writing-functions-d7e8f9", "lessonUrl": "https://tutorflow.io/en/platform/courses/b018.../lessons/5" },
{ "title": "Function Exercises", "type": "coding-test", "sequence": 6, "chapterTitle": "Defining Functions", "chapterSlug": "defining-functions-ad2b35", "lessonSlug": "function-exercises-0a1b2c", "lessonUrl": "https://tutorflow.io/en/platform/courses/b018.../lessons/6" }
],
"idempotencyKey": null,
"idempotentReplay": null,
"isTerminal": true,
"createdAt": "2026-03-24T10:32:15.108Z",
"completedAt": "2026-03-24T10:32:37.671Z"
}Preview URL vs Public URL
| URL | Purpose | Auth | Expiry |
|---|---|---|---|
previewUrl | Course editor (lecture, title, curriculum, chapters, lessons) | Public, no login | Edit token expires after 1h |
publicUrl | Public course URL for learners, opens at the first lesson | Public, no login | None |
The previewUrl returned by POST /v1/platform/courses and GET /v1/platform/courses/:id
is a direct link to the editor for the current edit token
(/{locale}/platform/courses/edit/{editToken}). Anyone with the URL can edit lesson
content, title, description, and curriculum structure without logging in. The edit
token is valid for one hour; the share-token endpoints (GET /public/:shareToken/full)
auto-refresh it on access, or you can mint a new one explicitly via
POST /v1/platform/courses/:id/edit-token.
The list endpoint (GET /v1/platform/courses) returns a previewUrl of the form
/{locale}/platform/courses/{shareToken} instead — that page resolves the latest
edit token server-side and redirects into the editor, so you do not need to refresh
tokens before generating list links.
The publicUrl is the read-only link you share with learners. Each lesson summary
also includes a lessonUrl that links directly to that lesson by sequence.
Refreshing an Edit Token
curl -X POST https://api.tutorflow.io/v1/platform/courses/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/courses/edit/:editToken | Get course summary |
GET | /v1/platform/courses/edit/:editToken/full | Get full course with all lesson content |
GET | /v1/platform/courses/edit/:editToken/lessons/:sequence | Get single lesson |
PATCH | /v1/platform/courses/edit/:editToken | Update course title/description |
PATCH | /v1/platform/courses/edit/:editToken/lessons/:sequence | Update lesson content, title, slug, or description |
POST | /v1/platform/courses/edit/:editToken/lessons | Add a new lesson to the course |
POST | /v1/platform/courses/edit/:editToken/infer-type | Infer the best lesson type from a title/description |
Add Lesson
curl -X POST https://api.tutorflow.io/v1/platform/courses/edit/{editToken}/lessons \
-H "Content-Type: application/json" \
-d '{
"title": "Solving Quadratic Equations",
"description": "Practice the quadratic formula on real problems.",
"type": "ai-tutor",
"quizCount": 0,
"chapterSequence": 2
}'| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Lesson title (≤ 255 chars) |
type | string | Yes | Lesson type (ai-tutor, coding-lesson, coding-test, chat-ai, exam, flashcard, markdown) |
description | string | No | Lesson description (≤ 2000 chars) |
quizCount | number | No | Number of quizzes to auto-generate (0–20) |
chapterSequence | number | No | Chapter sequence to add the lesson to. Defaults to the last chapter. |
Returns the created lesson as PlatformLessonPublicDto (id, sequence, title, type,
chapterTitle, description, lectureContent, codeContent, quizzes, lessonSlug,
chapterSlug).
Update Course
curl -X PATCH https://api.tutorflow.io/v1/platform/courses/edit/{editToken} \
-H "Content-Type: application/json" \
-d '{ "title": "New Title", "description": "Updated overview." }'| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Updated course title |
description | string | No | Updated course description |
Returns { "success": true }.
Update Lesson
curl -X PATCH https://api.tutorflow.io/v1/platform/courses/edit/{editToken}/lessons/3 \
-H "Content-Type: application/json" \
-d '{ "lecture": "<p>Updated HTML</p>", "title": "Loops, Revisited" }'| Field | Type | Required | Description |
|---|---|---|---|
lecture | string | No | Updated lecture HTML content |
title | string | No | Updated lesson title |
description | string | No | Updated lesson description |
lessonSlug | string | No | Updated lesson slug |
Returns { "success": true }.
Infer Lesson Type
Given a lesson title (and optional context), the AI returns the best lesson type to use. Useful when an editor wants to insert a new lesson without picking a type manually.
curl -X POST https://api.tutorflow.io/v1/platform/courses/edit/{editToken}/infer-type \
-H "Content-Type: application/json" \
-d '{
"lessonTitle": "Practice: Factoring Polynomials",
"lessonDescription": "Hands-on practice problems for factoring.",
"courseTitle": "Intermediate Algebra",
"courseDescription": "Algebra fundamentals for high school students.",
"existingLessons": [
{ "title": "Welcome to Algebra", "type": "ai-tutor" },
{ "title": "Variables and Expressions", "type": "ai-tutor" }
]
}'| Field | Type | Required | Description |
|---|---|---|---|
lessonTitle | string | Yes | Title of the lesson being inferred |
lessonDescription | string | No | Optional description for additional context |
courseTitle | string | No | Course title for context |
courseDescription | string | No | Course description for context |
existingLessons | array | No | Other lessons in the course as { title, type } items |
Response:
{ "type": "exam" }Async Mode
Set mode: "async" to queue course generation as a background job. The response
returns immediately with status: "PENDING" and a pollAfterMs value. Poll
GET /v1/platform/courses/:id until isTerminal is true.
Idempotency
Pass an idempotencyKey in the request body or Idempotency-Key header to prevent
duplicate course generation. Reusing the same key returns the original response
with idempotentReplay: true.