
# Authentication & API Keys

ContextNest Community distinguishes **two kinds of key**, and it is worth keeping
them straight:

| Key | Prefix | What it is | Who holds it |
|---|---|---|---|
| **API key** | `cnst_...` | A per-user credential that authenticates HTTP/MCP requests. | Every user; you can hold several. |
| **License key** | `pk_...` | The instance's PromptOwl Community License. It licenses the *server*, not a request. | One per instance; owned by a PromptOwl account. |

A `cnst_` key gets you *in*; the `pk_` key is what keeps the server writable at
all. This page is about the `cnst_` keys and how a request authenticates. For the
license see [Self-Hosting (Community Edition)](https://nest.promptowl.ai/d/promptowl-documents/protocol/self-hosting-community-edition).

## How a request authenticates

The server accepts two methods, evaluated in order:

1. **Cookie session** — `cnst_session=<id>`, set by `POST /auth/login` and
   `POST /auth/register`. This is the browser default.
2. **Bearer API key** — `Authorization: Bearer cnst_<token>`, minted by
   `POST /auth/keys`. Use this for scripts, service accounts, Postman, and
   remote CLIs.

```bash
curl -H 'Authorization: Bearer cnst_<your-token>' http://localhost:3838/nests
```

In `AUTH_MODE=open` most read endpoints bypass auth entirely — every request is
attributed to the anonymous admin user. In `AUTH_MODE=key` (the default for any
multi-user or internet-facing deployment) every request needs one of the two
methods above.

## `cnst_` API keys — the multiple-named-keys model

A user may hold **several** API keys — one per place they connect from — so
rotating the credential for one client doesn't disconnect the rest. The key
format is `cnst_<64-hex>`.

### Minting a key — `POST /auth/keys`

```json
{ "label": "cli", "nest_id": "optional — scope the key to one nest" }
```

- **`label` is required.** It must be unique among the caller's keys (compared
  case-insensitively, after trimming) and be at most **60 characters**. Once the
  plaintext is gone the label is the only thing that tells two keys apart.
- **`400`** if the label is missing, blank, non-string, or over 60 chars.
- **`409`** on a duplicate label, or at the **per-user ceiling of 20** keys.
- **`nest_id`** (optional) scopes the key to a single nest — a service-account
  credential. Omit it for a **user-level key** that works on every nest the user
  can access (this is what the Connect dialog mints). A nest-scoped key is
  rejected on any other nest's MCP/API paths.

The response returns the plaintext **once**:

```json
{ "api_key": "cnst_...", "id": "uuid", "key_prefix": "cnst_abc123...", "label": "cli" }
```

### Listing — `GET /auth/keys`

Returns the caller's keys (id, prefix, label, `nest_id`, `created_at`,
`last_used_at`), oldest first. Plaintext is never returned.

### Rotating — `POST /auth/keys/rotate`

Replaces **one** key and returns its new plaintext; the caller's other keys are
untouched.

```json
{ "key_id": "uuid", "label": "optional", "nest_id": "optional" }
```

- `key_id` names the target. Omitting it is accepted **only** when the account
  holds exactly one key (pre-multi-key clients); with several keys and no
  `key_id` the server answers `400` rather than guessing. `404` if the id isn't
  the caller's.
- **Scope on `nest_id`:** absent → keep the prior scope; `null` (or `""`) → clear
  to a user-level key; a nest id → scope to that nest.
- **Label on rotate:** absent keeps the prior label; a label matching a
  *different* key of the caller's is `409`; renaming a key to its current name is
  fine.

### Revoking — `DELETE /auth/keys/:keyId`

Revokes one key; `404` if the id isn't the caller's. Anything still using it
stops working immediately.

> **Rotation is atomic and per-key.** Because each connection carries its own
> named key, you rotate or revoke one credential without knocking the others
> offline. Rotate regularly on internet-facing deployments (`DELETE /auth/keys/:id`
> then `POST /auth/keys`, or `POST /auth/keys/rotate`).

## The `pk_` license key — different job

The `pk_...` key is **not** a request credential. It licenses the whole server:

- Unlicensed instances still boot and serve reads, but every write returns `503`
  until a valid key is installed.
