sandbox_shot

Takes a screenshot of the box's display, of its front window, of a URL opened in a fresh browser or of a tab already open in a browser on the box, captures several pages at once, or records the display to mp4.

Input Output
id, target (screen, window, url or tab), url, urls, width, height, deviceScaleFactor, mobile, fullPage, tab, port, reset, waitFor, stableMs, record (start or stop) the screenshot inline plus url (valid 1 hour; https://api.parallelsandbox.com/v1/files/…, which redirects to storage when opened), bytes, expiresAt, capturedAt, path, and as they apply window, windows, clipped, viewport, tab, waitFor, stable, loadError, pageErrors, takeover; with urls, no picture but files[] (each page's url, path, bytes, capturedAt, and pageErrors when the page reported any); record: "stop" returns the mp4's url (the same kind, valid 1 hour)
  • No record: a screenshot, returned inline and as a URL valid for one hour, with capturedAt, the moment it was taken. path is a copy of it in the box, under /work/.sbx/shots/ (relative to /work; the newest 100 are kept): fetch it with sandbox_pull or sandbox_get to keep the picture itself or hand it on.
  • While a person has taken the box over, target: "screen", "window" and "url" still capture, and the result carries takeover: true: the picture is what they are looking at. target: "tab" waits, like sandbox_exec, because it changes the tab they may be using.
  • target: "screen" (default) captures the virtual display, 390 × 844 portrait unless the box was started with orientation: "landscape" (1280 × 800). target: "window" captures only the front window, without the desktop, and adds window to the result: its x,y,width,height on the screen (such as "100,80,900,600"), so add x and y to coordinates on the image before clicking; a window larger than the screen is cut to the part on it, and clipped says so. windows lists every window on the screen, the captured one first, each with id, name, x, y, width, height and the pid that opened it when the program reports one: a window stays until its program ends, and this is how to check that none of yours is left before you finish (sandbox_procs stop ends one you started with background: true; xdotool windowkill <id> closes any one). With either, stableMs (100 to 10000) captures once the picture has not changed for that long, so a page transition or an animation is not caught half way; it gives up after 20 seconds and returns the last frame with stable.settled: false.
  • target: "url" opens url in a fresh, signed-out headless Chromium and captures it. width and height set the viewport in CSS pixels (by default the box's screen size: 390 × 844 portrait, 1280 × 800 landscape; width alone keeps the screen's height), and the page lays out at exactly that width, below Chromium's 500-pixel window minimum too: this is how to look at a phone or tablet layout (width: 390, height: 844). deviceScaleFactor (0.5 to 4, such as 3 for an iPhone) makes the image that many times the viewport, and mobile: true emulates a phone with touch, so nothing hovers and a page without <meta name=viewport> lays out 980 pixels wide, as on a real phone. fullPage: true captures the whole page rather than the viewport (height is then ignored). A page that does not load comes back with loadError. urls captures several pages in one call (pages of the same size share one browser): the pictures are not in the result but written in the box under /work/.sbx/shots/, and files[] lists each page's url, path (relative to /work), bytes and capturedAt; fetch the ones you want with sandbox_get (paths takes several). Such a batch is recorded as one step (summary like 2 urls) with no pictures: the person does not see them in the app, /media does not have them, and they go with /work when the box is stopped.
  • target: "tab" captures a page already open in a browser on the box, still signed in, as it is: it does not scroll or move the pointer, so hover states stay in the picture. That browser must have been started with --remote-debugging-port=9222 (Electron takes the same flag); port names its port when several browsers on the box have one, and a browser that is still starting is waited for, up to 45 seconds, so no sleep is needed after launching it. tab picks the tab by its id or a piece of its URL or title (default: the tab whose viewport is held, else the most recently used one), and the result's tab says which was captured. width gives that tab a viewport that wide, with height (default: the window's own height), deviceScaleFactor and mobile as above, and the box keeps it until reset: true, the tab closes or the browser exits, so a phone layout holds while you keep clicking and typing in the tab (an Emulation.setDeviceMetricsOverride from your own CDP script is undone when its connection closes). Each call with width, height, deviceScaleFactor or mobile replaces the whole emulated viewport, and one tab is held at a time; viewport in the result is what the page saw.
  • waitFor, with target: "url" or "tab": capture once this CSS selector matches a visible element, or, prefixed with js:, once this JavaScript expression is truthy (it may await). It waits up to 30 seconds; if the condition never holds you still get the screenshot, with waitFor.met: false, and a condition that cannot be evaluated is an error.
  • pageErrors, with target: "url" or "tab", is what the page itself reported: console (console.error, failed console.assert, uncaught exceptions with their script and line, and what the browser blocked, such as a Content Security Policy violation) and failedRequests (each resource that did not load, its URL followed by why: net::ERR_NAME_NOT_RESOLVED, the server responded with a status of 404 (Not Found)). The first 10 of each are listed, each cut to 300 characters, and more counts the rest; a page that reported nothing has no pageErrors. It covers the page now shown, from its last navigation: for target: "tab" that includes what happened before the call, so a page stuck on its loading screen shows why.
