Recording product UI
POST /v1/recordings (MCP: record_url) opens a public web page in a real browser and films it while
following your steps. You describe what to do; Second Studio makes it watchable: a visible cursor
that travels to each target, typing at human speed, scrolling in smooth flicks rather than jumps, and a
pause after every action. It never ends on an action: the last frame holds.
Steps
| Step | Fields | Does |
|---|---|---|
goto |
url |
Navigate to another public URL |
click |
selector, timeout? |
Move the cursor to the element and click it |
hover |
selector |
Move the cursor onto the element |
type |
selector, text, delay_ms? (default 85) |
Click the field and type, key by key |
press |
key (e.g. Enter, Tab) |
Press a key |
scroll |
pixels (negative scrolls up) |
Scroll in smooth flicks |
wait_for |
selector, timeout? (s) |
Wait until the element is visible |
hold |
seconds |
Hold still so viewers can read |
Every step also accepts after (seconds to pause afterwards, default 0.4) and mark (a name for this
moment in result.marks). Selectors use Playwright syntax:
CSS (#buy, [data-testid=save]) or text (text=Get started).
Without steps, the recording settles on the page, scrolls through it and returns to the top.
{
"url": "https://example.com",
"viewport": "mobile",
"steps": [
{ "type": "hold", "seconds": 2, "mark": "landing" },
{ "type": "click", "selector": "text=Search" },
{ "type": "type", "selector": "input[name=q]", "text": "chicken dinner" },
{ "type": "press", "key": "Enter" },
{ "type": "wait_for", "selector": ".results" },
{ "type": "hold", "seconds": 3, "mark": "results" }
]
}
Options
| field | |
|---|---|
viewport |
desktop 1280×800 (default), laptop 1440×900, hd 1920×1080, mobile 390×844 |
scale |
2 records at 2× device pixels (a 3840×2160 file for hd): crisp text when the edit zooms in |
typingDelayMs |
Per-character typing delay, default 85 (set delay_ms on a step to override) |
Steps gained: goto takes wait_for (a selector to wait for on the new page, so a transition is never a frozen or black frame. Otherwise it waits for network idle); network_idle waits for the page to settle; click, hover and type take glide (seconds the cursor travels, default 0.5) and settle (pause before the click, default 0.3). The cursor shows a click ripple.
Every mark for a click, hover or type carries box: the element's bounding box as fractions of the frame (x, y, w, h), so an editor can zoom and draw callouts on exact coordinates.
Viewports
viewport |
Size |
|---|---|
desktop (default) |
1280 × 800 |
mobile |
390 × 844 at 2× pixel density, touch enabled |
Output
A recording asset (mp4, 30 fps) and result.marks: [{ "name": "landing", "t": 2.41 }, …]. The
time of every step, for cutting on the action rather than in the middle of it. Use the recording as a
clip in an edit like any other footage.
Limits and rules
- Public
http(s)URLs only; private and internal network addresses are refused. - Up to 60 steps and 300 seconds of recording.
- Pages that need a login, or that sit on
localhost, are not reachable withPOST /v1/recordings. Use a repository recording (below) for an app you can share from GitHub, or record it yourself and upload the file as a clip. - Only record sites you own or have permission to film.
Recording a private repository
POST /v1/repo-recordings (MCP: record_repo) builds your app from a GitHub repository in an isolated,
throwaway sandbox, starts it with a local database, and records its key flows. Nothing from the build
leaves the sandbox except the videos and a redacted build log.
1. Connect GitHub (once, admin)
In the portal open GitHub and logins and press Connect GitHub, or call POST /v1/github/install
(MCP: connect_github) and open the url it returns. Pick the repositories to share. The app asks for
read-only access to code and metadata. Each recording gets its own key, scoped to one repository, that
expires within an hour. GET /v1/github lists linked installations; DELETE /v1/github/installations/{id}
unlinks one (uninstall the app on GitHub to remove our access entirely).
If GitHub is not switched on for the service yet, these calls return 503 github_not_configured.
2. Optional: env sets and logins (admin)
- Env sets:
POST /v1/env-sets{ "name": "staging", "env": { "NEXT_PUBLIC_API": "..." } }, or paste a.envfile in the portal. Use test or development values only. - Logins:
POST /v1/logins{ "name": "demo", "host": "localhost", "username": "demo@acme.com", "password": "...", "totpSecret": "optional base32 key" }. The recorder signs in off camera, before filming, and only on that host. - Signed-in session: pass a Playwright
storage_stateJSON inline asstorageState, or upload it (POST /v1/assets) and pass its id asstorageStateAssetId. Either way it is encrypted like a login and an uploaded file is deleted once copied, so it is never served by URL. Use this for single sign-on or approvals that can't be scripted.
Recordings that use an env set, a login or a session need an admin key. Values are encrypted at rest (versioned keys) and never returned: lists show names, variable names and a masked user name. They are decrypted only for the worker, once per run, and scrubbed from build logs, progress and errors. Deleting an env set or login, or disconnecting GitHub, cancels and refunds queued recordings that use it. Adding, changing, deleting and using them is written to the audit log on governed accounts.
3. Record
{ "repo": "acme/web", "ref": "main", "envSetId": "...", "loginId": "...", "maxFlows": 3,
"flows": [ { "title": "Create a project", "steps": [
{ "type": "goto", "url": "/projects" }, { "type": "cap", "text": "Start a project in one click" },
{ "type": "click", "selector": "text=New project" } ] } ] }
| Field | |
|---|---|
repo |
owner/name, required |
ref |
branch, tag or commit; default branch if omitted |
subdir |
the app's directory in a monorepo, like apps/web (relative, no ..); repo root if omitted |
installationId |
only when the account has more than one GitHub installation |
mode |
auto (default) or real; both build and run your real app today. mock (a faked backend) is not supported yet and is rejected with 400 invalid_field |
flows |
up to 5 {title, steps}; steps as above plus cap {text} for a caption. goto takes a path like /settings |
maxFlows |
1 to 5 (default 3). Without flows, we plan this many from the code and README |
viewport, scale |
as for POST /v1/recordings |
Price: 20 credits for up to 3 flows, 4 credits per extra flow (max 5), and 3 more when the run signs
in with a saved login, an OTP or a storage_state. That is quoted up front (input.quote). The first 25
sandbox minutes (install, build and recording) are included; after that it is 5 credits per started 10
minutes, charged at completion (result.overtimeCredits). The sandbox stops at 45 minutes, so overtime
adds at most 10 credits. The result has sandboxSeconds (what overtime is based on),
flows: [{ flow, storageId, marks }], buildLog (secrets and tokens removed) and modeUsed.
If the build, start-up or recording fails on our side (build_failed, start_failed, flow_failed),
the credits are refunded. bad_repo (nothing we can build or run, for example not a supported web app)
is not refunded. Supported first: Node frontends (Next.js, Vite) with Postgres or SQLite through
Prisma or Drizzle, signing in with a seeded user, a saved login, or a signed-in session.
Agents: this page is also available as Markdown; the whole reference is in llms-full.txt.