These examples show the full path from key creation to result retrieval. Resource calls require a classroom id, discovered with the Content API key after key creation.
Before running the examples
The curl examples use jq and an admin session cookie. If your admin account uses email and password sign-in, create the cookie jar first:
set -euo pipefail
export TUTORFLOW_API_BASE_URL="https://api.tutorflow.io"
curl -c tutorflow-admin.cookies -X POST "$TUTORFLOW_API_BASE_URL/auth/login" \
-H "Content-Type: application/json" \
-H "Referer: https://tutorflow.io/sign-in" \
-d '{
"email": "admin@example.com",
"password": "your-password",
"timezone": "Asia/Seoul"
}'If your organization uses SSO or OAuth sign-in, create the key from an authenticated TutorFlow admin environment instead of using the password-login example.
curl: find organization ID
curl "$TUTORFLOW_API_BASE_URL/v1/content/organizations" \
-b tutorflow-admin.cookies
export TUTORFLOW_CONTENT_ORG_ID="$(curl -s "$TUTORFLOW_API_BASE_URL/v1/content/organizations" \
-b tutorflow-admin.cookies | jq -r '.[0].id')"curl: create key
KEY_RESPONSE="$(curl -s -X POST "$TUTORFLOW_API_BASE_URL/v1/content/organizations/$TUTORFLOW_CONTENT_ORG_ID/api-keys" \
-H "Content-Type: application/json" \
-b tutorflow-admin.cookies \
-d '{"name":"external-content-test","rateLimitPerMinute":60}')"
export TUTORFLOW_CONTENT_API_KEY="$(printf '%s' "$KEY_RESPONSE" | jq -r '.apiKey')"
export TUTORFLOW_CONTENT_KEY_PREFIX="$(printf '%s' "$KEY_RESPONSE" | jq -r '.keyPrefix')"curl: find classroom ID
curl "$TUTORFLOW_API_BASE_URL/v1/content/classrooms" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"
export TUTORFLOW_CLASSROOM_ID="$(curl -s "$TUTORFLOW_API_BASE_URL/v1/content/classrooms" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" | jq -r '.[0].id')"curl: create expansion
cat > source-content.json <<JSON
{
"classroomId": "$TUTORFLOW_CLASSROOM_ID",
"requestedOutputs": ["interactive_module", "summary_video", "expanded_quiz"],
"payload": {
"category": {
"id": "language-basics",
"title": "Language Basics"
},
"language": "en",
"level": {
"id": "level-a1",
"title": "A1 Foundations",
"lessons": [
{
"id": "lesson-vocabulary-1",
"type": "vocabulary",
"title": "Basic greetings",
"items": [
{
"term": "hello",
"meaning": "a greeting"
}
]
}
]
}
}
}
JSON
JOB_RESPONSE="$(curl -s -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: language-basics-level-a1-2026-07-04" \
--data-binary @source-content.json)"
export JOB_ID="$(printf '%s' "$JOB_RESPONSE" | jq -r '.id')"curl: poll and retrieve
curl "$TUTORFLOW_API_BASE_URL/v1/content/integrations/expansions/$JOB_ID" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"
curl "$TUTORFLOW_API_BASE_URL/v1/content/integrations/expansions/$JOB_ID/result" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"Node.js: create expansion
const tutorFlowApiBaseUrl =
process.env.TUTORFLOW_API_BASE_URL ?? 'https://api.tutorflow.io'
const response = await fetch(
`${tutorFlowApiBaseUrl}/v1/content/integrations/expansions`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TUTORFLOW_CONTENT_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'language-basics-level-a1-2026-07-04',
},
body: JSON.stringify({
classroomId: process.env.TUTORFLOW_CLASSROOM_ID,
requestedOutputs: ['interactive_module', 'summary_video', 'expanded_quiz'],
payload: {
category: { id: 'language-basics', title: 'Language Basics' },
language: 'en',
level: {
id: 'level-a1',
title: 'A1 Foundations',
lessons: [
{
id: 'lesson-vocabulary-1',
type: 'vocabulary',
title: 'Basic greetings',
items: [{ term: 'hello', meaning: 'a greeting' }],
},
],
},
},
}),
},
)
if (!response.ok) {
throw new Error(await response.text())
}
const job = await response.json()Node.js: polling helper
async function waitForContentJob(jobId) {
const tutorFlowApiBaseUrl =
process.env.TUTORFLOW_API_BASE_URL ?? 'https://api.tutorflow.io'
for (let attempt = 0; attempt < 60; attempt++) {
const response = await fetch(
`${tutorFlowApiBaseUrl}/v1/content/integrations/expansions/${jobId}`,
{
headers: {
Authorization: `Bearer ${process.env.TUTORFLOW_CONTENT_API_KEY}`,
},
},
)
if (!response.ok) {
throw new Error(await response.text())
}
const job = await response.json()
if (job.status === 'completed' || job.status === 'failed') {
return job
}
await new Promise((resolve) => setTimeout(resolve, 3000))
}
throw new Error('Content job did not finish within the polling window')
}Python: create expansion
import os
import requests
api_base_url = os.environ.get("TUTORFLOW_API_BASE_URL", "https://api.tutorflow.io")
response = requests.post(
f"{api_base_url}/v1/content/integrations/expansions",
headers={
"Authorization": f"Bearer {os.environ['TUTORFLOW_CONTENT_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": "language-basics-level-a1-2026-07-04",
},
json={
"classroomId": os.environ["TUTORFLOW_CLASSROOM_ID"],
"requestedOutputs": ["interactive_module", "summary_video", "expanded_quiz"],
"payload": {
"category": {"id": "language-basics", "title": "Language Basics"},
"language": "en",
"level": {
"id": "level-a1",
"title": "A1 Foundations",
"lessons": [
{
"id": "lesson-vocabulary-1",
"type": "vocabulary",
"title": "Basic greetings",
"items": [{"term": "hello", "meaning": "a greeting"}],
}
],
},
},
},
timeout=30,
)
response.raise_for_status()
job = response.json()Manifest storage shape
{
"sourceContentId": "language-basics:level-a1",
"tutorFlowJobId": "3f9440c4-7b15-48d7-a02f-4c1c50a8c3e1",
"outputType": "interactive_module",
"status": "completed",
"resourceType": "module",
"resourceId": "module-request-id",
"manifest": {}
}