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 (or build a module in one call with POST /v1/courses/:id/build):

  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 becomes a video made of editable slides

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
pacestringHow modules are written and cut: snackable, course (default) or inDepth. See Pace
aiVideoobject{ "enabled": true, "stylePresetId": "documentary" }: AI pictures and clips instead of stock photos. Business and Enterprise only. See AI video
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.

Pace​

pace decides how long modules are, how fast scenes change and how the narration sounds.

ValueFeels likeModulesScenesVoice and captions
snackableShort-form social video1-3 min, more of them4-9 s each, a hook in the first 3 sEnergetic creator voice, big bold captions
courseOnline course (default)3-6 min8-15 s eachFriendly explainer
inDepthIn-depth training5-12 min15-30 s each, ends with a key-rules checklistCalm and precise

A snackable course never drops content to stay short: it gets more, shorter modules instead. Safety warnings and procedures are always kept.

AI video​

With aiVideo.enabled, scenes that the source has no picture for get an AI-generated still instead of a stock photo, and a few of them a short AI clip that loops behind the narration. Your document's own images and your recording's own frames still come first.

  • Plans: Business and Enterprise, trials included. Other plans get 403 limit_exceeded.
  • Style: stylePresetId is one of the AI footage presets. documentary (default), cinematic, cartoon3d, flat2d, storybook and brand are the ones the dashboard offers.
  • Clips are for context only: an opening hook, why it matters, the workplace. A procedure step is never shown with an AI picture, because AI pictures can get those details wrong.
  • Clips per module: up to 4 (snackable), 2 (course) or 1 (inDepth).
  • Credits: each picture is charged as ai_footage_still, each clip as ai_footage_clip, on success only, on top of the build (doc_video_generate, 50 credits per minute of video). A typical course-pace module (about 4 minutes) costs about 214 credits, or 262 with AI video. Generate checks the most a module can cost before it starts and returns 403 limit_exceeded if the balance can't cover it.
  • Failures don't fail the module: a picture that can't be made becomes a text slide, and a clip that can't be made shows its still. Both are listed in the module's warnings.
  • Marking: exported videos with AI pictures say so in the file's metadata (IPTC compositeWithTrainedAlgorithmicMedia).

In the storyboard, a scene's aiVisual is the picture it asked for (kind is still or clip, shot describes it). After generate, imageUrl holds the still and aiClipUrl the clip.

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 },
"pace": "course",
"aiVideo": { "enabled": false, "stylePresetId": 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.

Build a module in one step​

POST /v1/courses/:id/build

The dashboard's Create video button: storyboard (when the module has none yet), AI visuals when the course has AI video on, narration and slides, in one call.

curl -X POST https://api.vidocu.ai/v1/courses/dvp_.../build \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"moduleId": "dm_..."}'
{ "jobId": "course_module_...", "videoId": "3f1c...", "projectId": "...", "async": true }

The video exists right away (status in_progress) and fills in as the job runs. A module that is already ready is rebuilt, and its old video is replaced. Returns 409 while the module is being built, and 403 limit_exceeded when the balance can't cover the build.

The two steps below remain for callers that want to review or edit the storyboard before generating.

Generate​

POST /v1/courses/:id/generate

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

The video is made of slides, one per storyboard scene, each with its own voiceover and word timings - nothing is rendered until export, so the layout, text, picture and narration of every scene stay editable in the dashboard's video editor afterwards. When the job completes, the module's videoId points at an ordinary video - so export, translate and everything else work on it normally.

Who can take it, and what counts as finishing​

A course is shared as one link. PATCH /v1/courses/:id with a settings object decides what happens when somebody opens it.

{
"settings": {
"access": {
"identity": "email_verified",
"openEnrollment": false,
"allowedEmailDomains": ["company.com"],
"allowRoster": true
},
"completion": { "rule": "watch_quiz", "watchThresholdPct": 90 },
"trackProgress": true
}
}

identity is what a learner has to give before the course opens:

ValueWhat it means
anonymousNo gate. Progress is counted per browser - a visitor count, not a training record
nameA name, typed in and not checked
email_nameA name and an email, typed in and not checked
email_verifiedAn email the learner proves by opening a link we send them
authA Vidocu account

Only email_verified and auth produce records you can stand behind. The other three are completion rates.

The audience rules are additive - an address gets in if any of them admits it: openEnrollment (anyone who identifies), a matching entry in allowedEmailDomains, membership of invitedEmails, or allowRoster plus a place on the workspace's training roster. accessCode is separate: a shared code checked at the door, before anybody is asked who they are. Send "" or null to clear it. It is stored hashed and never returned.

completion.rule decides when a module is done: watch (past watchThresholdPct of its runtime), watch_quiz (that, and a passing attempt at the module's quiz - modules without a quiz fall back to watch), or manual (the learner presses a button). Watched time only accrues while the video is playing, so seeking to the end does not complete a module.

completion.sequential locks every module past the first unfinished one. It is enforced on the learner page, on the per-module payload and on the progress endpoint, so skipping ahead is refused whether it is attempted by clicking or by calling the API. A workspace member previewing their own course is exempt.

Settings are merged. A patch naming completion.rule leaves the invite list alone.

Making the link reachable at all is still the share setting: a private course is workspace members only, whatever access says.

Course progress​

GET /v1/courses/:id/progress

{
"courseId": "dvp_...",
"moduleCount": 4,
"tracking": true,
"completionRule": "watch_quiz",
"totals": { "people": 31, "started": 24, "completed": 18, "completionRate": 58, "avgCompletedModules": 3.1 },
"modules": [
{ "moduleId": "dmod_...", "title": "Lockout procedure", "started": 24, "completed": 21,
"completionRate": 88, "avgWatchedPct": 94, "hasQuiz": true, "quizAttempts": 29,
"quizPassed": 21, "quizPassRate": 72 }
],
"people": [
{ "personKey": "[email protected]", "email": "[email protected]", "name": "Anna",
"source": "progress", "anonymous": false, "completedCount": 4, "completedAll": true,
"startedAt": "2026-09-01T09:12:00.000Z", "lastSeenAt": "2026-09-03T14:40:00.000Z",
"cells": [{ "moduleId": "dmod_...", "status": "completed", "watchedPct": 100 }] }
]
}

Alongside those it returns funnel (per module, in course order: reached, completed and dropFromPrevious), completionsByWeek (the last eight weeks), neverStarted, and medianTimeToCompleteSec with the timeToCompleteSample it is drawn from. The median is deliberate: one learner who opens a course in March and finishes in September would make a mean meaningless.

reached counts anyone who opened a module. Unless the course unlocks in order it is not a strict funnel — learners can take modules in any order, so dropFromPrevious is a signal worth chasing, not a proof.

people is a union of everyone with progress, everyone on invitedEmails, and - when allowRoster is on - the training roster. Somebody who was invited and never pressed play appears with source: "invited" and every cell not_started, because "who has not done this yet" is the question the endpoint exists to answer.

A learner who identified with a verified email also joins the workspace training roster, so they show up in the training planner alongside quiz trainings. Finishing a course does not create a qualification - that stays a trainer sign-off.

tracking: false means the course records nothing; people will be empty.

Update and delete​

PATCH /v1/courses/:id changes title, userInstructions, projectId (the folder its videos are filed under), pace and aiVideo (used the next time each module is generated), and the settings described above. 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
403limit_exceededNot enough credits, or aiVideo on a plan without it
404not_foundCourse or module not found, or it belongs to another workspace
409conflictA step is already running for that module