# Recording product UI # 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](https://playwright.dev/docs/selectors): 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. ```json { "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 with `POST /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 `.env` file 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_state` JSON inline as `storageState`, or upload it (`POST /v1/assets`) and pass its id as `storageStateAssetId`. 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 ```json { "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.