AI Recorder
Describe a flow in plain words and an agent drives a real browser to produce a screen recording of it. No screen capture, no narration script, no editing.
The AI Recorder is included on every paid plan. On a free plan, starting a recording returns
403.
Start a recording
POST /v1/recordings
curl -X POST https://api.vidocu.ai/v1/recordings \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Show how to invite a teammate and set their role to Editor",
"url": "https://app.example.com/settings/team",
"device": "desktop"
}'
{ "jobId": "video_recording_1786093699160_motd1r", "status": "pending" }
Returns 202. Recordings take minutes, not seconds - poll the job.
Two recordings run per workspace at a time; a third start returns 429. Recording browsers are a
shared pool, so a job may also sit queued briefly before it starts - progress.step reports
queued while it waits.
| Field | Type | Description |
|---|---|---|
prompt | string | What the recording should show, up to 50,000 characters. Required |
name | string | Display name in lists, up to 200 characters. Absent = the prompt is shown. Same field PATCH renames |
url | string | Where to start |
device | desktop | mobile | Viewport to record in |
pacing | relaxed | standard | brisk | Replay speed of the finished video |
maxDurationSec | number | Max length of the finished video in seconds, up to 1800. 0 or absent means no limit |
removeLoading | boolean | Cut the time spent waiting for pages to load: the first load, navigations, and loads after a click. The video jumps straight to each loaded screen. Default false |
mode | regular | follow | steps | steps drives an explicit list instead of planning |
steps | array | {instruction, url?} - required when mode is steps |
sessionId | string | The saved login to start signed in with, by id from GET /v1/recorder-sessions. Omit it to use the site's default session |
A 422 means the recorder read the prompt and judged it not actionable. The response carries
rejected: true and usually a suggestion for a rewrite - show it rather than retrying blindly.
Attachments and file uploads are dashboard-only; the API takes JSON.
With removeLoading, Vidocu cuts the waits the recorder itself sees, from the moment a page
starts loading until its network goes quiet. A spinner that keeps turning after the network has
gone quiet stays in the video. Waits longer than 6 seconds are sped up into a short time-lapse
either way.
Check on it
GET /v1/recordings/:jobId
{
"jobId": "video_recording_...",
"status": "completed",
"aborted": false,
"partial": true,
"partialReason": "The export dialog needed a paid plan, so the last two steps aren't shown.",
"duration": "547s",
"completedAt": "2026-08-12T21:04:00.000Z"
}
Two failure shapes matter, and they are not the same thing:
aborted- the agent stopped and there is no usable video. A paywall, a missing permission, a UI that doesn't exist.errorholds its reason.partial- a video exists, but it doesn't show the whole flow. The steps that worked were recorded rather than thrown away.partialReasonsays what's missing.
Never present a partial recording as a finished tutorial. status is completed in both
cases; the flags are what tell them apart.
duration is the agent's wall-clock runtime, not the video's playback length.
When the agent gets stuck on a value
POST /v1/recordings/:jobId/input with {"value": "..."}
Some flows pause for something only a person has - a one-time code, a 2FA challenge, a security answer. The job waits, and this hands the value over.
Ask a human for it. Don't guess, and don't forward a credential that wasn't given for this purpose.
Get the file
GET /v1/recordings/:jobId/download
{ "downloadUrl": "https://...", "expiresIn": 3600 }
A time-limited URL, not the bytes - fetch it directly. 409 if the job hasn't completed.
List, rename and delete
GET /v1/recordings - cursor-paginated, filterable by
status.
PATCH /v1/recordings/:jobId with {"name": "Create a project folder"} gives a recording a
display name. Every recording response carries name (null when unset), and the dashboard's
recordings list shows it instead of the prompt. Send null or "" to clear it. Up to 200
characters. The prompt never changes. Needs recordings:write and a role that can edit.
DELETE /v1/recordings/:jobId removes the job from the list. A video already imported from it is
a separate object and is untouched.
Saved logins
GET /v1/recorder-sessions · PATCH /v1/recorder-sessions/:session ·
DELETE /v1/recorder-sessions/:session
To record a site behind a login, the recorder can keep the session and reuse it next time. Sessions expire on their own.
A site can have several saved sessions, for example "Sandbox" and "Admin user". Each one has:
| Field | Description |
|---|---|
id | Use it in the path below and as sessionId when you start a recording |
name | A label. Sessions saved before names existed show their domain |
isDefault | Exactly one session per site is the default. A recording that names no sessionId uses it |
createdBy | User id of whoever saved it. null for older sessions |
context | Who this login is and what it sees. null when unset. See Account context |
The stored cookies never leave Vidocu. The API reports that a session exists, when it expires, how long it is kept, its setup instructions and its context:
{
"sessions": [
{
"id": "6703a1f4c2e9b81d4f0a7c21",
"domain": "app.example.com",
"name": "Sandbox",
"isDefault": true,
"createdBy": "64f0c2a9e1b7d3a5c8f91e02",
"setupPrompt": "Switch to the Acme workspace and close the welcome banner",
"context": "Teacher account: manages classes, assigns lessons, sees grades",
"ttlDays": 30,
"createdAt": "2026-08-01T09:12:00.000Z",
"updatedAt": "2026-08-20T14:03:00.000Z",
"expiresAt": "2026-09-19T14:03:00.000Z"
}
]
}
Sessions are listed most recently used first. updatedAt is when the login was last refreshed.
Pick a session
:session in the path is a session id. A domain still works too, and means that site's
default session.
To record with a session that isn't the default, pass its id as sessionId to
POST /v1/recordings. If that session no longer exists, the request returns 400. Nothing
switches to another session behind your back.
Rename it, or make it the default
PATCH /v1/recorder-sessions/:session with { "name": "Admin user" } renames it. Up to 80
characters. "" resets it to the domain.
{ "isDefault": true } makes it the site's default. Only true is accepted: a site always has a
default, so to change it, make another session the default.
Send any mix of name, isDefault, setupPrompt, context and ttlDays, at least one. A field
you leave out stays as it is. Returns the updated session (id, domain, name, isDefault,
createdBy, setupPrompt, context, ttlDays, updatedAt, expiresAt), or 404 when there is no such session.
Requires recordings:write and a role that can edit.
Delete a session
DELETE /v1/recorder-sessions/:session returns { "deleted": true, "id": "...", "domain": "..." }.
Requires recordings:write and a role that can delete.
By domain, it only works when the site has one session. With several, it returns 409 and lists
them in sessions (id, name, isDefault), so delete by id instead. That way you never
delete one you didn't pick.
Delete the default, and the most recently used remaining session for that site becomes the new default. Delete the last one, and recordings on that site have to sign in again.
Setup instructions
setupPrompt is a short set of steps the recorder performs on that site before every recording
and screenshot, off camera - switching to the right workspace, dismissing a welcome banner,
opening the demo account. It is part of the session, so it applies to recordings started from the
dashboard, the API, and Pulse screenshots alike. null when unset.
PATCH /v1/recorder-sessions/:session with { "setupPrompt": "..." } sets it; { "setupPrompt": null } clears it. Up to 2000 characters. Each session has its own, so "Sandbox" and "Admin user"
on the same site can start from different places.
A recording request that carries its own setupPrompt wins over the session's for that one job;
the session's is not changed.
Account context
context says who the login is and what it can see and do. Useful when one product has very
different logins, for example a student and a teacher:
{ "context": "Teacher account: manages classes, assigns lessons, sees grades" }
It only describes the account. The recorder never performs it as steps (that is what
setupPrompt is for). Pulse uses it to write docs for the right audience, and a recording that
uses the session reads it as background about the account.
PATCH /v1/recorder-sessions/:session with { "context": "..." } sets it; { "context": null }
clears it. Up to 2000 characters. Signing in again, or a recording refreshing the login, never
changes it. null when unset.
How long a session is kept
ttlDays is how many days a session is kept after it was last used. Every recording or screenshot
that uses the session pushes expiresAt forward again. The default is 14. You can set it to 30,
90, 180 or 365.
PATCH /v1/recorder-sessions/:session with { "ttlDays": 90 } changes it. You can send it alone or
with other fields, and a field you leave out stays as it is. expiresAt is then counted
from the session's last use (updatedAt), not from now. So if you shorten it below the session's
age, the session expires right away. Any other value returns 400.
A longer ttlDays only stops Vidocu from forgetting the login. If the site itself signs people out
after a week, the session still stops working after a week.
Billing
Charged per minute of finished video when the job completes, with a 1-minute minimum that covers the fixed planning cost every job pays. A balance check runs before the job starts, so one that could never be paid for doesn't begin.
Failed and aborted jobs are free. A partial recording produced a video, so it is charged for what it produced.
To cap the cost of a run, set maxDurationSec. The agent plans to fit that length, the replay
speeds up if the plan runs over, and the finished video is cut at it, so a job is never charged
for more than that many seconds (the 1-minute minimum still applies).
Errors
| Status | Code | Cause |
|---|---|---|
| 402/403 | limit_exceeded | Free plan, or not enough balance to start |
| 400 | validation_error | Invalid input, or the sessionId no longer exists |
| 404 | not_found | No such recording or saved session in this workspace |
| 409 | conflict | Download requested before the job finished, or a session delete by domain when the site has several |
| 422 | validation_error | The recorder judged the prompt not actionable |
| 429 | rate_limit_exceeded | Two recordings are already running for this workspace |