- Install it via the browser **License Setup Page** or the `PROMPTOWL_KEY` env
  var (see [Self-Hosting](https://nest.promptowl.ai/d/promptowl-documents/protocol/self-hosting-community-edition)).
- The **admin identity follows the license** — the admin user is whichever
  PromptOwl account owns the installed key, resolved live per request.
- A long-poll watcher tracks revocation; a revoked license blocks writes within
  seconds.

Never treat a `pk_` key as an API token, and never send a `cnst_` key where a
license is expected — they authenticate different things.

## CLI / remote clients — env-var auth

The `ctx` CLI authenticates with a `cnst_` key supplied through the environment,
not stored inline in commands. Mint a user-level key from the Connect dialog (or
`POST /auth/keys` with no `nest_id`), then hand it to the CLI:

- **Remote aliases** (`ctx --vault <alias> …`). There is no fixed variable name:
  you choose one when you register the server, and the registry stores only that
  name, never the key.

  ```bash
  export TEAM_NEST_TOKEN=cnst_...
  ctx vault add team --url https://nest.example.com/mcp --bearer-env TEAM_NEST_TOKEN
  ```

  If the named variable is unset at call time, the command fails with
  `CONFIG_ERROR` instead of calling the server unauthenticated.
- **`ctx push`** reads `CONTEXTNEST_API_KEY`. `--key cnst_...` also works but is
  visible to other processes and lands in shell history.

Scope the key to one nest (`nest_id`) for a locked-down service account. See
[Remote & Governed Nests](https://nest.promptowl.ai/d/promptowl-documents/protocol/remote-nests/remote-governed-nests) and
[CLI Reference](https://nest.promptowl.ai/d/promptowl-documents/protocol/cli-reference).

## OIDC single sign-on

For human logins, Community Edition supports generic **OpenID Connect** SSO
against any spec-compliant IdP (Microsoft Entra ID and Google are first-class
presets; Okta / Keycloak work as standard issuers):

- `GET /auth/oidc/login` starts an authorization-code flow with PKCE and redirects
  to your IdP.
- `GET /auth/oidc/callback` verifies the returned ID token (issuer, audience,
  nonce, signature against the issuer's JWKS), then starts a normal browser
  session — identical to a password login.
- `GET /auth/oidc/logout` performs an RP-initiated logout where the IdP supports
  it.

Users are looked up by email; unknown emails are JIT-provisioned when
`OIDC_AUTO_PROVISION` is on, and `OIDC_ALLOWED_DOMAINS` can restrict which email
domains may sign in. Configure via the `OIDC_*` env vars or **Settings → Single
sign-on** (superadmin). OIDC sessions still act through the same session/cookie
mechanism as everything else.

## PromptOwl native connection (service-ticket auth)

The official PromptOwl deployment can connect on its users' behalf **without
storing a `cnst_` key**: it mints a short-lived HS256 **service ticket** signed
with the shared `OFFICIAL_COMMUNITY_SSO_SECRET`. The ticket carries
`scope: "mcp"`, an `aud` bound to this server's `PUBLIC_BASE_URL`, a ~5-minute
`exp`, and no `jti`; it resolves to an already-existing account by email (native
access never auto-provisions). It is accepted as a `Bearer` on the MCP retrieval
paths only. On self-hosted servers `OFFICIAL_COMMUNITY_SSO_SECRET` is unset, so
this path is inert and you use a real `cnst_` key. See
[Connect an Agent](https://nest.promptowl.ai/d/promptowl-documents/platform/hosted-nest/connect-an-agent-mcp).

## Related

- [CLI Reference](https://nest.promptowl.ai/d/promptowl-documents/protocol/cli-reference) — `ctx push` and `ctx vault add --bearer-env`
- [REST API Reference](https://nest.promptowl.ai/d/promptowl-documents/protocol/rest-api-reference) — the `/auth/*` endpoints in context
- [Self-Hosting (Community Edition)](https://nest.promptowl.ai/d/promptowl-documents/protocol/self-hosting-community-edition) — auth modes and the license
- [Connect an Agent (MCP)](https://nest.promptowl.ai/d/promptowl-documents/platform/hosted-nest/connect-an-agent-mcp) — using a key over MCP

## Sources

- `API.md` (Community server repo) — §1 Auth (`/auth/keys*`, register, login, invite, OIDC) and §9 MCP (PromptOwl native connection / service tickets).
- `CONFIGURATION.md` (Community server repo) — `AUTH_MODE`, `PROMPTOWL_KEY`, the `OIDC_*` vars, and `OFFICIAL_COMMUNITY_SSO_SECRET`.
- `README.md` (Community server repo) — the "several API keys per account" feature and license behaviour.
- `packages/cli/src/index.ts` and `packages/engine/src/registry.ts` (ContextNest repo) — `ctx push` key handling and remote `bearer_env` auth.
