Use this guide for the first technical test with an external content system. It covers key creation, source JSON submission, polling, result retrieval, UI-authored content export, and the minimum checks before production automation.
1. Create a test key
Create a Content Integration API key while signed in as a TutorFlow admin.
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/organizations/$TUTORFLOW_ORGANIZATION_ID/api-keys" \
-H "Content-Type: application/json" \
-H "Cookie: tutorflow_admin_session=..." \
-d '{
"name": "external-content-test",
"rateLimitPerMinute": 60
}'The response includes an apiKey that starts with tf_content_. Store the full value as a secret. Share only the key prefix in support messages.
export TUTORFLOW_CONTENT_API_KEY="tf_content_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TUTORFLOW_API_BASE_URL="https://api.tutorflow.io"
export TUTORFLOW_ORGANIZATION_ID="00000000-0000-4000-8000-000000000001"2. Prepare one small payload
Start with one category and one level, or one similarly small content group. Keep the customer's current lesson fields and include answer keys when they exist.
Save a test file named source-content.json:
{
"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 used when meeting someone",
"example": "Hello, Mina."
}
]
},
{
"id": "lesson-check-1",
"type": "quiz",
"title": "Greeting check",
"questions": [
{
"question": "Which phrase is a greeting?",
"choices": ["Hello", "Blue", "Desk"],
"answer": "Hello",
"explanation": "Hello is used to greet someone."
}
]
}
]
}
}
}3. Create an expansion job
Use a stable idempotency key for each source content group. If the same request is retried with the same key, TutorFlow returns the existing job instead of creating a duplicate.
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: language-basics-level-a1-2026-07-04" \
--data-binary @source-content.jsonExpected response:
{
"id": "3f9440c4-7b15-48d7-a02f-4c1c50a8c3e1",
"sourceType": "source_json",
"status": "queued",
"sourceTitle": "Language Basics, A1 Foundations",
"requestedOutputs": ["interactive_module", "summary_video", "expanded_quiz"],
"outputs": [
{
"outputType": "interactive_module",
"status": "queued",
"manifest": null
}
]
}Store the returned id as the TutorFlow job id.
4. Poll until terminal status
JOB_ID="3f9440c4-7b15-48d7-a02f-4c1c50a8c3e1"
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/integrations/expansions/$JOB_ID" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"Status values:
| Status | Meaning | Client action |
|---|---|---|
queued | The request was accepted. | Poll again after 2 to 5 seconds. |
processing | TutorFlow is creating outputs. | Keep polling the same job id. |
completed | All requested outputs finished. | Call the result endpoint. |
failed | The job cannot finish. | Read error and fix the payload or requested outputs. |
Do not create a second job while the first job is still queued or processing.
5. Retrieve the result
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/integrations/expansions/$JOB_ID/result" \
-H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"Store these fields in the external system:
| Field | Why it matters |
|---|---|
job.id | Reconciliation key for TutorFlow support and retries. |
job.status | Terminal state for the content group. |
outputs[].outputType | Distinguishes module, video, and quiz outputs. |
outputs[].status | Per-output success or failure. |
outputs[].resourceType | Target content type represented by the manifest. |
outputs[].resourceId | TutorFlow content id when one is available. |
outputs[].manifest | Data to reinsert, review, or sync downstream. |
Minimum success criteria:
job.statusiscompleted.- Every requested
outputTypeappears once. - The interactive module output has a completed manifest.
- The summary video output returns editable video structure before MP4 rendering.
- The expanded quiz output preserves source answer intent.
6. Test UI-authored export
Use this only after signing in to the TutorFlow admin UI. This endpoint exports content already authored in TutorFlow, so it does not use the Content Integration API key.
curl -sS -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/{classroomId}/integrations/exports" \
-H "Content-Type: application/json" \
-H "Cookie: tutorflow_admin_session=..." \
-d '{
"lessonId": "standalone-lesson-id",
"idempotencyKey": "standalone-lesson:standalone-lesson-id"
}'Store the returned manifest the same way as expansion results. Use this flow when TutorFlow UI content needs to be synchronized into another system.
7. Add webhooks after polling works
Use polling for the first pilot. Add webhooks after job creation, polling, and result storage are verified.
Supported events:
| Event | Trigger |
|---|---|
content.completed | A Content Integration job reached completed. |
content.failed | A Content Integration job reached failed. |
Webhook requests include:
X-Content-Integration-Event: content.completed
X-Content-Integration-Signature: hmac_sha256_signatureThe receiver should verify the signature, return a 2xx response quickly, and fetch the result endpoint if it needs the full manifest.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
401 | Missing key or wrong key format. | Use a tf_content_ key in the Authorization header. |
400 | Payload has no level or no lessons. | Send one level or content group with at least one lesson. |
| Same job returned after retry | Same idempotency key. | This is expected. Change the key only when the source payload changes. |
Output has failed status | One generated output failed. | Inspect the output error, adjust the source content, and create a new job. |
| Video is not an MP4 | Content Integration returns editable video structure first. | Trigger rendering separately after review. |
Pilot handoff
At the end of the first pilot, share:
- The submitted source JSON.
- The idempotency key.
- The returned job id.
- The final result manifest.
- Any output review notes.
- Expected production request volume.