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
}Response 201:
{
"apiKey": "tf_content_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"keyId": "0b063e58-7e19-4787-90f0-d081c85f50d3",
"keyPrefix": "tf_content_abc123def456",
"name": "external-content-test",
"rateLimitPerMinute": 60
}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.
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. |
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 delete returns 200 with deleted id and optional chatSessionId.
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 204 with no body. Scene delete returns 200 with the updated video object.
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 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 }.
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, slide create, key rotate, key revoke, and webhook delete requests succeeded. |
201 | Key, webhook, module, course, video, video scene, 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, or webhook was not found. |
409 | Idempotency or state conflict. |
429 | Rate limit exceeded. |
500 | Unexpected server error. |