> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Render settings

Every setting for screenshots and videos, with its default, values and an example, and how render encodes and captures in the beta.

Set these in the body of `POST /v1/render/screenshots` or `POST /v1/render/videos`. The look itself (background, frame, cursor, captions) is a set of named presets: see [Styles and captions](/render/styles).

| Setting | Default |
|---|---|
| [Screenshot format and quality](#screenshot-format-and-quality): `format` | `webp` (`format`), 90 (`quality`) |
| [Pixel ratio](#pixel-ratio): `dpr` | 1 |
| [Screenshot viewport](#screenshot-viewport): `viewport` | 1440 by 900 CSS pixels |
| [Full page](#full-page): `full_page` | `false` |
| [When the page is ready](#when-the-page-is-ready): `wait_until` | `auto` (`wait_until`), 0 ms (`wait_for_ms`) |
| [Settle](#settle): `settle` | `auto` |
| [Screenshot timeout](#screenshot-timeout): `timeout_ms` | 30,000 ms |
| [Color scheme](#color-scheme): `theme` | `auto` |
| [Overlays](#overlays): `overlays` | `remove` |
| [Screenshot auto background](#screenshot-auto-background): `auto_background.pin` | none: the image is picked ([per field](#screenshot-auto-background)) |
| [Screenshot ad blocking](#screenshot-ad-blocking): `block_ads` | `true` |
| [Video formats](#video-formats): `formats` | `mp4`, `poster` |
| [Output size and frame rate](#output-size-and-frame-rate): `output` | 1920 by 1080 at 60 frames a second |
| [Narration speed](#narration-speed): `voice.speed` | 1 |
| [Captions](#captions): `captions.burn_in` | `karaoke` when narrated, `static` when silent ([per field](#captions)) |
| [Camera zoom](#camera-zoom): `camera.style` | `auto` ([per field](#camera-zoom)) |
| [Background under the camera](#background-under-the-camera): `camera.background_motion` | 0.1 ([per field](#background-under-the-camera)) |
| [Typing shot](#typing-shot): `camera.typing_zoom` | 2 |
| [Camera framing](#camera-framing): `camera.framing` | `subject` |
| [Scrolling to a step](#scrolling-to-a-step): `scroll` | `comfortable` |
| [Cursor motion](#cursor-motion): `cursor.smoothing_ms` | 100 ([per field](#cursor-motion)) |
| [Auto background](#auto-background): `auto_background.pin` | none: the image is picked ([per field](#auto-background)) |
| [Reuse a recording](#reuse-a-recording): `recording.reuse` | `false` ([per field](#reuse-a-recording)) |
| [Video viewport](#video-viewport): `viewport` | 1440 CSS pixels wide (430 for a portrait output; about its window's width for a square or near-square one, no wider than 4:3), at the shape of the space the page gets in the frame |
| [Start time](#start-time): `start_time` | when the job was created |

## Screenshots

### Screenshot format and quality

How the capture is encoded. WebP and JPEG are smaller; PNG is lossless. `quality` applies to WebP and JPEG. Each entry of `variants` adds another encoding of the same capture and bills one more `render.screenshot`.

- **Field:** `format` and `quality` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** `webp` (`format`), 90 (`quality`)
- **Values:** `webp`, `png` or `jpeg` (`format`); 1 to 100 (`quality`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)
- **Guide:** [Options](/render/screenshots#options)

```json
{"url": "https://example.com", "format": "png"}
```

```bash
b3os screenshot https://example.com --format png
```

### Pixel ratio

The device pixel ratio of the capture. 2 or 3 gives a sharper image with 4 or 9 times the pixels.

- **Field:** `dpr` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** 1
- **Values:** 1, 2 or 3
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"url": "https://example.com", "dpr": 2}
```

```bash
b3os screenshot https://example.com --dpr 2
```

### Screenshot viewport

The browser window's size before the capture, which sets the page's layout.

- **Field:** `viewport` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** 1440 by 900 CSS pixels
- **Values:** `width`: 320 to 3,840; `height`: 240 to 2,160
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"url": "https://example.com", "viewport": {"width": 390, "height": 844}}
```

```bash
b3os screenshot https://example.com --width 390 --height 844
```

### Full page

Captures the whole scroll height instead of the first screen.

- **Field:** `full_page` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** `false`
- **Values:** `true` or `false`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"url": "https://example.com", "full_page": true}
```

```bash
b3os screenshot https://example.com --full-page
```

### When the page is ready

`wait_until: auto` takes the load event, or a painted first screen when the load event is held up by a tracker or a script that never answers. `load`, `domcontentloaded` and `networkidle` wait for that event. `wait_for_ms` adds settle time after that.

- **Field:** `wait_until` and `wait_for_ms` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** `auto` (`wait_until`), 0 ms (`wait_for_ms`)
- **Values:** `auto`, `load`, `domcontentloaded` or `networkidle` (`wait_until`); 0 to 10,000 ms (`wait_for_ms`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)
- **Guide:** [When the page is ready](/render/screenshots#when-the-page-is-ready)

```json
{"url": "https://example.com", "wait_until": "networkidle", "wait_for_ms": 1500}
```

```bash
b3os screenshot https://example.com --wait-for-ms 1500
```

### Settle

`auto` finishes entrance animations, waits a bounded time for the first screen to fill in, and captures again while the main area is still blank. `fast` captures as soon as the page is ready.

- **Field:** `settle` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** `auto`
- **Values:** `auto` or `fast`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"url": "https://example.com", "settle": "fast"}
```

### Screenshot timeout

How long loading the page and capturing it may take in all. A screenshot that produces no image bills nothing.

- **Field:** `timeout_ms` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** 30,000 ms
- **Values:** 1,000 to 60,000 ms
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"url": "https://example.com", "timeout_ms": 60000}
```

### Color scheme

Emulates `prefers-color-scheme`. `auto` leaves the page's own default.

- **Field:** `theme` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** `auto`
- **Values:** `light`, `dark` or `auto`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"url": "https://example.com", "theme": "dark"}
```

```bash
b3os screenshot https://example.com --theme dark
```

### Overlays

`remove` hides consent walls and banners, sign-up and app pop-ups, dim layers and chat widgets before the capture. `keep` leaves them and lists them in `warnings`. Add `hide` selectors for anything it does not find.

- **Field:** `overlays` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** `remove`
- **Values:** `remove` or `keep`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)
- **Guide:** [Consent walls, pop-ups and ads](/render/screenshots#consent-walls-pop-ups-and-ads)

```json
{"url": "https://example.com", "overlays": "keep"}
```

### Screenshot auto background

How the default `auto` background picks its library image. A small vision model picks among the images that keep the window's edges clear, and the pick is kept per site, so a site's screenshots and videos match. `pin` draws one image instead, `repick` asks the model again for the site and keeps its answer, and `vision: false` picks with the color rules alone. The screenshot's `auto_background` says which image was drawn and how it was chosen. See [the auto background](/render/styles#the-auto-background).

- **Field:** `auto_background.pin`, `auto_background.repick` and `auto_background.vision` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** none: the image is picked (`auto_background.pin`), `false` (`auto_background.repick`), on, where the node has the model (`auto_background.vision`)
- **Values:** a library image in an auto pack, such as `gradient-blur-collection/gradbg-049` (`auto_background.pin`); `true` or `false` (`auto_background.repick`); `true` or `false` (`auto_background.vision`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"auto_background": {"repick": true}}
```

### Screenshot ad blocking

Blocks requests to ad and tracker hosts and collapses the slots they leave empty, so no blank band is left in the image.

- **Field:** `block_ads` on [screenshot](/api/post-v1-render-screenshots)
- **Default:** `true`
- **Values:** `true` or `false`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"url": "https://example.com", "block_ads": false}
```

## Videos

### Video formats

The files a video produces besides its captions. A GIF takes longer to encode and is larger than the MP4.

- **Field:** `formats` on [video](/api/post-v1-render-videos)
- **Default:** `mp4`, `poster`
- **Values:** any of `mp4`, `gif` and `poster`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"formats": ["mp4", "gif", "poster"]}
```

```bash
b3os video script.json --formats mp4,gif,poster
```

### Output size and frame rate

The video's width, height and frame rate. When you set `output`, set all three; width and height must be even.

- **Field:** `output` on [video](/api/post-v1-render-videos)
- **Default:** 1920 by 1080 at 60 frames a second
- **Values:** `width`: 16 to 3,840; `height`: 16 to 2,160; `fps`: 24, 25, 30, 50 or 60
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"output": {"width": 1080, "height": 1920, "fps": 30}}
```

### Narration speed

How fast the narration speaks. Leave `voice` out for the node's default voice, `kokoro-82m` `af_heart`; `"voice": null` makes a silent video.

- **Field:** `voice.speed` on [video](/api/post-v1-render-videos)
- **Default:** 1
- **Values:** 0.5 to 2
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)
- **Guide:** [Voices](/render/videos#voices)

```json
{"voice": {"model": "kokoro-82m", "voice": "af_heart", "speed": 1.2}}
```

### Captions

`burn_in` draws captions into the frames: `static` as a small pill in the margin under the window, `karaoke` the same in step with the narration (the words spoken white, the word being spoken lit, the words to come dimmed), `none` for files only. `sidecars` are the caption files. The line limits split your `say` text into cues.

- **Field:** `captions.burn_in`, `captions.sidecars`, `captions.max_chars_per_line`, `captions.max_lines` and `captions.max_chars_per_second` on [video](/api/post-v1-render-videos)
- **Default:** `karaoke` when narrated, `static` when silent (`captions.burn_in`), `vtt` (`captions.sidecars`), 42 (`captions.max_chars_per_line`), 2 (`captions.max_lines`), 20 (`captions.max_chars_per_second`)
- **Values:** `none`, `static` or `karaoke` (`captions.burn_in`); any of `vtt` and `srt` (`captions.sidecars`); 10 to 80 (`captions.max_chars_per_line`); 1 to 3 (`captions.max_lines`); 5 to 40 (`captions.max_chars_per_second`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)
- **Guide:** [Styles and captions](/render/styles)

```json
{"captions": {"burn_in": "karaoke", "sidecars": ["vtt", "srt"]}}
```

### Camera zoom

`style` `auto` zooms in on the element each click, select or typing step acts on, stays in through the step and while the next one starts within 2.5 s or is nearby, pans between them and ends on the whole window. The camera moves over the whole frame: the window, its shadow and the page zoom together and the background and the window's edges leave the frame, while captions stay put. It zooms so the element fits, from 1.25x up to `max_zoom`; a small button reaches `max_zoom`, and an element in a nav, aside or header bar stops at 1.25x. `subtle` stops every zoom at 1.25x, and `off` (the older `auto_zoom: false`) keeps the whole window in view.

- **Field:** `camera.style`, `camera.auto_zoom` and `camera.max_zoom` on [video](/api/post-v1-render-videos)
- **Default:** `auto` (`camera.style`), none: `style` decides (`camera.auto_zoom`), 1.7 (`camera.max_zoom`)
- **Values:** `auto`, `subtle` or `off` (`camera.style`); `true` or `false` (`camera.auto_zoom`); 1 to 3 (`camera.max_zoom`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"camera": {"style": "subtle"}}
```

### Background under the camera

As the camera zooms, the background moves and zooms by `background_motion` of the window's move, a tenth by default: 0 holds it still, 1 zooms it with the window so the whole frame zooms as one. `background_blur` softens it: an image background is drawn 4 px soft on the whole window at 1080p and softer as the camera zooms in, about 7 px at 1.6x on a 1080p video; left out, only an image background softens.

- **Field:** `camera.background_motion` and `camera.background_blur` on [video](/api/post-v1-render-videos)
- **Default:** 0.1 (`camera.background_motion`), on for an image background (the default look's among them), off otherwise (`camera.background_blur`)
- **Values:** 0 to 1 (`camera.background_motion`); `true` or `false` (`camera.background_blur`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"camera": {"background_motion": 1, "background_blur": false}}
```

### Typing shot

A field typed into, by a `type` step or a click that a `type` without a target follows, is one shot: the camera zooms to `typing_zoom`, framed from the start of the field, and holds there through the last key. It never zooms past `max_zoom`, nor past the zoom where the page is as sharp as captured, so the default types at 1.73x on the default canvas; `style: subtle` keeps its 1.25x.

- **Field:** `camera.typing_zoom` on [video](/api/post-v1-render-videos)
- **Default:** 2
- **Values:** 1 to 3
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"camera": {"typing_zoom": 1.6}}
```

### Camera framing

`subject` frames the panel the element sits in, not the click point: the nearest enclosing element with a box of its own or a section, form, table, list or dialog, at least 240 x 120 CSS px, fitted with 24 px around it into the frame above the captions, from 1.4x up to `max_zoom`, and placed from its start when it is wider than the view; a view edge moves off a neighbouring panel to the gap beside it. A hover the narration speaks over, an element a `wait` waits for, and after a click that loads a page the element its beat waits for, are framed and held through their line. `point` frames the click point as before.

- **Field:** `camera.framing` on [video](/api/post-v1-render-videos)
- **Default:** `subject`
- **Values:** `subject` or `point`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"camera": {"framing": "point"}}
```

### Scrolling to a step

`comfortable` scrolls an element outside 8% to 85% of the viewport's height so its panel sits in the middle, before the cursor sets off for it and as a waited-for element appears; one already there never scrolls the page. `minimal` scrolls only an element outside the viewport, and `off` only by your own `scroll` steps, failing a step whose element is outside the viewport.

- **Field:** `scroll` on [video](/api/post-v1-render-videos)
- **Default:** `comfortable`
- **Values:** `comfortable`, `minimal` or `off`
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"scroll": "minimal"}
```

### Cursor motion

Each cursor move starts softly and settles over about `smoothing_ms`, as Screen Studio's smoothed cursor does, and still arrives when it would have; 0 keeps the eased path. After a click, a select or the first key typed into a field, `move_aside` moves the cursor just below the element, so it does not cover the label or the text being typed while the camera holds. Still for `hide_after_ms`, it fades out, and back in before its next move; 0 never hides it. A restyle keeps the path its video was recorded with.

- **Field:** `cursor.smoothing_ms`, `cursor.hide_after_ms` and `cursor.move_aside` on [video](/api/post-v1-render-videos)
- **Default:** 100 (`cursor.smoothing_ms`), 2,000 (`cursor.hide_after_ms`), `true` (`cursor.move_aside`)
- **Values:** 0 to 300 (`cursor.smoothing_ms`); 0 to 10,000 (`cursor.hide_after_ms`); `true` or `false` (`cursor.move_aside`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"cursor": {"hide_after_ms": 0, "move_aside": false}}
```

### Auto background

How the default `auto` background picks its library image. A small vision model picks among the images that keep the window's edges clear, and the pick is kept per site, so a site's videos and screenshots match. `pin` draws one image instead, `repick` asks the model again for the site and keeps its answer, and `vision: false` picks with the color rules alone. `result.auto_background` says which image was drawn and how it was chosen. See [the auto background](/render/styles#the-auto-background).

- **Field:** `auto_background.pin`, `auto_background.repick` and `auto_background.vision` on [video](/api/post-v1-render-videos)
- **Default:** none: the image is picked (`auto_background.pin`), `false` (`auto_background.repick`), on, where the node has the model (`auto_background.vision`)
- **Values:** a library image in an auto pack, such as `gradient-blur-collection/gradbg-049` (`auto_background.pin`); `true` or `false` (`auto_background.repick`); `true` or `false` (`auto_background.vision`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"auto_background": {"repick": true}}
```

### Reuse a recording

`reuse` replays your project's newest recording of the same source, steps, viewport, seed and start time, up to `max_age_s` old, instead of loading the site again. Faster, and the page shows what the site served then. `result.recording` says which happened.

- **Field:** `recording.reuse` and `recording.max_age_s` on [video](/api/post-v1-render-videos)
- **Default:** `false` (`recording.reuse`), 86,400 s (`recording.max_age_s`)
- **Values:** `true` or `false` (`recording.reuse`); 60 to 86,400 s (`recording.max_age_s`)
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"recording": {"reuse": true, "max_age_s": 3600}}
```

### Video viewport

The browser's size in CSS pixels during the recording. Left out, the window fills the frame with even margins.

- **Field:** `viewport` on [video](/api/post-v1-render-videos)
- **Default:** 1440 CSS pixels wide (430 for a portrait output; about its window's width for a square or near-square one, no wider than 4:3), at the shape of the space the page gets in the frame
- **Values:** `width`: 320 to 3,840; `height`: 240 to 2,160
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"viewport": {"width": 1280, "height": 800}}
```

### Start time

The page's `Date` when the video starts; the clock then moves with the video. The finished video's `result.start_time` gives the value used: send it back with the same `seed` (randomness) and `recording.reuse` and a re-render is identical. Without `recording.reuse` the site loads again, so a page that changed shows the change.

- **Field:** `start_time` on [video](/api/post-v1-render-videos)
- **Default:** when the job was created
- **Values:** an RFC 3339 date and time
- **Invalid value:** [`400 invalid_request`](/errors#invalid_request)

```json
{"seed": 7, "start_time": "2026-11-01T09:00:00Z"}
```

## Fixed in the beta

These work one way in the beta, and no request field changes them.

### Video encoding

**H.264, BT.709.** MP4 files are H.264 from x264 at the `veryfast` preset and CRF 20, with BT.709 color. No field changes the codec or quality.

### Video capture scale

**Picked per segment.** Parts of a video where the camera zooms in are captured at device scale 2, so zoomed text stays sharp, and the rest at 1. Screenshots take `dpr`; videos have no such field.

### Video background

**Picked from 24 library images.** A video's `auto` background, the default, is one of the library images `GET /v1/render/backgrounds?tag=video` lists, picked from the video's first frame for the page's tone and accent color, at least 12 L* from the page at the window's edges. The timeline's `style` names it. `studio` names the soft mesh videos drew before; `background_ref` names any library background. The set is the node's; no field changes it.

- **Guide:** [Styles](/render/styles)

### Render egress

**Through the node's egress guard.** Every capture browser's only way out is the node's egress proxy, under the same egress guard as vision, from the node's own address. You cannot bring a proxy.

### Signed-in captures

**Through a vision profile.** A screenshot with `profile`, or a video with `source.profile`, loads that vision profile's cookies and storage before the page loads: into a video's probe and every capture segment. It needs `vision:profiles:use`. A page that is still signed out fails and bills nothing.

- **Error:** [`404 profile_not_found`](/errors#profile_not_found), [`422 profile_expired`](/errors#profile_expired)

### Signed URL lifetime and retention

**24 hours and 7 days.** A signed artifact URL works for 24 hours, and never past its file. Screenshots, videos and their files are kept 7 days. Fetch the resource again for a fresh URL.

- **Guide:** [Artifacts](/render#artifacts)