Nova Admin Docs
Guides

Authentication

How callers authenticate to the Nova Admin API — session cookies for the dashboard, API keys with scopes for programmatic access.

The Nova Admin API accepts two authentication mechanisms. Every endpoint requires one of them; there are no anonymous API routes.

Session vs. API key

MechanismHeader / cookieUsed byPermission model
Session cookieadmin-session cookieThe dashboard UI (browser)Full access — a valid session is treated as a workspace member with the wildcard scope *.
API keyAuthorization: Bearer <key>Scripts, agents, external clientsCoarse scopes (and optional fine-grained grants).

A session cookie is minted by Firebase Auth after passwordless sign-in. Programmatic callers should use an API key instead — sessions are not meant to be scripted.

Using an API key

Pass the key as a Bearer token:

curl https://admin.example.com/api/v1/agents \
  -H "Authorization: Bearer $NOVA_API_KEY"

Keys are created and revoked in the dashboard under Settings → API Keys. Only a key's prefix and a hash are stored — the full secret is shown once at creation time and cannot be recovered.

Scopes

API keys carry one or more coarse scopes. The vocabulary is intentionally small:

ScopeGrants
readRead-only access to agents and threads.
writeCreate and update agents, threads, and messages.
adminFull access including API key management and settings.

Routes may require finer-grained scopes such as agents:read or chat:write. The coarse scopes satisfy those by the following rules (hasScope in lib/auth/api-key-auth.ts):

  • admin satisfies everything.
  • write satisfies any *:write and any *:read requirement.
  • read satisfies any *:read requirement.
  • A direct match always works (for example read satisfies read).
  • The wildcard * (session callers) satisfies everything.

Fine-grained grants (MCP)

Keys that drive the unified MCP endpoint may additionally carry grants — generic permission strings using the grammar <resource>(:<op>)?(:<id>)?:

tool:*
agent:invoke:engineer
memory:read
*

When a key has grants, they take precedence over scopes for permission checks while the dashboard's read/write/admin picker keeps working unchanged. The grant grammar is validated on write so malformed strings (tool::sentry, trailing whitespace) can't be stored.

Errors

Authentication and authorization failures return a JSON { "error": "..." } body:

  • 401 — no valid session or key (Authentication required).
  • 403 — authenticated but the key's scopes/grants don't satisfy the route (Insufficient permissions).

On this page