Your browser is too old for Guard.ch

Update it as shown below, then open this page again.

iPhone and iPad

Update iOS or iPadOS in Settings > General > Software Update. Every browser on iPhone and iPad runs on the system's version, so switching to another browser app won't help.

Computer and Android

Update your browser, or open Guard.ch in a current version of Chrome, Edge, Firefox or Safari.

All integrations

Guard.ch API reference

Every route you can call with an API key, with its fields, examples and errors.

This page lists what the API accepts and returns. For a walkthrough from API key to a running Playwright script, see the custom integration guide. To connect an AI assistant, use MCP instead.

Basics

Base URL
https://api.guard.ch/v7
Authentication
An API key in the Authorization header
Format
JSON over HTTPS
Access
Enterprise

Paths in this reference are relative to the base URL. Send request bodies as JSON with Content-Type: application/json. Responses can contain fields not listed here; ignore any you don't use.

Create API keys on the Agents tab of your dashboard, under Advanced. A key acts for your account: the sessions it starts are yours, count towards your limits and appear in your session list.

Create an API key

Authentication

Send the key with every request:

Authorization: Bearer YOUR_API_KEY

Keys start with brl_k_ (keys created earlier with brl-k-v7-). The API reads them only from this header, never from the URL. GET /image is the only route on this page that works without a key. A key works until you revoke it, or until the fixed date chosen when it was created.

Responses and errors

Every route answers with HTTP 200 and a JSON body. The body's status field tells you the outcome, so check it instead of the HTTP code:

StatusMeaning
okThe request succeeded. The other fields hold the result.
deniedThe request was refused, for example because the key is missing or expired, a value is invalid, no session slot is free or the session doesn't exist.
upgradeYour plan doesn't include this: API access itself, or an app or option you asked for.
errorSomething went wrong on our side, or the path doesn't exist. Try again later.

Treat any status other than ok as a failure. message gives the reason in a sentence you can show to a person. Its language follows the Accept-Language header and defaults to English, so don't parse it: branch on status and, where present, code or reason.

{
  "status": "denied",
  "message": "This browser session could not be found. Check the session ID and try again."
}

If the key is missing, invalid, revoked or expired, the answer is denied with loggedIn: false. If your email address isn't confirmed yet, it's denied with verified: false. File downloads are the only exception to HTTP 200; see Download a file.

Endpoints

EndpointDescription
POST /web/sessionsStart a session
GET /web/sessionsList your sessions
GET /web/sessions/:idRead one session
DELETE /web/sessions/:idStop a session
GET /web/sessions/:id/downloads/:downloadIdFetch a file the browser downloaded
GET /extensionsThe apps a new or running session can use
POST /vm/:vmId/extensionsSwitch apps on in a running session
PATCH /vm/:vmId/extensions/:idChange an active app's settings
DELETE /vm/:vmId/extensions/:idSwitch an app off
GET /web/usageYour API usage over the last 30 days
GET /user/quotaYour plan's limits and what is in use
GET /imageBrowser images and locations

Sessions

A session is one cloud browser. Your code drives it over CDP, and a person can watch or take over in the live view. These routes only reach sessions of your own account, never a colleague's.

Start a session

POST https://api.guard.ch/v7/web/sessions

Starts a browser and answers immediately while the browser keeps starting in the background: its status is starting, then running. Connect Playwright's connectOverCDP or Puppeteer's browserWSEndpoint to connectUrl. If the connection is refused at first, wait until GET /web/sessions/:id reports running.

All body fields are optional:

