Skip to main content

Courses

A course turns one source - a document or an existing video - into a set of modules, each of which becomes exactly one video.

The pipeline runs in three steps, and each is asynchronous:

  1. Create → the source is extracted and split into modules
  2. Storyboard → a module gets the scenes its video will be made of
  3. Generate → a module is rendered into a video

Every step returns a jobId. Poll GET /v1/jobs/:id exactly as you would for any other async operation.

Create a course

POST /v1/courses

Give either a fileUrl + filename, or a sourceVideoId - not both.

FieldTypeDescription
fileUrlstringPublic URL of the document (PDF, DOCX, PPTX, Keynote, TXT)
filenamestringRequired with fileUrl - its extension decides which extractor runs
sourceVideoIdstringBuild from an existing video instead of a document
instructionsstringGuidance for the planner: audience, tone, what to emphasise
curl -X POST https://api.vidocu.ai/v1/courses \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"fileUrl": "https://example.com/onboarding-handbook.pdf",
"filename": "onboarding-handbook.pdf",
"instructions": "For new warehouse staff. Keep each module under two minutes."
}'

Response 202 Accepted

{ "courseId": "dvp_1786480000000_a1b2c3d4", "jobId": "doc_plan_...", "status": "extracting" }

When that job completes, GET /v1/courses/:id shows the modules the planner proposed.

The document is fetched by our servers, so it must be publicly reachable - internal and private addresses are refused.

List and read

GET /v1/courses · GET /v1/courses/:id

{
"id": "dvp_...",
"title": "Onboarding handbook",
"status": "plan_ready",
"source": { "filename": "onboarding-handbook.pdf", "kind": "pdf", "videoId": null },
"moduleCount": 4,
"modules": [
{
"id": "dm_...",
"title": "Receiving a delivery",
"objective": "Check in a pallet against its work order",
"status": "ready_to_generate",
"sourcePages": [3, 4, 5],
"targetDurationSec": 95,
"sceneCount": 7,
"videoId": null
}
]
}

GET /v1/courses supports status, page/limit and cursor.

Storyboard

GET /v1/courses/:id/storyboard returns every module's scenes, plus two fields worth reading:

  • omissions - source content the storyboard deliberately left out. Reviewable rather than silent, so you can tell "it missed this" from "it chose to skip this".
  • warnings - non-blocking review hints.

POST /v1/courses/:id/storyboard builds one module's storyboard:

curl -X POST https://api.vidocu.ai/v1/courses/dvp_.../storyboard \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"moduleId": "dm_..."}'

Returns 409 if that module is already being worked on - only one step runs per module at a time.

Generate

POST /v1/courses/:id/generate

Renders one module into a video. It needs a storyboard first. Same body, same 409 rule.

When the job completes, the module's videoId points at an ordinary video - so export, translate and everything else work on it normally.

Update and delete

PATCH /v1/courses/:id changes title, userInstructions, or projectId (the folder its videos are filed under). Modules and status are owned by the pipeline and can't be set by hand - editing them would desync a running job.

DELETE /v1/courses/:id removes the course plan. Videos already generated from its modules are kept - they're ordinary videos with their own lifecycle. The response tells you how many:

{ "success": true, "id": "dvp_...", "deleted": true, "generatedVideosKept": 2 }

Errors

StatusCodeCause
400validation_errorMissing or conflicting source, or a bad moduleId
401authentication_errorInvalid or missing API key
403insufficient_scopeToken lacks courses:read / courses:write
404not_foundCourse or module not found, or it belongs to another workspace
409conflictA step is already running for that module