Resources
Video Rendering

Video Rendering

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

On this page

A video created through the Content API, or by expansion's summary_video output, is an editable plan of scenes, narration scripts, and subtitles. It is not an mp4 yet. After a person has reviewed it in the TutorFlow video editor and generated its narration, render it 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}/rendercontent:generate202
GET/v1/content/classrooms/{classroomId}/videos/{videoId}/rendercontent:read200
POST/v1/content/classrooms/{classroomId}/videos/{videoId}/render/cancelcontent:generate200 with { "status": "ok" }

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"

Response 202:

JSON
{
  "status": "accepted",
  "renderStatus": "RENDERING",
  "estimatedSeconds": 180,
  "totalDurationSeconds": 120
}
  • 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 generated narration audio, not only a script. Narration audio is generated in the TutorFlow video editor; the Content API does not generate it. A video with a scene that has no narration audio returns 400 before anything is charged, so have a person open the video in the editor, generate narration, and review it before you render.
  • 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.

Was this page helpful?