Resources
Developer and AI Agent Runbook

Developer and AI Agent Runbook

Safe defaults, boundaries, and verification steps for developers and AI coding agents writing Content API integration code.

On this page

Use this runbook when you, or an AI coding agent working for you, write integration code, test scripts, SDK wrappers, or support diagnostics for the Content API. For a machine-readable description of every route, import the OpenAPI description.

The boundary

TaskAPI
Create, read, update, delete, or sync modules, courses, videos, slides, tests, games, or simulations in a classroom/v1/content/classrooms/{classroomId}/...
Plan, build, or revise a game or simulation/v1/content/classrooms/{classroomId}/{games,simulations}/{id}/{brief,build,revise}
Render a video to mp4/v1/content/classrooms/{classroomId}/videos/{videoId}/render
Expand your own source JSON into TutorFlow content/v1/content/integrations/expansions
Generate a test, module, or course from a topic/v1/content/classrooms/{classroomId}/{tests,modules,courses}/generate, then /runs/{runId}
Upload a file for a resource field/v1/content/classrooms/{classroomId}/assets
Read or manage a classroom's learners/v1/content/classrooms/{classroomId}/learners/..., with learners:read or learners:write
Read what a key spent or changed/v1/content/credits/history, /v1/content/audit-log
Create or rotate a keySettings > Content API, or /v1/content/organizations/{organizationId}/api-keys with an admin session
Autonomous AI agent workflows built for the Agent Platform/v1/platform/**, a separate API

A Content API client never needs a platform key, a workspace id, an edit token, or a platform URL. If generated code reaches for one, it is on the wrong API.

Discovery

Never hard-code ids copied from screenshots, examples, or a database.

  1. The key comes from Settings > Content API, as an environment variable.
  2. GET /v1/content/credits confirms the key works.
  3. GET /v1/content/classrooms lists the classrooms the key can use. Select one by name or slug from configuration, not by array position.
  4. Resource ids come from create responses, lists, or expansion results.

Resource calls do not need an organization id. It is needed only for admin session routes.

Environment variables

Use these names, as the rest of these docs do:

VariableHolds
TUTORFLOW_API_BASE_URLhttps://api.tutorflow.io
TUTORFLOW_CONTENT_API_KEYThe tf_content_ key. From a secret manager, never from source.
CLASSROOM_IDThe target classroom id.
JOB_ID, MODULE_ID, COURSE_ID, VIDEO_ID, SLIDE_ID, TEST_ID, GAME_ID, SIMULATION_ID, WEBHOOK_IDIds returned by the API.
TUTORFLOW_WEBHOOK_SECRETThe webhook signing secret.

Safe defaults

ConcernDefault
HeadersAuthorization: Bearer tf_content_... and Content-Type: application/json.
IdempotencyAn Idempotency-Key on every create and every POST action, built from your own ids and a version: module:{externalId}:v{version}, video:{videoId}:render:v{n}, expansion:{externalLevelId}:v{sourceVersion}. The full pattern list is in Idempotency. Never use the current time.
RetriesFollow What to do for each status: retry 429 after Retry-After, retry 5xx and network errors with backoff only when the request is safe to repeat, stop on 402, read before deciding on 409, and never retry 400, 401, 403, 404, or 422 unchanged. The request wrapper implements this.
Timeouts30 seconds for ordinary requests. For game and simulation generation, use async mode (Prefer: respond-async); a streamed build runs for minutes and hanging up aborts it.
PollingExpansion jobs every 3 to 5 seconds, generation runs every 10 to 30 seconds, renders every 10 to 15 seconds, each with a deadline. Prefer webhooks in production.
Test modeBuild and test with a tf_content_test_ key in the sandbox classroom first; it never spends credits. Check livemode in webhook receivers. See Test Mode.
ScopesAsk for the narrowest key. On 403 content_insufficient_scope, report error.requiredScope; do not retry.
CreditsRead Pricing and check availableCredit before a batch of priced calls.
VisibilityCreate games and simulations PRIVATE.
LoggingLog keyPrefix, route, status, error.code, Request-Id, and ids. Never the key, the webhook secret, or a signed videoUrl.
ListsPage with limit (at most 100) and page until meta.hasNextPage is false. Sync with updatedSince.

What to store

ValueWhy
TutorFlow resource id and classroomIdNeeded for every later call, and proves the classroom.
The Idempotency-Key usedExplains replays, and lets you retry safely.
updatedAt last seen, and the start time of the last sync runFor syncing changes with updatedSince.
Request-Id of failed callsThe first thing TutorFlow support asks for.
Expansion job.id, outputs[].resourceId, outputs[].manifestLinks your source to the created resources.
keyPrefixSafe to share with support.

Smoke test

bash
set -euo pipefail
 
curl -sSf "$TUTORFLOW_API_BASE_URL/v1/content/credits" -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" > /dev/null
 
MODULE_ID="$(curl -sSf -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$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", "content": "<p>Created by the Content API.</p>", "metadata": { "externalId": "smoke-test" } }' \
  | jq -er '.id')"
 
curl -sSf "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/modules/$MODULE_ID" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" > /dev/null
 
curl -sSf -X PATCH "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/modules/$MODULE_ID" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Content API smoke test, updated" }' > /dev/null
 
curl -sSf -X DELETE "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/modules/$MODULE_ID" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" | jq -e '.deleted == true' > /dev/null
 
echo "smoke test passed"

Run the same create, read, update, and delete for every resource type the integration manages; the bodies are in Resources. The script fails fast on any non-2xx status and on an empty id.

Common mistakes

MistakeCorrect behavior
Calling /v1/platform/** because the task mentions automationUse /v1/content/** for content managed in TutorFlow classrooms.
Taking the first classroom from the listSelect the classroom by name or slug from configuration.
Reusing an expansion Idempotency-Key for new source contentThe key answers 409. Change it with the source version.
Reading items, totalCount, total, or success on deletes and updates, or sending takeRead data, meta.itemCount, deleted, and the resource fields, and send limit. The old names are deprecated.
Reading the module again after PATCH /modules/{id}The PATCH response already is the updated module.
Sending a full course object on PATCH to change one fieldSend only the fields you mean to change; omitted fields keep their values.
Passing a slug or an external id where a TutorFlow id goesId path parameters must be UUIDs; anything else is 422.
Treating contentKey, pdfKey, or thumbnailKey as a URLThey are opaque storage references. See Storage references and URLs.
Parsing a streamed generation response as JSONUse async mode, or read it as Server-Sent Events.
Retrying a streamed build after build-doneIt was stored and charged. Use async mode, where a retry with the same key replays.
Verifying webhooks after JSON.parseVerify the raw bytes first.
Treating 402 as a server errorStop and report it. Retrying cannot succeed until billing or the key's monthly limit changes.
Sending If-None-Match from fetch without Cache-Control: max-age=0fetch adds Cache-Control: no-cache, so the answer is always 200. Send max-age=0.
Resending the old body with currentETag after a 412Read the resource again and reapply the change.
Building a storage key, or reusing one from another classroomUpload the file and use the returned assetKey. Keys from other classrooms answer 400.
Expecting a learner route to work with a full-access keyLearner scopes are opt-in. Ask an admin to add learners:read or learners:write to the key.
Publishing a build nobody has openedKeep visibility PRIVATE until a person has checked it.

Before handing code to a customer

  1. It discovers classrooms through the API and takes the key from the environment.
  2. It uses only /v1/content/** and tf_content_ keys.
  3. Every create and POST action sends a stable Idempotency-Key.
  4. It stores TutorFlow ids next to the customer's ids.
  5. It handles 400, 401, 402, 403, 404, 409, 413, 422, and 429 as in Errors.
  6. Logs redact the key and the webhook secret.
  7. A small real request has completed before larger batches run.

Was this page helpful?