PromptOwl + ContextNest Docs

Authentication & API Keys

Updated Open in ChatGPTOpen in Claude

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).

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.
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

{ "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:

{ "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.

{ "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).
  • 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.

    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 and 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.

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.