FieldDescription
imageBrowser image ID. Any enabled image with supports_programmatic in GET /image. Default: chrome.
nameDisplay name for your session list, 1 to 64 characters. Leave it out and a name like brisk-otter-27 is generated.
urlAddress to open at the start. Only http and https are accepted.
extensionsThe apps the session starts with, as [{ id, settings? }] (see Apps below). agent is always added. Leave it out and the session starts with your always-activate set; what your plan, your workspace or the browser refuses there is left out instead of failing the start. If an app you name is refused, the start fails, and the answer names it in extension. Your workspace's enforced apps come on either way. Name a network app either here or with the older fields below, not both.
countryThe older way of naming network.location, and it still works: a country or city code from vpnLocations in GET /image, for example de or us-dal. If no exit of that location is free right now, the session still starts, on the data centre's own connection, and the response says so with location_applied: false. Can't be combined with proxy.
egressresidential stands for network.residential: the session leaves through a residential connection, and country chooses the country. The traffic counts towards your plan's residential allowance. Can't be combined with proxy.
proxyStands for network.proxy: your own proxy as protocol://[user:password@]host:port, with http, https, socks4 or socks5. Private and internal addresses are refused. Can't be combined with country or egress. For a proxy or VPN saved in Guard.ch, name network.proxy or network.wireguard with its egressId in extensions.
analysisThe older switch for the security apps: true starts the session with all of them, like the Security Suite. A browser without supports_analysis answers analysis_unavailable.
screenViewport as {"width": 1440, "height": 900}: width 320 to 1920, height 480 to 1080. Default: 1366 x 768.
idempotencyKeyRetry key of 1 to 128 characters. Repeat the request with the same API key and retry key within 10 minutes and you get the first successful response back instead of a second browser. Failed attempts aren't stored, so a retry after a refusal really tries again.

For example:

curl -X POST https://api.guard.ch/v7/web/sessions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "triage-4821",
    "url": "https://example.com",
    "extensions": [
      { "id": "network.location", "settings": { "country": "DE" } },
      { "id": "security.reputation" }
    ],
    "screen": { "width": 1440, "height": 900 },
    "idempotencyKey": "ticket-4821"
  }'
{
  "status": "ok",
  "id": "brl-v-v7-q8m2r7k4d1",
  "name": "triage-4821",
  "image": "chrome",
  "connectUrl": "wss://api.guard.ch/web/brl-v-v7-q8m2r7k4d1/cdp?token=3f9c2a7d",
  "liveViewUrl": "https://guard.ch/en/viewer?id=brl-v-v7-q8m2r7k4d1",
  "createdAt": "2026-09-26T08:14:03.512Z",
  "screen": { "width": 1440, "height": 900 },
  "analysisEnabled": true,
  "location_applied": true,
  "quota": {
    "running_session_limit": 3,
    "running_sessions_used": 1,
    "running_sessions_remaining": 2
  }
}

A successful response contains:

FieldDescription
idSession ID for the other routes.
nameThe name you gave, or the generated one.
imageThe image the session runs on.
connectUrlCDP WebSocket URL for Playwright or Puppeteer. It carries the session's own token, so treat it like a password.
liveViewUrlThe session's live view in Guard.ch, for watching and taking over. Opening it requires signing in to your account.
createdAtStart time in ISO 8601.
screenThe viewport the browser runs with.
analysisEnabledWhether the session started with the security apps on.
location_applied, location_reasonlocation_applied is false when the location you asked for could not be applied, and location_reason says why: unavailable means no exit of that location was free. The session then runs on the data centre's connection. Without a location it is true.
quotaYour sessions at once, including this one: running_session_limit, running_sessions_used and running_sessions_remaining.

A refused start also carries a machine-readable code:

CodeMeaning
invalid_argumentsA value can't be used, for example a malformed proxy, or an app or setting the API does not accept (reason and extension say which). Most field checks answer without a code; message then names the field.
plan_requiredYour plan lacks an app you asked for (status upgrade; extension and feature name it), or your workspace switched it off (status denied, reason: workspace_policy, policy names it).
analysis_unavailableanalysis: true on a browser that cannot run the security apps.
image_not_supportedThe image doesn't exist or can't run API sessions. allowedImages lists the images you can use.
image_restrictedYour plan can't start this image (status upgrade). allowedImages lists the images you can use.
quota_exceededAll your session slots are taken or an allowance is used up. Stop a session or change the request; sending the same call again won't help.
rate_limitedYou started too many sessions in a short time. retryAfterMs says how many milliseconds to wait.
internal_errorSomething failed on our side (status error). Try again later.

