Use the Content Resource API when an external system needs direct control of TutorFlow-authored content. These endpoints use the dedicated tf_content_ bearer key. They do not use admin cookies after the key has been created.
Use these endpoints for content that people create, review, or manage in TutorFlow classrooms. Do not use Agent Platform endpoints for this workflow. Agent Platform endpoints live under /v1/platform/**, use separate credentials, and are reserved for AI agent workflows.
All paths are relative to https://api.tutorflow.io.
export TUTORFLOW_API_BASE_URL="https://api.tutorflow.io"Set TUTORFLOW_CONTENT_API_KEY from the apiKey returned by Content API Key Management. Do not paste the placeholder key from response examples into scripts.
Find the target classroom before running resource examples:
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/classrooms" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"Then store the classroom id:
export TUTORFLOW_CLASSROOM_ID="$(curl -s "$TUTORFLOW_API_BASE_URL/v1/content/classrooms" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" | jq -r '.[0].id')"If TUTORFLOW_CLASSROOM_ID is empty or null, create or select a classroom in TutorFlow before using direct resource CRUD.
Common request headers:
Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY
Content-Type: application/json
Idempotency-Key: source-system-resource-versionResource model
| Resource | Collection path | Use it for |
|---|---|---|
| Module | /v1/content/classrooms/{classroomId}/modules | One interactive lesson or learning module. |
| Course | /v1/content/classrooms/{classroomId}/courses | Multi-lesson curriculum with chapters and lesson content. |
| Video | /v1/content/classrooms/{classroomId}/videos | Editable video plan, scenes, narration, and rendering control. |
| Slide | /v1/content/classrooms/{classroomId}/slides | Editable slide presentation content. |
| Test | /v1/content/classrooms/{classroomId}/tests | Assessment metadata, items, answers, scoring, and submissions. |
The response is scoped to the Content API. Internal authoring tokens and internal ownership fields are not returned. Use the IDs returned by these endpoints for later reads, updates, deletes, and downstream reconciliation.
Request and response conventions
Create endpoints support Idempotency-Key. Use one stable key per external resource version. If the same key and same body are retried, TutorFlow returns the original response. If the same key is reused with a different body, TutorFlow returns 409.
Create and update responses return the same classroom-owned content shape used by the TutorFlow UI, with internal editing fields removed. Store these fields when present:
| Field | Why it matters |
|---|---|
id | Use as {moduleId}, {courseId}, {videoId}, {slideId}, or {testId} on later calls. |
classroomId | Confirms the resource is attached to the intended classroom. |
title or name | Display label for review queues and sync logs. |
slug | Useful for human-readable reconciliation when returned. |
createdAt, updatedAt | Useful for incremental sync checks when returned. |
Example trimmed create response:
{
"id": "00000000-0000-4000-8000-000000000101",
"classroomId": "00000000-0000-4000-8000-000000000010",
"title": "Introduction to safety",
"description": "A concise interactive module for new employees.",
"type": "markdown",
"status": "ready",
"metadata": {
"externalId": "module:intro-to-safety"
},
"createdAt": "2026-07-05T00:00:00.000Z",
"updatedAt": "2026-07-05T00:00:00.000Z"
}Create responses are resource-specific:
| Endpoint | Success response |
|---|---|
POST /modules | 201 with the created module object |
POST /courses | 201 with the created course object |
POST /videos | 201 with the created video object |
POST /videos/{videoId}/scenes | 201 with the updated video object |
POST /slides | 200 with the created slide object |
POST /tests | 201 with the created test object |
Update responses are also resource-specific. Module, slide, and test updates return 200 with { "success": true }. Course and video updates return 200 with the updated resource object. Video scene update and reorder return 200 with the updated video object.
Delete responses are resource-specific:
| Endpoint | Success response |
|---|---|
DELETE /modules/{moduleId} | 200 with { "success": true } |
DELETE /courses/{courseId} | 200 with deleted id and optional chatSessionId |
DELETE /videos/{videoId} | 204 with no body |
DELETE /slides/{slideId} | 200 with the deleted slide object |
DELETE /tests/{testId} | 200 with { "success": true } |
DELETE /videos/{videoId}/scenes/{sceneId} | 200 with the updated video object |
Modules
Create an interactive module:
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/modules" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: module:intro-to-safety:v1" \
-d '{
"title": "Introduction to safety",
"description": "A concise interactive module for new employees.",
"type": "markdown",
"isPublic": false,
"content": "<h2>Safety first</h2><p>Always report hazards.</p>",
"metadata": {
"externalId": "module:intro-to-safety"
}
}'Common module endpoints:
GET /v1/content/classrooms/{classroomId}/modules?limit=20&page=1
GET /v1/content/classrooms/{classroomId}/modules/{moduleId}
PATCH /v1/content/classrooms/{classroomId}/modules/{moduleId}
DELETE /v1/content/classrooms/{classroomId}/modules/{moduleId}
POST /v1/content/classrooms/{classroomId}/modules/{moduleId}/copy-to-courseUpdate a module:
{
"title": "Workplace safety basics",
"description": "A concise interactive module for new employees.",
"content": "<h2>Safety first</h2><p>Always report hazards.</p>",
"quizzes": [
{
"id": "quiz-1",
"sequence": 1,
"title": "Safety check",
"question": "What should you do when you see a hazard?",
"type": "select",
"options": ["Ignore it", "Report it", "Hide it"],
"correctAnswers": ["Report it"],
"showFeedback": true
}
]
}Module quiz type values are select, blank, and true-false.
Copy a module into an existing course:
Set MODULE_ID to the id returned by module create or list.
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/modules/$MODULE_ID/copy-to-course" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"courseId": "00000000-0000-4000-8000-000000000201",
"sequence": 1
}'Courses
Create a course:
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/courses" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: course:customer-onboarding:v1" \
-d '{
"title": "Customer onboarding",
"description": "Five onboarding lessons for customer success teams.",
"level": "Beginner",
"visibility": "PRIVATE"
}'Common course endpoints:
GET /v1/content/classrooms/{classroomId}/courses?limit=20&page=1
GET /v1/content/classrooms/{classroomId}/courses/{courseId}
PATCH /v1/content/classrooms/{classroomId}/courses/{courseId}
DELETE /v1/content/classrooms/{classroomId}/courses/{courseId}Course lesson structure follows the same stored course object the TutorFlow UI reads. Use module copy or the TutorFlow UI for detailed lesson authoring.
Course delete response:
{
"id": "00000000-0000-4000-8000-000000000201",
"chatSessionId": "00000000-0000-4000-8000-000000000301"
}Videos
Create an editable video:
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/videos" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video:safety-summary:v1" \
-d '{
"title": "Safety summary",
"description": "Editable video shell for workplace safety.",
"visibility": "PRIVATE"
}'Common video endpoints:
GET /v1/content/classrooms/{classroomId}/videos
GET /v1/content/classrooms/{classroomId}/videos/{videoId}
PATCH /v1/content/classrooms/{classroomId}/videos/{videoId}
DELETE /v1/content/classrooms/{classroomId}/videos/{videoId}
POST /v1/content/classrooms/{classroomId}/videos/{videoId}/scenes
PATCH /v1/content/classrooms/{classroomId}/videos/{videoId}/scenes/{sceneId}
DELETE /v1/content/classrooms/{classroomId}/videos/{videoId}/scenes/{sceneId}
PATCH /v1/content/classrooms/{classroomId}/videos/{videoId}/scenes/reorderCreate a scene:
{
"insertAfterSceneId": null
}The create-scene response is the updated video object. Read the new scene id from the returned scenes array, then use that id for scene update, delete, or reorder calls.
Update a scene:
{
"script": "Welcome to the safety summary.",
"displayText": "Welcome to the safety summary.",
"duration": 8
}Reorder scenes:
{
"sceneIds": ["00000000-0000-4000-8000-000000000001"]
}Video rendering is not exposed as a Content API endpoint yet. Treat videos created here as editable classroom assets and trigger final rendering from TutorFlow after review.
Slides
Create slides:
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/slides" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: slides:safety-training:v1" \
-d '{
"title": "Safety training",
"description": "Deck for the July onboarding cohort.",
"outline": "1. Why safety matters\n2. Reporting hazards",
"visibility": "PRIVATE",
"content": "<slide><h1>Safety training</h1></slide>"
}'Common slide endpoints:
GET /v1/content/classrooms/{classroomId}/slides
GET /v1/content/classrooms/{classroomId}/slides/{slideId}
PATCH /v1/content/classrooms/{classroomId}/slides/{slideId}
DELETE /v1/content/classrooms/{classroomId}/slides/{slideId}Update slide content:
{
"title": "Safety training",
"description": "Updated deck for the July onboarding cohort.",
"content": "<slide><h1>Safety training</h1></slide>"
}Tests
Create a test:
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/tests" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: test:safety-check:v1" \
-d '{
"topicPrompt": "Safety check",
"targetPrompt": "Assess basic workplace safety knowledge.",
"level": "MEDIUM",
"passingValueType": "PERCENTAGE",
"passingValue": 70
}'Common test endpoints:
GET /v1/content/classrooms/{classroomId}/tests?limit=20&page=1
GET /v1/content/classrooms/{classroomId}/tests/{testId}
PATCH /v1/content/classrooms/{classroomId}/tests/{testId}
DELETE /v1/content/classrooms/{classroomId}/tests/{testId}Test item type values are select, blank, open-ended, true-false, and submission.
Update a test item:
{
"name": "Safety check",
"timeLimit": 20,
"items": [
{
"question": "What should you do when you see a hazard?",
"type": "select",
"options": ["Report it", "Ignore it", "Hide it"],
"correctAnswers": ["Report it"],
"explanation": "Reporting hazards helps the team respond quickly.",
"score": 10
}
]
}Idempotency
Send an Idempotency-Key on every create request. Reusing the same key returns the original resource instead of creating duplicates.
Good key patterns:
module:{sourceLessonId}:{sourceVersion}
course:{sourceLevelId}:{sourceVersion}
video:{sourceLevelId}:summary:{sourceVersion}
slides:{sourceDeckId}:{sourceVersion}
test:{sourceAssessmentId}:{sourceVersion}External system checklist
- Store TutorFlow resource IDs next to source system IDs.
- Store the idempotency key used for each create request.
- Use
GETafterPOSTbefore showing content to reviewers. - Treat generated videos as editable classroom assets. Rendering is controlled from the TutorFlow UI until a dedicated Content API render endpoint is published.
- Never store or display internal authoring tokens.
- Use a separate Content API key for test and production.
- Do not use
/v1/platform/**, platform API keys, Agent Platform ownership identifiers, edit tokens, or Platform public URLs for content that people create or manage in TutorFlow UI.