All paths are relative to https://api.tutorflow.io.
Content Integration uses /v1/content/** for classroom-owned TutorFlow content. The Agent Platform uses /v1/platform/** for AI agent workflows. Do not use Platform credentials or Agent Platform ownership fields for Content Integration requests.
Authentication types
| Auth type | Used by | Header |
|---|---|---|
| Admin session | Key management and webhook management | Cookie: jwt=... or curl -b tutorflow-admin.cookies |
| Content API key | Expansion create, status, result, direct content resources | Authorization: Bearer tf_content_... |
API key endpoints
List manageable organizations
GET /v1/content/organizationsAuth: admin session.
Use this endpoint first when the customer does not know their organization ID. It returns organizations where the signed-in admin can manage Content Integration.
Response 200:
[
{
"id": "00000000-0000-4000-8000-000000000001",
"name": "Customer Academy",
"slug": "customer-academy",
"role": "ADMIN"
}
]Create key
POST /v1/content/organizations/{organizationId}/api-keysAuth: admin session. Use an id returned by GET /v1/content/organizations as {organizationId}.
Request:
{
"name": "external-content-test",
"rateLimitPerMinute": 60,
"expiresAt": "2026-12-31T23:59:59.000Z"
}expiresAt is optional. Omit it or send null for a non-expiring key.
Response 201:
{
"apiKey": "tf_content_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"keyId": "0b063e58-7e19-4787-90f0-d081c85f50d3",
"keyPrefix": "tf_content_abc123def456",
"name": "external-content-test",
"rateLimitPerMinute": 60,
"expiresAt": "2026-12-31T23:59:59.000Z"
}List keys
GET /v1/content/organizations/{organizationId}/api-keysAuth: admin session.
Response 200 includes key metadata only. The full secret is not returned.
Rotate key
POST /v1/content/organizations/{organizationId}/api-keys/{keyId}/rotateAuth: admin session.
Response 200 returns a new one-time apiKey.
The rotated key keeps the old key's name, rateLimitPerMinute, and expiresAt. Create a new key instead if you need a different expiration policy.
Revoke key
POST /v1/content/organizations/{organizationId}/api-keys/{keyId}/revokeAuth: admin session.
Response 200 has no body.
Expansion endpoints
Create expansion
POST /v1/content/integrations/expansionsHeaders:
Authorization: Bearer tf_content_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: source-content-level-a1-2026-07-04Request fields:
| Field | Type | Required | Notes |
|---|---|---|---|
classroomId | uuid | Yes | Target classroom. Use GET /v1/content/classrooms after key creation to discover it. |
requestedOutputs | string[] | No | Supported values are interactive_module, summary_video, expanded_quiz. |
payload | object | Yes | Existing source JSON. |
sourceType | string | No | Defaults to source_json. |
idempotencyKey | string | No | Header is preferred. If both are sent, the Idempotency-Key header is used. |
Idempotency is request-body aware. Repeating the same key with the same classroom, requested outputs, source type, and payload returns the existing job. Reusing the same key with different request content returns 409.
Response 202 returns the created job and queued output rows.
Get status
GET /v1/content/integrations/expansions/{jobId}Response 200 returns the job and outputs. Poll until completed or failed.
Get result
GET /v1/content/integrations/expansions/{jobId}/resultResponse 200 returns job, outputs, and a combined manifest.
Classroom discovery
GET /v1/content/classroomsAuth: Content API key.
Response:
[
{
"id": "00000000-0000-4000-8000-000000000010",
"name": "Customer Academy",
"slug": "customer-academy",
"locale": "en"
}
]Use one returned id as {classroomId} for expansion and resource CRUD endpoints.
Content resource endpoints
The same tf_content_ bearer key can create and control TutorFlow content resources directly. These endpoints do not require the external system to know an organization ID after the key has been created.
Modules
POST /v1/content/classrooms/{classroomId}/modules
GET /v1/content/classrooms/{classroomId}/modules
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-courseModule delete returns 200 with { "success": true }.
Courses
POST /v1/content/classrooms/{classroomId}/courses
GET /v1/content/classrooms/{classroomId}/courses
GET /v1/content/classrooms/{classroomId}/courses/{courseId}
PATCH /v1/content/classrooms/{classroomId}/courses/{courseId}
DELETE /v1/content/classrooms/{classroomId}/courses/{courseId}Course PATCH is partial. Fields omitted from the request keep their existing values. Course delete returns 200 with deleted id and optional chatSessionId.
Course create also prepares the admin editor starter curriculum when no curriculum is supplied: 3 chapters, 4 lessons per chapter, and 12 editable markdown lessons.
Videos
POST /v1/content/classrooms/{classroomId}/videos
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/reorderVideo delete returns 200 with { "success": true }. Scene delete returns 200 with the updated video object.
POST /videos accepts an optional scenes array. If scenes is omitted, TutorFlow creates the Content API default video setup: landscape 16:9, layout mode auto, target duration 120 seconds, and 13 starter scenes. Scene timing fields must be numbers when present; omit optional timing fields instead of sending null.
Slides
POST /v1/content/classrooms/{classroomId}/slides
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}Slide create returns 201 with the created slide object. If content is omitted, TutorFlow creates the Content API default deck: 10 editable slides with generated image prompt markers on every fourth slide, starting with slide 1. If content is included on create, the create response and later GET /slides/{slideId} response include the stored content. Slide delete returns 200 with the deleted slide object.
Tests
POST /v1/content/classrooms/{classroomId}/tests
GET /v1/content/classrooms/{classroomId}/tests
GET /v1/content/classrooms/{classroomId}/tests/{testId}
PATCH /v1/content/classrooms/{classroomId}/tests/{testId}
DELETE /v1/content/classrooms/{classroomId}/tests/{testId}Test delete returns 200 with { "success": true }.
Test create defaults omitted level, totalScore, passingValueType, and passingValue to MEDIUM, 100, SCORE, and 80. A new test receives 20 starter questions worth 5 points each.
For request and response examples, see Content Resource API.
Optional legacy standalone lesson export
POST /v1/content/classrooms/{classroomId}/integrations/exportsRequest:
{
"lessonId": "standalone-lesson-id",
"idempotencyKey": "standalone-lesson:standalone-lesson-id"
}Skip this endpoint for new integrations. Use it only when TutorFlow support confirms that an existing standalone lesson is not represented by the course, video, slide, test, or module resource APIs. For courses, videos, slides, tests, and modules, use the Content resource endpoints above with the tf_content_ bearer key.
Webhook endpoints
Create webhook
POST /v1/content/organizations/{organizationId}/webhooksAuth: admin session. Use an id returned by GET /v1/content/organizations as {organizationId}.
Request:
{
"url": "https://example.com/tutorflow/content-webhooks",
"events": ["content.completed", "content.failed"]
}Response 201 returns endpoint metadata and a one-time secret.
List webhooks
GET /v1/content/organizations/{organizationId}/webhooksResponse 200 returns endpoint metadata without the signing secret.
Delete webhook
DELETE /v1/content/organizations/{organizationId}/webhooks/{webhookId}Response 200 has no body.
Status codes
| Code | Meaning |
|---|---|
200 | Read, update, delete, module copy, key rotate, key revoke, and webhook delete requests succeeded. |
201 | Key, webhook, module, course, video, video scene, slide, or test created. |
202 | Expansion job accepted. |
400 | Request body is invalid. |
401 | Missing or invalid Content API key. |
403 | Signed-in admin cannot manage the organization. |
404 | Job, key, webhook, classroom, or content resource was not found. Deleted content resources also return 404. |
409 | Idempotency or state conflict, including reusing an idempotency key with different request content. |
429 | Rate limit exceeded. |
500 | Unexpected server error. |