Use this runbook when a developer or AI coding agent is writing integration code, test scripts, SDK wrappers, or support diagnostics for Content Integration.
Non-negotiable boundary
Content Integration is the API surface for content that people create, review, or manage in TutorFlow UI.
| Use case | Correct API |
|---|---|
| Create, read, update, or delete UI-owned courses, videos, slides, tests, or modules | /v1/content/** |
| Expand customer-owned source JSON into editable TutorFlow content | /v1/content/integrations/expansions |
| Create or rotate a key for an external content system | /v1/content/organizations/{organizationId}/api-keys |
| Autonomous AI agent workflows that were built for Agent Platform | /v1/platform/** |
Do not mix these surfaces. A Content API client should never require an Agent Platform ownership id, platform key, edit token, or Platform public URL.
Discovery sequence
Never ask a customer to guess ids from a screenshot or copy ids from an internal database.
- Sign in as a TutorFlow admin and create
tutorflow-admin.cookies. - Call
GET /v1/content/organizationswith the admin session cookie. - Create a
tf_content_key for the selected organization. - Call
GET /v1/content/classroomswith thetf_content_bearer key. - Use the returned classroom
idfor expansion and resource CRUD.
Minimal environment variables
Discover these values through the API. Do not ask the customer to find them in screenshots or internal admin tools.
export TUTORFLOW_API_BASE_URL="https://api.tutorflow.io"
export TUTORFLOW_CONTENT_API_KEY="$(printf '%s' "$KEY_RESPONSE" | jq -r '.apiKey')"
export TUTORFLOW_CLASSROOM_ID="$(curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/classrooms" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" | jq -r '.[0].id')"Do not introduce an organization-id environment variable as a prerequisite for bearer-key resource calls. Organization id is only needed while an admin creates or manages keys and webhooks.
Safe request defaults
Use these defaults in generated clients and examples:
| Concern | Default |
|---|---|
| Auth header | Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY |
| Content type | Content-Type: application/json |
| Idempotency | Send Idempotency-Key on every create request. |
| Timeout | Use a client timeout of at least 30 seconds for create requests. |
| Polling | Poll expansion jobs every 2 to 5 seconds until completed or failed. |
| Retries | Retry network failures with the same idempotency key. Do not retry validation errors blindly. |
Good idempotency key patterns:
module:{externalModuleId}:{version}
course:{externalCourseId}:{version}
video:{externalVideoId}:{version}
slides:{externalDeckId}:{version}
test:{externalAssessmentId}:{version}
expansion:{externalContentGroupId}:{version}Response handling contract
Store these values after every successful create or expansion result:
| Field | Store it because |
|---|---|
TutorFlow resource id | It is required for later GET, PATCH, and DELETE calls. |
classroomId | It proves the resource is attached to the intended classroom. |
keyPrefix | It is safe to share in support tickets. |
Idempotency-Key | It explains replay behavior and prevents accidental duplicates. |
job.id | It is the support and reconciliation id for expansion jobs. |
outputs[].resourceId | It links generated outputs to direct resource APIs. |
outputs[].manifest | It is the handoff payload for review or downstream sync. |
Do not store full tf_content_ keys in logs, analytics events, crash reports, or generated code comments.
Smoke test script outline
The smallest smoke test can use one module. A production-ready integration test must also exercise the resource types the customer plans to manage.
set -euo pipefail
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/classrooms" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"
MODULE_ID="$(curl -sS -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:smoke-test:v1" \
-d '{
"title": "Content API smoke test",
"type": "markdown",
"content": "<h2>Smoke test</h2><p>This module was created by the Content API.</p>",
"metadata": {
"externalId": "module:smoke-test"
}
}' | jq -r '.id')"
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/modules/$MODULE_ID" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"
curl -sS -X PATCH "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/modules/$MODULE_ID" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Content API smoke test updated"
}'
curl -sS -X DELETE "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/modules/$MODULE_ID" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"Full CRUD coverage matrix
For a customer integration that controls TutorFlow UI content, verify every required resource type before handoff.
| Resource | Create | Read | Update | Delete | Required handoff value |
|---|---|---|---|---|---|
| Course | POST /v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/courses | GET /courses/$COURSE_ID | PATCH /courses/$COURSE_ID | DELETE /courses/$COURSE_ID | COURSE_ID |
| Video | POST /v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/videos | GET /videos/$VIDEO_ID | PATCH /videos/$VIDEO_ID | DELETE /videos/$VIDEO_ID | VIDEO_ID |
| Slide | POST /v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/slides | GET /slides/$SLIDE_ID | PATCH /slides/$SLIDE_ID | DELETE /slides/$SLIDE_ID | SLIDE_ID |
| Test | POST /v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/tests | GET /tests/$TEST_ID | PATCH /tests/$TEST_ID | DELETE /tests/$TEST_ID | TEST_ID |
| Module | POST /v1/content/classrooms/$TUTORFLOW_CLASSROOM_ID/modules | GET /modules/$MODULE_ID | PATCH /modules/$MODULE_ID | DELETE /modules/$MODULE_ID | MODULE_ID |
Use Content Resource API for complete request bodies. A test script should fail fast when any returned id is empty or null.
Common AI agent mistakes
| Mistake | Correct behavior |
|---|---|
Using /v1/platform/** because the task mentions external automation | Use /v1/content/** for human-owned TutorFlow UI content. |
| Asking the customer for a hard-coded organization id | Call GET /v1/content/organizations after admin login. |
| Asking the customer for a hard-coded classroom id | Call GET /v1/content/classrooms with the Content API key. |
| Reusing one idempotency key for changed request bodies | Generate a new key when the source version changes. |
| Logging the full bearer key in test output | Log only keyPrefix. |
| Treating summary video output as a finished MP4 | Treat it as editable video structure until rendering is triggered after review. |
| Expecting every delete to return the same body | Follow the resource-specific delete table in Content Resource API. |
Pre-handoff checklist
Before handing integration code to a customer, verify:
- The script discovers organization and classroom ids through the API.
- The script uses only
tf_content_keys for/v1/content/**. - No Agent Platform ownership id, platform API key, edit token, or Platform URL appears in generated customer code or support notes.
- Create requests send stable idempotency keys.
- The script stores returned TutorFlow ids next to external ids.
- The script handles
400,401,403,404,409, and429responses. - Logs redact the full bearer token.
- A small pilot payload completes before larger production payloads are sent.