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

# Authentication

> Sign in with the device flow, use your own OpenRouter key, or authenticate with environment variables.

9p supports two modes:

* **Platform** — metered through 9th Protocol, credits and plan limits apply.
* **BYOK** — your own OpenRouter key, calls go direct, we meter nothing.

## Creating an account

Three ways, all landing in the same account:

<CardGroup cols={3}>
  <Card title="GitHub" icon="github">
    One click, identity only — no repo scopes.
  </Card>

  <Card title="Email link" icon="envelope">
    We email a link. No password to choose or forget.
  </Card>

  <Card title="Password" icon="key">
    Classic email and password.
  </Card>
</CardGroup>

**Sign in with GitHub** requests `user:email` and nothing else. Repository access
is a separate, explicit connector — signing in never asks for your code. If you
already have a password account on the same verified GitHub email, signing in
with GitHub links the two rather than creating a duplicate.

**Email links** expire after 15 minutes and work once. Requesting a link for an
address with no account creates one on first use.

## Platform: `9p login`

```bash theme={null}
9p login
```

9p asks the API for a code pair, shows you a short code, and opens
`https://app.9thprotocol.com/device`. Approve it there and the CLI receives
tokens. This is the [OAuth 2.0 Device Authorization Grant (RFC 8628)](https://datatracker.ietf.org/doc/html/rfc8628) —
your password is never typed into or seen by the terminal.

<Warning>
  Only approve a code you just generated yourself. Approving a code someone else
  sent you gives their machine full access to your account and credits. 9th
  Protocol will never ask you to approve a code on someone else's behalf.
</Warning>

Codes expire after 10 minutes and are single-use.

### Headless machines

```bash theme={null}
9p login --paste
```

Prompts for an API URL and an access token from the dashboard. Use this on
servers and containers with no browser.

### Signing out

```bash theme={null}
9p logout
```

Revokes the refresh token server-side and clears `~/.9p/auth.json`. Any BYOK key
in that file is preserved.

## BYOK

Set an OpenRouter key and 9p calls OpenRouter directly:

```bash theme={null}
export OPENROUTER_API_KEY=sk-or-...
9p
```

Or store it so it persists:

```json ~/.9p/auth.json theme={null}
{
  "openrouterApiKey": "sk-or-..."
}
```

In BYOK mode there are no plan locks, so every model OpenRouter offers is
available and the [Auto router](/models) picks from its full ladder. Your key
stays on your machine; the system prompt is assembled locally.

## Environment variables

Environment variables take precedence over `~/.9p/auth.json`, which makes them
the right choice for CI.

| Variable             | Purpose                                                     |
| -------------------- | ----------------------------------------------------------- |
| `NINEP_API_URL`      | Platform API base, e.g. `https://api.9thprotocol.com/v1`    |
| `NINEP_TOKEN`        | Platform access token (used verbatim — 9p won't refresh it) |
| `OPENROUTER_API_KEY` | BYOK key                                                    |
| `NINEP_MODEL`        | Pin a model for the session, bypassing Auto                 |
| `NINEP_AUTO_BIAS`    | `economy`, `balanced`, or `quality`                         |

Resolution order:

1. `NINEP_API_URL` + `NINEP_TOKEN`
2. `~/.9p/auth.json` (`apiUrl` + `token`)
3. `OPENROUTER_API_KEY`
4. `~/.9p/auth.json` (`openrouterApiKey`)

## Token lifetime

Access tokens last 15 minutes; refresh tokens last 30 days and rotate on every
use. 9p refreshes automatically when a stored token is close to expiry and
writes the rotated pair back to `~/.9p/auth.json`.

<Note>
  Tokens supplied via `NINEP_TOKEN` are **not** refreshed — 9p doesn't own their
  lifecycle. For long-running automation, prefer `9p login` on the machine, or
  mint a fresh token per run.
</Note>

If a refresh fails (expired or revoked), 9p stops and tells you to run
`9p login` again rather than failing mid-task.

## Credential file

`~/.9p/auth.json`, created with `0600`:

```json theme={null}
{
  "apiUrl": "https://api.9thprotocol.com/v1",
  "token": "eyJhbGciOi...",
  "refreshToken": "atCOhpxk...",
  "email": "you@example.com"
}
```

Treat it like an SSH key. If it leaks, run `9p logout` from any machine to
revoke that refresh token.
