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

# Remote access

> Pair another device with a machine's dashboard, revoke it, and keep the LAN out

# Remote access

Each machine runs its own Overdeck. That machine owns its projects, agents,
conversations, terminals and credentials, and its dashboard serves only its own
state. Another device (a laptop, a tablet, a second desktop) reaches a machine
by **pairing** with it. Pairing gives that device its own revocable session.
The machine's root internal token never leaves the machine.

<Note>
  Pairing from the terminal (`pan pair`, `pan devices`) is in v0.64.0. Settings → Anywhere,
  Settings → Access Tokens and scoped API tokens (`pan token`) arrive in the next release
  (v0.65.0).
</Note>

Every machine has a stable public identity. `GET /api/environment` returns it
without a credential:

```json theme={null}
{
  "descriptorVersion": 1,
  "environmentId": "3f0c…",
  "label": "desk",
  "platform": { "os": "linux", "arch": "x64" },
  "serverVersion": "0.63.0",
  "protocolVersion": 1,
  "capabilities": { "pairing": true, "deviceSessions": true, "terminalAuth": true }
}
```

The descriptor contains no paths, usernames or tokens.

<Warning>
  Do not expose a dashboard on a network until `GET /api/environment` reports
  `capabilities.terminalAuth: true`. That value means every `/ws/*` connection
  (terminals, RPC, voice) requires a credential.
</Warning>

## Pair a device

### From the dashboard

1. On the machine, open **Settings → Anywhere** and click **Pair a device**.
2. Pick the address the other device opens, for example your Tailscale name.
   If it is not in the list, choose **Another address…**, type it, and click
   **Add to trusted addresses**. It is trusted at once, with no restart.
   **Use this machine only** makes a link that works only on this machine.
3. Click **Create pairing link**. Scan the QR code with the other device, or
   copy the link and open it there. The dialog names the device when it pairs.

Only a browser on the machine itself can create pairing links or add
addresses. A paired device that opens the dialog sees an explanation instead
of a link.

### From the terminal

1. On the machine, run `pan pair` with an address the other device can reach:

   ```bash theme={null}
   pan pair --url https://desk.tailnet.ts.net
   ```

   It prints a URL such as `https://desk.tailnet.ts.net/#pair=odp_…`, the
   expiry time, and the raw credential for manual entry. Use `--json` for
   `{ url, credential, expiresAt }`. `--label <name>` records a name for the
   pairing in the activity log.
2. Open that URL on the other device. The dashboard exchanges the credential
   for a device session, removes it from the address bar, and loads.

The pairing credential:

* works once, and expires 10 minutes after it was created;
* lives only in the running dashboard's memory, so a dashboard restart
  invalidates every credential that has not been used yet;
* always travels in the URL fragment (`#pair=`), never in a query string, so
  it never reaches a server or proxy log.

Without `--url`, `pan pair` uses this machine's own dashboard address. When
that address is `localhost` (or `127.0.0.1`, `::1`, `*.localhost`), the URL only
works on this machine, and `pan pair` says so.

`pan pair` talks to a dashboard started by `pan up`, which shares this
machine's internal token. A paired device cannot create more pairing
credentials.

### Open one conversation on another screen

To look at one conversation on your phone or another computer while this
machine keeps running it, open the conversation's ⋮ menu (in its header or on
its row) and choose **Continue on another device**. The **Open on another
screen** tab shows a link to that conversation, a **Copy** button and a QR
code.

The tab offers only trusted addresses other devices can reach, never a loopback
address. If there is none yet, type one and click **Add an address**.

In a browser on this machine, **Also pair the device** is checked by default.
Click **Create pairing link** to add a one-time pairing credential to the link
(`…/conv/<id>#pair=odp_…`): an unpaired device that opens it pairs and lands on
the conversation in one step. The credential follows the same rules as above:
it works once, expires after 10 minutes, and travels only in the URL fragment.
On a paired device the option is not shown, and the link works for devices
that are already paired. Available since v0.65.0.

### Open the dashboard at a non-default address

The dashboard only accepts browser requests from origins it trusts. To open it
at an address other than the default (for example through Tailscale), add that
address in the pair dialog with **Add to trusted addresses**. It takes effect on
the next request, with no restart, and is stored in
`~/.overdeck/trusted-origins.json`:

```json theme={null}
{ "version": 1, "origins": ["https://desk.tailnet.ts.net"] }
```

