---
description: >-
  Authenticate Watcher on headless machines with device-code sign-in, organization API keys, external credential helpers, or shared container credentials.
---

> Part of the Watcher docs. Index of all pages, with summaries: https://watcher-docs.apolloresearch.ai/llms.txt
> Before upgrading Watcher, read the release pages and full changelogs for every version crossed: https://watcher-docs.apolloresearch.ai/releases/index.md
> Found an issue with this page? Report it: https://github.com/ApolloResearch/watcher-bin/issues/new?title=Docs%20feedback%3A%20Signing%20in%20without%20a%20browser&body=Page%3A%20Signing%20in%20without%20a%20browser%0AURL%3A%20https%3A%2F%2Fwatcher-docs.apolloresearch.ai%2Fclient%2Fheadless-sign-in%2F%0A%0A

# Signing in without a browser

The usual way to sign the [Watcher client](../concepts/index.md) in is the
local UI, which hands off to your identity provider in a browser on the same
machine. A headless server has no browser to hand off to.

Use `watcher login` for a person who approves SSO from another device, an
organization API key for an unattended SSO deployment, or an
[external credential helper](credential-helpers.md) when a proxy deployment
requires tokens from your company CLI.

## Choose how the machine authenticates

| The machine | Use | Sessions are attributed to |
| --- | --- | --- |
| A server or VM a person reaches over SSH, even if nobody stays logged in | `watcher login` | The person who approved the sign-in |
| Unattended: CI, a container nobody logs in to, a shared runner | `WATCHER_API_KEY` | The organization, not a person |
| A container on a developer machine that is already signed in | The host's credentials, bind-mounted | The host's user |
| A proxy deployment that requires renewable bearer tokens | [External credential helper](credential-helpers.md) | The identity validated by your gateway |
| A proxy deployment with another gateway authentication mechanism | Configure the gateway and any required [client headers](../self-hosting/client-deployment.md#sending-additional-headers) | The identity your proxy asserts |

The SSO options are below. For the container cases, see
[Containers](containers.md#authenticating-the-container), which compares all
of them for that setting. On a proxy-authenticated deployment the gateway
signs requests in, so terminal sign-in does not apply and `watcher login`
says so.

## Terminal sign-in

### Before you start

- The [client](index.md) is installed on the machine, and either
  `watcher target set --cloud` or
  `watcher target set --url https://<your-watcher-host>` has recorded which
  backend it talks to.
- The deployment uses SSO. Terminal sign-in is a sign-in flow, so it only
  applies where there is an identity provider to sign in to.
- You have a browser on some other device: a laptop, a phone. It does not
  need any network route to the headless machine.

### Sign in

Run this on the headless machine, over SSH:

```bash
watcher login
```

`watcher auth login` is an alias for the same sign-in flow.

It prints a URL and a short code, then waits:

```text
Signing in to https://watcher.example.com

On any device with a browser, open:

    https://id.example.com/device?user_code=ABCD-EFGH

and confirm it shows this code: ABCD-EFGH

Waiting for approval (the code expires in 5 minutes; Ctrl-C to cancel)...
```

Open that URL on the other device, sign in as you normally would, and check
that the page shows the same code the terminal printed. Confirming the code is
what ties your approval to this specific machine, so compare the two rather
than clicking through.

The command returns once you approve:

```text
Signed in as person@example.com.
Credentials saved to /home/dev/.apollo_monitor/credentials/production.json.
```

Those are the same credentials the browser flow writes, in the same place, so
the hooks, the background client, and token refresh all behave identically from
here on. You do not need to repeat this after a reboot: the credentials are
refreshed on use, including after the machine has been off for a while.

### Keep the client running

Signing in stores credentials. It does not start the client, and on a server
nobody stays logged into, starting it once is not enough either.

Run the client once so it installs the [login agent](../concepts/index.md),
the service that starts it and keeps it running. It starts in the background
and returns control to the terminal:

```bash
watcher
```

On Linux that agent is a systemd user unit, which starts at login and stops
with the user's last session. A server running agents unattended needs the
user instance to survive logout and reboot. Enable lingering for the user you
signed in as, the one whose home holds the credentials:

```bash
loginctl enable-linger <user>
```

Without lingering, sign-in and `watcher doctor` both look healthy while
someone is logged in, and nothing records once they disconnect.

### Verify

```bash
watcher doctor
```

The `auth` check confirms that credentials exist for this target and that a
live authenticated request succeeds. Then start a coding agent session on the
machine and confirm it appears in the [Analyzer](../concepts/analyzer.md).

### If it does not complete

- **The code expired.** It is valid for the few minutes the command prints, so
  a delay in getting to the other device runs it out. Run `watcher login`
  again for a fresh code.
- **You cancelled or denied the sign-in in the browser.** Run `watcher login`
  again.
- **The command reports that the server predates device login.** The backend is
  older than the client and does not offer the endpoints yet. Update the
  backend, or sign in from a browser: start the client on a machine that has
  one, or port-forward this machine's local UI (port 8228 by default) to a
  machine that does.
- **The command reports that the deployment does not use SSO sign-in.** It
  authenticates through a reverse proxy, and the gateway supplies the identity.
  See [Sending additional headers](../self-hosting/client-deployment.md#sending-additional-headers).

## Unattended machines

Terminal sign-in needs a person to approve it. A machine nobody signs in to,
such as a CI runner or a container started by an agent, authenticates with an
organization API key instead:

```bash
export WATCHER_API_KEY=<key>
```

The hooks and the client both read it from the environment, so set it where
the machine's processes will actually see it: the runner's environment
configuration, the container's run command, or the service definition that
starts the client. Exporting it in one SSH session leaves every other process
without it.

Create and revoke these keys on the API Keys tab in the Analyzer, described in
[Organization settings](../concepts/organization-settings.md#api-keys).

Sessions recorded this way are attributed to the organization rather than to a
person, which is the tradeoff for not having anyone to attribute them to. Where
a person does own the machine, prefer `watcher login`, so its sessions carry
their identity.
