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

# Where requests run

> Every API request names the place that opens its connection — this device, the desktop app, or the workspace's server — and lets you choose where more than one can.

Every request editor carries a place button after **Save**: a run glyph
beside the place's own mark — this browser's logo, this machine's
operating-system mark, or the server's icon, the same one the workspace
switcher shows for it. Hover it for the words (**Runs locally: in this
browser extension**, **Runs locally: in the desktop app**, **Runs
remotely: on** the workspace's server) and click it for
the reason, the alternatives and, where this surface cannot run the
request, the way to get it running. Nothing is forwarded silently, and
nothing is downgraded silently: a request that cannot run where you are
says so and names the place that can.

## The three places

A request opens its connection in one of three places. They are roles,
not machines: the same setting means the same thing on every device.

| Role | What it is |
| - | - |
| **The surface you send from** — *Browser extension* in the extension, *Desktop app* in the app | It opens the connection itself — the desktop app's own network stack, the extension's service worker for HTTP, the extension's browser socket for WebSocket and MQTT over `ws://` / `wss://`. |
| **Desktop app**, picked from the extension | The desktop app on this device opens the connection on the request's behalf. It is reachable from the extension when both run on the same machine; the app is always its own place. |
| **Server** | The server that provides the workspace opens the connection. An Org workspace has one by construction; a personal workspace has none. |

**Automatic** is the default everywhere: the request runs on this device
when it can, otherwise on the one place that can. The place button shows
the resolved place, never the word "Automatic".

## The matrix

Which places can open a request depends only on the transport the
request needs — a raw TCP socket, HTTP/2 trailers, a node-only knob —
and on which places are connected right now.

| Kind | Extension | Desktop app | Web app (served by a server) |
| - | - | - | - |
| HTTP, SSE | Here. Alternatives: the desktop app, the server. | Here. Alternative: the server. | Resolved in the tab; the server opens the connection. |
| GraphQL query | Here. Alternatives: the desktop app, the server. | Here. Alternative: the server. | Resolved in the tab; the server opens the connection. |
| GraphQL subscription | Here, over the browser socket. Alternatives: the desktop app, the server. | Here. Alternative: the server. | Resolved in the tab; the server opens the connection. |
| WebSocket | Here, over the browser socket. Alternatives: the desktop app, the server. | Here. Alternative: the server. | Resolved in the tab; the server opens the connection. |
| MQTT over `ws://` / `wss://` | Here, over the browser socket. Alternatives: the desktop app, the server. | Here. Alternative: the server. | Resolved in the tab; the server opens the connection. |
| MQTT over `mqtt://` / `mqtts://` | **Needs the desktop app** or the server — a browser page cannot open a raw TCP socket. Automatic picks whichever is connected. | Here. Alternative: the server. | Resolved in the tab; the server opens the connection. |
| gRPC | Resolved here; the connected desktop app, else the workspace's server, opens the connection — the browser has no HTTP/2 stack that exposes trailers. With both connected, Automatic picks the desktop app and the server is offered beside it. Without either: **Needs the desktop app**. | Here. | Resolved in the tab; the server opens the connection. |

An alternative is offered only while that place is connected: the
desktop app's row appears when the extension is paired with a running
app, the server's row when the workspace's server is reachable.

A session over the browser socket cannot apply the node-only knobs —
custom handshake headers, disabled SSL verification, the credential's
handshake header. The place button names the ones the request has set, so you
can move the session to a place that applies them.

## Choosing a place

A place names *this device's* topology — the desktop app on this
machine, the server this device is signed in to — so it is kept on
this device and never syncs to anyone else. Two layers, the more
specific one winning:

1. **The request.** Click the place button and pick a row under **Run
   on**. The popover lists every place this surface knows — **Browser
   extension**, **Desktop app**, **Server** — each with its mark; the
   ones that cannot run the request stay visible but disabled, with
   the reason and the way to fix it (open or download the desktop app,
   open Backup and Sync for a server, or *Available in a server
   workspace* for a personal one). In a served browser tab the Browser
   extension and Desktop app rows are there for discovery alone — a
   tab reaches neither — under *Install* and *Download*; the tab's one
   place is its server. A pick is a draft edit: the request
   runs there at once, and **Save** keeps it — with the request, on
   this device. The **Runs on** row on the request's **Settings** tab
   is the same value; *Reset to automatic* in the popover clears it.
2. **Global.** **Settings › API Requests › Where requests run ›
   Execution place**, applying to every kind: **Automatic** and the same
   places the picker lists on this surface, in the same words. A request
   with no place of its own follows it; the picker selects the place it
   resolved to and offers no *Reset to automatic*. The served tab has
   one place, so it carries no global row.

A role the current surface cannot honour is never honoured silently:
the place button says **Cannot run on the desktop app yet** (or the
server's name) on hover, its row stays selected but disabled, and the
eligible places are there to pick back.

When a request cannot run on this surface at all — an `mqtts://`
session or a gRPC call on the extension — the disabled **Send**,
**Connect** or **Invoke** button says so on hover and offers **Choose
where it runs**, which opens the same popover: the desktop app, or a
server where no desktop app can be installed.

## What a delegated send carries

The surface you sit at is the request's context. It resolves everything
it holds — variables, the environment, vault secrets, TOTP codes,
client certificates, files, cookies, the pre-request script — and only
then hands the fully resolved request to the chosen place, which opens
the connection and streams the raw response or frames back. The
context does everything after: the post-response script, cookie
capture, publish-on-run, the saved response.

Where the resolved values travel is stated in the place button's
popover:

* **To the desktop app on this device** — nothing the vault does not
  already sync there. The popover reads "Runs on the desktop app. This
  browser fills in the request first, then hands it to the desktop app
  on this computer, which opens the connection."
* **To the server** — the resolved values, secrets included, travel to
  it, as they would through any agent or proxy. The popover reads "Runs
  on *server*. The request is filled in here first, variables and
  secrets included, then sent to *server*, which opens the connection."

A knob the place cannot apply is named on the same popover, never
dropped in silence. The one such knob today is the context's cookie
jar: a delegated connection never sees this surface's jar, so the
popover reads "Not applied on *place*: the cookie jar."

A `{{vault.…}}` reference the context does not hold fails before the
send leaves, with the message and the action to unlock it here — the
requirement is always on the surface you send from, never on the
place.

## Scripts run where you are

Pre-request and post-response scripts run in the context, never at the
place, whatever the place's own script posture:

* **The extension** runs them in its sandboxed Safe runtime.
* **The desktop app** runs them under the mode you choose on the
  request's **Settings** tab — **Safe mode** or **Developer mode**, per
  workspace, on this device only.
* **The web app** runs them in the tab's own sandboxed Safe runtime —
  the `oh.*` script API only, no filesystem, no process access, no
  module loader. A browser tab has no Developer mode, so the Settings
  tab states **Safe mode** as a fact rather than offering a choice.

Each run records the mode it executed under on the response.

## Consent, attribution, refusal

The place that opens a connection on another surface's behalf decides
whether it does so. In the desktop app, under **Backup and Sync › Your
devices**, two switches gate it: **Allow this device's browsers to send
requests** (on by default — pairing is the consent) and **Allow other
connected devices to send requests** (off by default). A server has the
same two tiers with the opposite default for other devices: it exists
to serve the people it admitted, so **Let connected devices run requests
on this server** starts on, and an admin turns it off or on under
**Server Admin › Server** (headlessly, `ohd config set
backend.allowRemotePeerExecute false` while the daemon is stopped).
Sending also requires write access to the request's workspace at that
place, and every delegated send is audited there.

The device sending has a say too. A send to a server carries the
request fully resolved, secrets included, so **Settings › API Requests
› Run requests on a server** (on by default, never synced) lets you keep
every request from this device off any server: the Server place then
reads *Turned off in Settings* with a link back to the row, Automatic
never resolves to it, and a request that asked for the server says so
instead of running.

Every response and every session timeline says which place answered:
**Sent from** *name* on the meta strip, with the details in its popover.

A place that refuses shows the refusal on the response itself, naming
the switch to turn on and where — never a silent failure.

## When the place button asks for the desktop app

On the extension, a request that no connected place can open — an
`mqtts://` session or a gRPC call with neither the desktop app nor the
workspace's server connected — turns the place button the warning
colour and says **Needs the desktop app** on hover. The popover's
action follows what is installed:

| State | Action |
| - | - |
| The app is running and paired | **Open in the desktop app** |
| The app is installed and paired, but not running | **Open app** |
| The app is installed but never paired | **Connect** |
| No app on this device | **Download** |

Composing and saving the request works regardless; only the connection
waits. For MQTT, switching the address to `ws://` or `wss://` runs the
session here instead.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.