Resources
Content API Quickstart

Content API Quickstart

Create a Content API key, make your first call, create a module, and optionally expand source JSON, in about 5 minutes.

On this page

This quickstart takes you from no key to a module in your classroom. You need a TutorFlow admin account for the organization, curl, and jq.

1. Create a key

  1. In TutorFlow, open Settings > Content API (/dashboard/settings/content-api).
  2. Create a key named, for example, quickstart, and choose Test under Mode (the default is Live). A test key works only in a private sandbox classroom, answers generation and renders with canned content, and never spends AI Credits or touches real classrooms, so it is the recommended way to start. See Test Mode.
  3. Choose scopes. For this quickstart pick content:read and content:write. Add content:generate if you want to try the optional expansion in step 5.
  4. Copy the key. A test key starts with tf_content_test_, a live key with tf_content_. It is shown only once.

Scripting key creation instead? See Create a key with the API.

Set it in your shell:

bash
export TUTORFLOW_API_BASE_URL="https://api.tutorflow.io"
export TUTORFLOW_CONTENT_API_KEY="tf_content_..."   # paste your key

2. Make your first call

bash
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/credits" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" | jq
JSON
{
  "credit": 500,
  "overdraftCredit": 0,
  "overdraftLimit": 100,
  "availableCredit": 500,
  "isPaymentFailed": false,
  "livemode": true
}

A 200 means the key works. With a live key, the balance is the organization's real balance. A test key sees a fixed sandbox balance with livemode: false instead, and never changes the real one. A 401 means the key is missing, mistyped, revoked, or expired.

3. Pick a classroom

bash
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/classrooms" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" | jq
JSON
[
  {
    "id": "00000000-0000-4000-8000-000000000010",
    "name": "API sandbox",
    "slug": "api-sandbox-1a2b3c4d",
    "locale": "en",
    "isSandbox": true
  }
]

A test key lists only the sandbox classroom. A live key lists the organization's classrooms, or those in its allowlist, with isSandbox: false. Copy the id of the classroom you want:

bash
export CLASSROOM_ID="00000000-0000-4000-8000-000000000010"   # your classroom id

4. Create a module

bash
MODULE_JSON="$(curl -sS -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:quickstart-safety:v1" \
  -d '{
    "title": "Introduction to safety",
    "description": "A short interactive lesson for new employees.",
    "type": "markdown",
    "content": "<h2>Safety first</h2><p>Report every hazard you see.</p>",
    "metadata": { "externalId": "quickstart-safety" }
  }')"
 
printf '%s\n' "$MODULE_JSON" | jq '{id, classroomId, title, createdAt}'
export MODULE_ID="$(printf '%s' "$MODULE_JSON" | jq -r '.id')"

The response is 201 with the module. Open the classroom in TutorFlow and the module is there; with a test key, it is in the API sandbox. Running the same command again with the same Idempotency-Key returns the same module instead of creating a second one.

That is a complete integration loop: authenticate, find a classroom, write content, and read back its id. Creating and updating content you supply costs no credits.

5. Optional: expand source JSON

Expansion turns your own lesson JSON into a module, a video plan, and a quiz. It needs content:generate and is free with any key: the outputs are editable drafts, created without calling a model. This request asks for the module output only.

bash
JOB_JSON="$(curl -sS -X POST "$TUTORFLOW_API_BASE_URL/v1/content/integrations/expansions" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: expansion:language-basics-a1:v1" \
  -d "$(jq -n --arg classroomId "$CLASSROOM_ID" '{
    classroomId: $classroomId,
    requestedOutputs: ["interactive_module"],
    payload: {
      language: "en",
      category: { id: "language-basics", title: "Language Basics" },
      level: {
        id: "level-a1",
        title: "A1 Foundations",
        lessons: [
          {
            id: "lesson-greetings",
            type: "vocabulary",
            title: "Basic greetings",
            items: [{ term: "hello", meaning: "a greeting", example: "Hello, Mina." }]
          }
        ]
      }
    }
  }')")"
 
export JOB_ID="$(printf '%s' "$JOB_JSON" | jq -r '.id')"
echo "job: $JOB_ID"

Poll until the job finishes. The loop waits longer when the API answers 429:

bash
while true; do
  RESPONSE="$(curl -sS -w '\n%{http_code}' "$TUTORFLOW_API_BASE_URL/v1/content/integrations/expansions/$JOB_ID" \
    -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY")"
  HTTP_STATUS="$(printf '%s' "$RESPONSE" | tail -n 1)"
  BODY="$(printf '%s' "$RESPONSE" | sed '$d')"
 
  if [ "$HTTP_STATUS" = "429" ]; then sleep 30; continue; fi
 
  JOB_STATUS="$(printf '%s' "$BODY" | jq -r '.status')"
  echo "status: $JOB_STATUS"
  if [ "$JOB_STATUS" = "completed" ] || [ "$JOB_STATUS" = "failed" ]; then break; fi
  sleep 5
done
 
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/integrations/expansions/$JOB_ID/result" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  | jq '.outputs[] | {outputType, status, resourceType, resourceId}'

The resourceId of the interactive_module output is a module id you can read with GET /modules/{moduleId}.

Next steps

Was this page helpful?