List sessions

GET https://api.guard.ch/v7/web/sessions

Returns your sessions in data, newest first. Only sessions started through the API or MCP are listed. Sessions you start by hand in Guard.ch are left out, but GET /web/sessions/:id still finds them by ID.

ParameterDescription
statusall adds ended sessions from the last 30 days. Without it, only sessions that are starting or running are listed.
limitMaximum number of sessions, 1 to 200. Default: 50.
curl "https://api.guard.ch/v7/web/sessions?status=all&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "status": "ok",
  "data": [
    {
      "id": "brl-v-v7-q8m2r7k4d1",
      "name": "triage-4821",
      "image": "chrome",
      "status": "running",
      "createdAt": "2026-09-26T08:14:03.512Z",
      "endedAt": null,
      "endedReason": null,
      "startRefusal": null,
      "expiresAt": "2026-09-27T02:14:03.512Z",
      "lastseen": "2026-09-26T08:31:40.087Z",
      "url": "https://example.com",
      "country": "de",
      "location_applied": true,
      "connectUrl": "wss://api.guard.ch/web/brl-v-v7-q8m2r7k4d1/cdp?token=3f9c2a7d",
      "liveViewUrl": "https://guard.ch/en/viewer?id=brl-v-v7-q8m2r7k4d1",
      "startedByKind": "agent",
      "analysisEnabled": false,
      "startedBy": { "id": 4821, "email": "ana@example.com", "firstname": "Ana", "lastname": "Meier" }
    }
  ]
}

Each session has these fields:

