Skip to main content
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. 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. 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. 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: 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.