Ressourcen
Video Rendering

Video Rendering

Render a reviewed classroom video to mp4 over the Content API, follow its status, and download the result.

Auf dieser Seite

A video created through the Content API is an editable plan of scenes, narration scripts, and subtitles. It is not an mp4 yet. A new video, including an expansion's summary_video output, has no scenes until you add them, and the TutorFlow workspace lists it only once it has at least one scene. Every scene needs narration audio before it can render: generate it from each scene's script with the narration routes below, or let the render request do it. Then render with the routes below. Rendering uses the same render engine and price as the TutorFlow editor; see Pricing.

MethodPathScopeSuccess
POST/v1/content/classrooms/{classroomId}/videos/{videoId}/narrationcontent:generate200
POST/v1/content/classrooms/{classroomId}/videos/{videoId}/scenes/{sceneId}/narrationcontent:generate200
POST/v1/content/classrooms/{classroomId}/videos/{videoId}/rendercontent:generate202
GET/v1/content/classrooms/{classroomId}/videos/{videoId}/rendercontent:read200
POST/v1/content/classrooms/{classroomId}/videos/{videoId}/render/cancelcontent:generate200 with { "status": "ok" }

Generate narration

A render needs narration audio on every scene. Each scene's hasNarration field (on the video read and list) says whether it has it. Narration is spoken from the scene's script with the video's voice and speed, the same way the TutorFlow editor makes it, at 1 AI Credit per scene.

Narrate every scene that has none:

bash
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/videos/$VIDEO_ID/narration" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  -H "Idempotency-Key: video:$VIDEO_ID:narration:v1"

Response 200:

JSON
{
  "videoId": "00000000-0000-4000-8000-000000000030",
  "narratedSceneIds": ["00000000-0000-4000-8000-000000000031"],
  "creditsCharged": 1
}
  • Scenes that already have narration are kept and cost nothing. When every scene has narration the response lists none and charges 0.
  • The balance for every scene is checked before the first one is narrated, so a short balance returns 402 before any charge.
  • A scene without a script cannot be narrated: the request returns 400 content_video_scene_script_missing with the sceneIds, before any charge. Set script with the scene PATCH first.
  • After you change a scene's script, narrate that scene again with POST .../videos/{videoId}/scenes/{sceneId}/narration. It replaces the scene's narration and charges 1 AI Credit.
  • Send an Idempotency-Key so a retried request does not narrate twice.

Start a render

bash
curl -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/videos/$VIDEO_ID/render" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  -H "Idempotency-Key: video:$VIDEO_ID:render:v1" \
  -H "Content-Type: application/json" \
  -d '{ "generateMissingNarration": true }'

Response 202:

JSON
{
  "status": "accepted",
  "renderStatus": "RENDERING",
  "estimatedSeconds": 180,
  "totalDurationSeconds": 120,
  "narratedSceneIds": ["00000000-0000-4000-8000-000000000031"],
  "narrationCreditsCharged": 1
}
  • Send an Idempotency-Key. A retry with the same key replays this response instead of starting and charging a second render. Use a new key, such as video:{videoId}:render:v2, when you mean to render again.
  • A video that is already rendering returns 409 content_video_render_in_progress. Wait for it, or cancel it first to restart on purpose.
  • Every scene needs narration audio, not only a script. With "generateMissingNarration": true the request first narrates the scenes that have none (1 AI Credit each) and then renders; the balance check covers the narration and the render together. Without it, a video with such scenes returns 400 content_video_narration_missing with their sceneIds, before anything is charged. narratedSceneIds and narrationCreditsCharged report what the request narrated.
  • 402 means the organization does not have enough AI Credits.
  • A render started through the API sends no email. It reports through the video.render.completed and video.render.failed webhooks, which are also sent for renders started in TutorFlow.

Read the render status

bash
curl "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/videos/$VIDEO_ID/render" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"
JSON
{
  "renderStatus": "COMPLETED",
  "renderError": null,
  "progress": 100,
  "videoUrl": "https://cdn.tutorflow.io/orgs/.../videos/rendered/...mp4?Expires=...&Signature=...",
  "videoUrlExpiresAt": "2026-09-30T15:30:00.000Z",
  "thumbnailUrl": "https://cdn.tutorflow.io/...",
  "updatedAt": "2026-09-30T09:30:00.000Z"
}
FieldNotes
renderStatusIDLE, RENDERING, COMPLETED, or FAILED.
renderErrorOn FAILED, the fixed message "The render failed. Start it again, or contact TutorFlow support if it keeps failing." Otherwise null.
progress0 to 99 while rendering when the engine reports it, 100 when complete, otherwise null.
videoUrlA link to the mp4, usually signed. null until renderStatus is COMPLETED.
videoUrlExpiresAtWhen the signed link stops working, 6 hours after it was issued, or null when the link is not signed. Read the status again for a new link.
thumbnailUrlA link to the thumbnail when TutorFlow stores it for this classroom, otherwise null.
updatedAtWhen the video last changed.

Poll every 10 to 15 seconds while renderStatus is RENDERING, or wait for the webhook. estimatedSeconds from the start response is a reasonable first wait.

Download the mp4 promptly and store your own copy if you need it for longer than the link lasts. videoUrl is the only usable link to the rendered file; the video object itself does not return the file's storage key.

Cancel

POST .../render/cancel stops a running render and answers { "status": "ok" }. The render was charged when it started, and canceling does not refund it; see Pricing.

A render loop

bash
# curl + jq: start, then poll until the render finishes
curl -sS -X POST "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/videos/$VIDEO_ID/render" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY" \
  -H "Idempotency-Key: video:$VIDEO_ID:render:v1"
 
while true; do
  STATUS_JSON="$(curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/classrooms/$CLASSROOM_ID/videos/$VIDEO_ID/render" \
    -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY")"
  RENDER_STATUS="$(printf '%s' "$STATUS_JSON" | jq -r '.renderStatus')"
  echo "renderStatus: $RENDER_STATUS"
  if [ "$RENDER_STATUS" != "RENDERING" ]; then break; fi
  sleep 15
done
 
printf '%s\n' "$STATUS_JSON" | jq '{renderStatus, videoUrl, videoUrlExpiresAt}'

The same loop in Node.js and Python is in Examples.

War diese Seite hilfreich?