sandbox_exec { "id": "<id>", "cmd": "chromium --no-sandbox --remote-debugging-port=9222 --user-data-dir=/tmp/chrome http://localhost:5173/", "background": true, "note": "open the app in a browser on the box" }
sandbox_shot { "id": "<id>", "target": "tab", "width": 390, "height": 844, "mobile": true, "deviceScaleFactor": 3, "waitFor": "nav [aria-label='Menu']" }
sandbox_shot { "id": "<id>", "target": "tab", "waitFor": "js:document.querySelectorAll('.order').length > 0" }
sandbox_shot { "id": "<id>", "target": "tab", "reset": true }
  • To keep a page on the box's screen yourself (a Chromium you keep driving), start Chromium with the flags for the screen's orientation; see Browsers on the box's screen in sandbox_exec.
  • record: "start" begins recording the display to mp4; record: "stop" ends it and returns its download URL, valid for an hour. A recording keeps the box from freezing, but only until a full hour has passed since the box's last use (the end of a tool call that acts on it, or a wake; see Box states), the same point a background command keeps it awake until; if the last use was a foreground command, that hour counts from when the command ended. How long the recording has run does not matter. After that, once nothing else keeps the box awake (a foreground command, a takeover and the rest in Box states), ParallelSandbox stops the recording without uploading it, and the box freezes as usual; the mp4 stays in the box at /work/.sbx/rec/rec-<unix time>.mp4; fetch it with sandbox_get, which wakes the box itself if it is frozen. A recording stopped automatically does not become a step: neither the app nor /media shows it, and unless you fetch it, it goes with /work when the box is stopped. A copy fetched with sandbox_get is a temporary transfer, automatically removed after about one day and not billed as stored files (see Credits). Download it before stopping the box; after the box stops, a new download URL cannot be requested.
  • One frame out of a recording, as a single image: ffmpeg -ss 5 -i in.mp4 -frames:v 1 -update 1 out.png (-update 1 writes one file instead of expecting a numbered pattern such as out%03d.png). ffmpeg is installed in the box.
  • Each screenshot, and each recording you end with record: "stop", also shows in the person's app as a step of the box, under its Screenshots & recordings (also when the box is listed under Stopped (last 7 days), see sandbox_exec); the files are deleted after 7 days. Pages captured together with urls are the exception: the whole batch is recorded as one step with no pictures, see above. Past the hour, get fresh URLs (another sandbox_shot takes a new picture; it does not bring back the old one): call GET https://api.parallelsandbox.com/v1/boxes/{id}/media with the API key (Authorization: Bearer <key>); it is REST only, with no MCP tool. It returns the box's steps taken in the last 7 days that have pictures, the newest 200 at most, oldest first, for a stopped box too: {"keepDays": 7, "steps": [...]}. Each step has the fields of sandbox_status steps[], plus media[], each with kind (video or image), url and bytes, and durationSec for a video; the URLs are signed again on each call and valid for at least an hour (see REST). How long each kind of picture is kept, and who can still see it once the box is stopped: see the table under Finishing a box.

Every tool and topic is listed in the tool reference.