FieldDescription
id, name, imageSession ID, display name and image, as returned when it started.
statusstarting or running while the session is live. Any other value means it has ended.
createdAt, endedAtStart and end time in ISO 8601. endedAt is null while the session runs.
endedReasonWhy it ended: stopped (by you or through the API), timeout (the maximum lifetime was reached), disconnect (nothing was connected for too long), runtime (your plan's runtime allowance is used up), disk (it reached its storage limit), error or lost (the browser failed or its machine went away). Null while it runs.
expiresAtThe latest time the session can end, set by your plan. It can end earlier; see Limits.
lastseenWhen a connected client last kept the session alive.
urlThe start address from the request, or null.
countryThe location the session exits from right now, or null for the data centre's own connection.
location_applied, location_reasonlocation_applied is false when the location asked for at the start could not be applied and has not been changed since; location_reason then says why (unavailable).
connectUrlCDP WebSocket URL while the session is starting or running, otherwise null.
liveViewUrlThe live view in Guard.ch.
startedByKindagent for sessions started through the API or MCP, app for sessions started by hand in Guard.ch.
analysisEnabledWhether a security app is on right now.
startedByThe account that started the session: id, email, firstname and lastname.

Read a session

GET https://api.guard.ch/v7/web/sessions/:id

Returns one session in data, with the same fields as the list. It also finds a session you started by hand in Guard.ch, so a script can attach to it through its connectUrl. An unknown ID or another person's session answers denied.

Stop a session

DELETE https://api.guard.ch/v7/web/sessions/:id

Ends the session, removes its browser and frees the slot immediately. The answer is ok even if the session had already ended. endedReason becomes stopped unless the session ended earlier for another reason. This also ends sessions you started by hand, so only stop what your script started.

curl -X DELETE https://api.guard.ch/v7/web/sessions/brl-v-v7-q8m2r7k4d1 \
  -H "Authorization: Bearer YOUR_API_KEY"

Download a file

GET https://api.guard.ch/v7/web/sessions/:id/downloads/:downloadId

Returns the bytes of a file the browser downloaded during the session, with its content type and file name. The MCP tool browser_downloads hands out this URL with a signed token parameter. The link itself is the credential: it opens this one file for 15 minutes without an Authorization header, so you can open it in a browser or pass it on. List the downloads again for a fresh link.

Unlike every other route, this one answers with real HTTP status codes: 200 with the file, 403 with status denied if the link has expired or isn't valid, or 404 with status denied if the file isn't available. The file is requested again from the page with its cookies, so a one-time or expired download link answers 404.

Apps

Everything optional in a session is an app from the App Store: its exit, anonymous mail, files, agent control and the security apps. A session starts with the ones you name in extensions, or with your always-activate set, and you can switch them while it runs. Your workspace can lock an app or enforce it; the API follows those rules and names the one that refused.

The apps and their settings:

AppDescription
network.locationLocation Switch, a data centre exit. Settings: country (two letters, US) and optionally location, a city code from GET /image (us-dal).
network.residentialHome IP, a residential connection counted towards your allowance. Settings: country, optional.
network.proxyProxy Connect, your own proxy. Settings: egressId of a proxy saved for you or your workspace, or proxy as protocol://[user:password@]host:port.
network.wireguardCustom VPN, your own WireGuard VPN. Settings: egressId of a saved VPN.
mailAnonymous Mail: your addresses and inbox in the session.
filesFiles: uploads and downloads between your device and the session.
agentAgent Bridge: CDP, MCP and handing the browser over. Always on in sessions started through this API.
security.reputation, security.domain, security.privacy, security.page, security.aiThe security apps: Reputation Check, Domain Inspector, Tracker Scan, Page Guard and AI Verdict. They need an image with supports_analysis in GET /image.

List apps

GET https://api.guard.ch/v7/extensions

With image (a browser image ID) the answer is what a new session on that browser could use; with vm (a session ID) it is that session's current state. Send exactly one of the two.

curl "https://api.guard.ch/v7/extensions?image=chrome" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "status": "ok",
  "extensions": [
    { "id": "network.location", "state": "available", "alwaysOn": true },
    { "id": "mail", "state": "available", "alwaysOn": false },
    { "id": "security.ai", "state": "locked", "reason": "policy", "alwaysOn": false }
  ],
  "packs": [
    {
      "id": "pack.security",
      "extensions": ["security.reputation", "security.domain", "security.privacy", "security.page", "security.ai"]
    }
  ],
  "alwaysOn": ["network.location"]
}
FieldDescription
extensionsEach app with id, state (available, active, locked or enforced), reason when locked (offered, feature, policy or image), feature for a missing plan feature, and alwaysOn.
packsGroups that the App Store switches on in one step, such as pack.security (Security Suite), with their extensions. To start or change a session, send the apps themselves.
alwaysOnYour always-activate set: what a session starts with when you send no extensions.
canChangeWith vm: whether you may switch this session's apps.

Switch apps on

POST https://api.guard.ch/v7/vm/:vmId/extensions

Switches apps on in a running session, or changes the settings of ones already on. The session keeps its tabs and state, and a network app replaces the active one. The answer comes once the change is applied. The body is extensions, one or more { id, settings? } from the list above.

curl -X POST https://api.guard.ch/v7/vm/brl-v-v7-q8m2r7k4d1/extensions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "extensions": [{ "id": "mail" }, { "id": "network.residential", "settings": { "country": "DE" } }] }'
FieldDescription
extensionsThe active apps after the change, each with id, settings, activatedAt and problem.
activated, reconfigured, deactivatedWhat the change switched on, changed or switched off (a new exit switches the old one off).
egressOnly when the exit changed: auto, datacenter, residential, proxy or wireguard, with country, location, egressId and, for your own proxy or VPN that does not work, problem.

A refused change names the extension and a reason:

ReasonMeaning
featureYour plan lacks it (status upgrade; feature names the missing plan feature).
workspace_policyYour workspace locked it (policy names it).
enforcedYour workspace enforces it, so it cannot be switched off.
imageThe session's browser cannot carry it, for example a security app on Firefox.
invalid_settingsA setting is wrong; field names it.
unknown, offered, unavailableGuard.ch does not know or offer this app, or it cannot run right now.
conflict, inactive, not_running, not_foundTwo exits at once, a change to an app that is off, a session that is not running, or one that is not yours.

A new exit can also be refused for its own reason, such as residential_limit_reached, invalid_proxy or egress_not_found.