Loopback addresses (`localhost`, `127.0.0.1`, `::1`, `*.localhost`) cannot be
added: they are already trusted for this port and never help another device.
To remove an address, edit the file and restart the dashboard.

`OVERDECK_TRUSTED_ORIGINS` still works and is merged with the file. Separate
several origins with commas, and set it before starting the dashboard:

```bash theme={null}
export OVERDECK_TRUSTED_ORIGINS=https://desk.tailnet.ts.net
pan up
```

A later release discovers and advertises endpoints automatically
([PAN-4403](https://github.com/eltmon/overdeck/issues/4403)).

## List and revoke devices

**Settings → Anywhere** lists paired devices with their scopes, when each was
paired and when it was last seen. **Revoke** there has the same effect as
`pan devices revoke`. From the terminal:

```bash theme={null}
pan devices list            # id, name, created, last used, revoked
pan devices revoke <id>
```

Revoking a device stops its next HTTP request and closes its open WebSocket and
live event-stream connections at once. If the dashboard is not running,
`pan devices revoke` writes the revocation straight into the registry; no
connections can be open then. The running dashboard also rereads the registry
every 5 seconds, so a revocation from any process takes effect within 5 seconds.

Device sessions are stored in `~/.overdeck/access-tokens.json` (mode `0600`).
The file keeps only a SHA-256 hash of each token. If the file is corrupt, the
dashboard accepts no device session and logs an error naming the file.

## Anywhere status

The **Anywhere** card shows whether other devices can use this machine. It
appears in **Settings → Anywhere** and on the **Health** page, and shows:

* **This machine:** its label and the start of its environment id.
* **Reachable at:** the trusted addresses another device can open, or "Only
  this machine".
* **Paired devices:** how many device sessions are active (not revoked).
* **Session Vault:** "Not set up", "Locked on this machine", "Key rotation
  unfinished", or "Ready" with its backend. **Set up** opens Settings → Session
  Vault, which shows the `pan vault setup <git-url>` command to run in a
  terminal. Setting up from the dashboard is planned
  ([PAN-4446](https://github.com/eltmon/overdeck/issues/4446)). Not using the
  vault is a valid choice, not a problem.

Below that, the card lists each problem with its fix:

| Problem | Fix |
| - | - |
| No address another device can reach is trusted yet. | **Add an address** opens the pair dialog, where you can add one. |
| Session Vault is set up, but this machine cannot open it. | **Open Session Vault** goes to Settings → Session Vault, which shows the command to run in a terminal on this machine: `pan vault join <backend>`, then the vault passphrase or the recovery phrase. Unlocking from the dashboard is planned: [PAN-4446](https://github.com/eltmon/overdeck/issues/4446). |
| A vault key rotation started on this machine has not finished. | **How to fix** links to [Session Vault](/configuration/session-vault). Finish the rotation on this machine before syncing again. |
| This machine's identity file is unreadable. | **How to fix** links to this page. Repair or restore `~/.overdeck/environment-id.json`; it is never re-created automatically. |

The card reads `GET /api/anywhere/status`, which any dashboard credential may
call.

## Scoped API tokens

A paired device can do everything the dashboard can. A program that needs only
part of that (an event sidecar, Hermes, a script that sends messages to agents)
should hold a **scoped API token** instead. A leaked scoped token exposes only
the routes its scopes name, and it can never create a session, a pairing
credential or another token.

Create one on the machine:

```bash theme={null}
pan token create sidecar --scopes read:events
pan token create notifier --scopes read:state,tell --json   # { id, name, scopes, token }
```

`pan token create` prints the token (`odk_…`) once. Store it right away; it
cannot be shown again. A running dashboard accepts a new token within 5 seconds.

The scopes:

| Scope | Allows |
| - | - |
| `read:events` | The live event stream (`GET /events/stream`) and `GET /events/version`. |
| `read:state` | Issue, agent, agent-directory, Flywheel and pipeline reads. |
| `read:conversations` | Conversation lists, one conversation, its messages and its summary. |
| `tell` | Sending a message to an agent or a conversation. |
| `operate` | Everything `tell` allows, plus terminal WebSockets (`/ws/terminal`). |
| `admin` | Everything, like a paired device. |

Anything not in the table needs `admin`. A request whose token lacks the scope
gets `403` with `{ "error": "insufficient_scope", "missingScope": "<scope>" }`.

Send the token on every request as `Authorization: Bearer odk_…`. It works over
HTTP, the event stream and WebSocket upgrades. It does not work as a cookie or a
URL parameter, and a request carrying it needs no `Origin` header or CSRF token.

```bash theme={null}
curl -N -H "Authorization: Bearer odk_…" https://desk.tailnet.ts.net/events/stream
```

List and revoke tokens:

```bash theme={null}
pan token list              # id, name, scopes, created, last used, revoked
pan token revoke <id>
```

Revoking a token stops its next request and closes its open WebSocket and
event-stream connections at once. If the dashboard is not running,
`pan token revoke` writes the revocation into the registry, and a dashboard that
starts later never accepts the token. Only this machine's internal token or root
session can create or revoke tokens. The dashboard API is
`GET`/`POST /api/access-tokens` and `DELETE /api/access-tokens/:id`.

You can also manage tokens in **Settings → Access Tokens**: the table shows
each token's name, scopes, created, last used and revoked times; **Create
token** shows the plaintext once with a Copy button; **Revoke** asks for
confirmation. A browser signed in as a paired device can see the list but
cannot create or revoke tokens; the section explains why.

## What the LAN can reach

The dashboard binds every interface. Every `/api/*` and `/events/*` request that
does not come from this machine must carry a credential (the root session, a
device session, a scoped API token, or the internal token), or it gets `401`.
A scoped token is always judged by its scopes, even from this machine. "From this machine"
means a loopback address, or the host-local Traefik and Docker networks that
front `overdeck.localhost`.

These routes answer without a credential:

| Route | Why |
| - | - |
| `GET /api/health` | Health probe. |
| `GET /api/environment` | The public descriptor. |
| `OPTIONS` and `POST /api/dashboard/session` | The session mint; it checks its own credential. |
| `POST /api/pairing/exchange` | Pairing itself. Ten failed attempts in 60 seconds lock it for 60 seconds. |
| `POST /api/webhooks/github` | Verified by its HMAC signature. |

`GET /events/stream` also accepts its `OVERDECK_EVENTS_TOKEN` bearer token when
that variable is set, so an external consumer such as a TTS sidecar keeps
working. A `read:events` scoped token works there too, set or not. The dashboard's static page files stay public.

## Local reverse proxies: `require_token_mint`

A local reverse proxy (Tailscale Serve, cloudflared, a Traefik route) connects
to the dashboard from `127.0.0.1`, so every visitor looks like a local caller.
Turn on `require_token_mint` whenever such a proxy forwards outside traffic.
Idle dashboard terminals stay connected behind proxies with idle timeouts of 30s
or more because the terminal WebSocket exchanges a ping/pong control frame every
20s; programmatic clients opt in with `?heartbeat=1` ([PAN-4434](https://github.com/eltmon/overdeck/issues/4434)).

```yaml theme={null}
# ~/.overdeck/config.yaml
dashboard:
  require_token_mint: true
```

With it on:

* A browser can only get a root session by presenting the internal token (for
  example the one-time `#overdeck_token=<token>` URL fragment, where the token is
  the contents of `~/.overdeck/internal-token`). Being local no longer counts.
* A local request that carries a proxy header (`X-Forwarded-For`,
  `X-Forwarded-Host` or `Forwarded`) counts as remote, so it needs a credential.

A browser that already holds a session keeps working. A new browser behind the
proxy needs the `#overdeck_token=` fragment or pairing. You can also turn it on
in **Settings → Access Tokens → Require a token to sign in**. A Settings change
applies at once. A hand edit of `config.yaml` needs a dashboard restart. The key
is read only from the global `~/.overdeck/config.yaml`, never from a project
`.pan.yaml`.

## What pairing is not

* **The relay is optional reachability.** The Overdeck relay
  ([PAN-2356](https://github.com/eltmon/overdeck/issues/2356)) is one way to
  reach a machine. It is not where multi-machine state lives: each machine
  keeps its own.
* **Moving a conversation to another machine is Session Vault.** Pairing lets a
  device use a machine's dashboard. To resume a conversation on a different
  machine, use [Session Vault](/configuration/session-vault).

## What comes next

* A desktop connection list, environment switcher and version gating:
  [PAN-4402](https://github.com/eltmon/overdeck/issues/4402).
* Discovered HTTPS and Tailscale endpoints and advertised origins:
  [PAN-4403](https://github.com/eltmon/overdeck/issues/4403).
* Launching Overdeck on an SSH host from the desktop app:
  [PAN-4404](https://github.com/eltmon/overdeck/issues/4404).


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