Resources
Content Integration Testing Guide

Content Integration Testing Guide

End-to-end steps for testing Content Integration from key request to result storage.

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.json

Expected 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:

StatusMeaningClient action
queuedThe request was accepted.Poll again after 2 to 5 seconds.
processingTutorFlow is creating outputs.Keep polling the same job id.
completedAll requested outputs finished.Call the result endpoint.
failedThe 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:

FieldWhy it matters
job.idReconciliation key for TutorFlow support and retries.
job.statusTerminal state for the content group.
outputs[].outputTypeDistinguishes module, video, and quiz outputs.
outputs[].statusPer-output success or failure.
outputs[].resourceTypeTarget content type represented by the manifest.
outputs[].resourceIdTutorFlow content id when one is available.
outputs[].manifestData to reinsert, review, or sync downstream.

Minimum success criteria:

  1. job.status is completed.
  2. Every requested outputType appears once.
  3. The interactive module output has a completed manifest.
  4. The summary video output returns editable video structure before MP4 rendering.
  5. 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:

EventTrigger
content.completedA Content Integration job reached completed.
content.failedA Content Integration job reached failed.

Webhook requests include:

X-Content-Integration-Event: content.completed
X-Content-Integration-Signature: hmac_sha256_signature

The receiver should verify the signature, return a 2xx response quickly, and fetch the result endpoint if it needs the full manifest.

Troubleshooting

SymptomLikely causeFix
401Missing key or wrong key format.Use a tf_content_ key in the Authorization header.
400Payload has no level or no lessons.Send one level or content group with at least one lesson.
Same job returned after retrySame idempotency key.This is expected. Change the key only when the source payload changes.
Output has failed statusOne generated output failed.Inspect the output error, adjust the source content, and create a new job.
Video is not an MP4Content Integration returns editable video structure first.Trigger rendering separately after review.

Pilot handoff

At the end of the first pilot, share:

  1. The submitted source JSON.
  2. The idempotency key.
  3. The returned job id.
  4. The final result manifest.
  5. Any output review notes.
  6. Expected production request volume.