Change an app's settings

PATCH https://api.guard.ch/v7/vm/:vmId/extensions/:id

Changes the settings of an active app, for example the city of the server location. Send the new settings as the body; an app that is off answers reason: inactive. The answer is the same as for switching on.

curl -X PATCH https://api.guard.ch/v7/vm/brl-v-v7-q8m2r7k4d1/extensions/network.location \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "settings": { "country": "US", "location": "us-dal" } }'

Switch an app off

DELETE https://api.guard.ch/v7/vm/:vmId/extensions/:id

Switches one app off, or every security app at once with pack.security. Switching the network app off moves the session to the data centre's own connection; an app your workspace enforces stays on (reason: enforced).

curl -X DELETE https://api.guard.ch/v7/vm/brl-v-v7-q8m2r7k4d1/extensions/pack.security \
  -H "Authorization: Bearer YOUR_API_KEY"

Usage and quota

API usage

GET https://api.guard.ch/v7/web/usage

Totals for the sessions your account started through the API or MCP:

FieldDescription
activeSessionsSessions starting or running right now.
sessionsLast30dSessions started in the last 30 days.
totalMinutesLast30dTheir combined runtime in minutes; running sessions count up to now.
planYour plan: name, runningSessionLimit (sessions at once) and featureProgrammaticAccess.

Plan limits

GET https://api.guard.ch/v7/user/quota

The meters your plan enforces, across every session of your workspace, whether started by hand or through the API:

FieldDescription
has_planWhether an active plan applies.
running_session_limit, running_sessions_used, running_sessions_remainingSessions at once: allowed, in use and free.
cycle_start, cycle_session_limit, cycle_sessions_used, cycle_sessions_remainingWhen the current billing cycle began, and the sessions counted in it. A limit of null means unlimited.
residential_cycle_mb_limit, residential_bytes_used_cycleYour workspace's residential allowance for the cycle in MB (null when unlimited, 0 without a plan) and what is used, in bytes.
memberYour own share: the limits a manager or the plan holds you to (null when only the workspace's limit applies) and your own sessions and residential bytes.

Images and locations

GET https://api.guard.ch/v7/image

The catalogue behind image, the location codes and the residential exits. It needs no key.

curl https://api.guard.ch/v7/image
FieldDescription
imagesBrowser images with id, name, picture, enabled, requires_feature_restricted_images, supports_analysis (can run the security apps) and supports_programmatic. Pick one that is enabled and supports programmatic access.
vpnLocationsCountries with code, name, default (the city a bare country code resolves to) and cities, each with code and location. Both country and city codes work as a location.
residentialResidential coverage: configured, and countries with the country codes it can reach (null: every country).

Limits

  • 3 sessions at once per seat. Sessions you start by hand count too. The whole team shares them, and a workspace manager can limit individual members. When every slot is taken, a new session is refused, not queued.
  • Sessions end on their own. A session ends a few minutes after the last client disconnects, and at expiresAt at the latest: 18 hours after it started. Stop sessions you no longer need to free their slot right away.
  • Starting sessions is rate-limited. Limits per minute, hour and day apply to each API key and each account. A refusal carries code: rate_limited and retryAfterMs.
  • Requests are rate-limited too. The API host accepts about two requests per second from one IP address, with short bursts above that. Beyond it, the answer is denied; wait a moment and try again.
  • Calls aren't billed. Neither requests nor sessions are charged by volume. Residential traffic counts towards your plan's allowance for the billing cycle.

Security

  • An API key has the same rights as your account on these routes. Keep it in a secret store and revoke it on the Agents tab if it ever leaks.
  • Send keys only in the Authorization header, never in URLs, where they end up in logs and browser history.
  • Every connectUrl carries its own token and stops working when the session ends. Don't log or share it.
  • Target pages load in our infrastructure, never on the machine that calls the API.

Need help?

Questions about a route, or missing something for your integration? Get in touch.

Try Guard.ch for 30 days.

Start without a card. Add one for the trial and pay nothing until it ends.

Start 30-day trial