Skip to main content

Authentication

NexSpace supports three authentication methods. Choose based on your use case.

API Keys

API keys are the simplest way to authenticate. Each key has scopes that control what it can access.
Minting a key is currently restricted. All of /api/api-keys (create, list, revoke, rotate) sits behind a guard that requires a dashboard session or JWT and either the super_admin role or an admin/manager role resolved into the platform-internal NexSpace 365 organization. An ordinary customer admin gets 403 FORBIDDEN — “API key management is restricted to the NexSpace 365 organization”.If you’re integrating as a customer, use the OAuth 2.1 connector flow or nexspace login device-code login (both work for any dashboard user), or email admin@nexspace365.com to request a key. See Quickstart → Step 1 for the decision table.

Key Prefixes

The prefix is not selectable. There is no environment picker in the dashboard or in the create payload — the only choice at creation time is the personal-access-token toggle (asPat). The server derives the rest from its own NODE_ENV: a production instance mints nex_live_, a non-production instance mints nex_test_. So a key created against https://api.nexspace365.com is always nex_live_ (or nex_pat_) — you cannot obtain a nex_test_ key there. nex_test_ keys come only from a non-production NexSpace deployment.
Sandbox (nex_test_) keys share the production database but only see is_sandbox rows, skip real notifications and outbound platform webhooks, and are omitted from usage analytics totals. See Sandbox (test API keys) for isolation rules, TTL, and scope limits.

Scopes

Scopes follow the pattern resource:action. Wildcard resource:* grants all actions on a resource — crm:*, for example. The resource half must be a real resource name; a bare * or *:* is rejected by the create validator with 400 VALIDATION_ERROR.

What a scope means depends on the surface

  • MCP (mcp.nexspace365.com) — scope only ever narrows. A tool call is first checked against the credential’s scopes, then executed through the governed path using the bound user’s role, so suite entitlements, RBAC, and AI autonomy rules still apply on top. A credential cannot exceed its user’s own access.
  • REST (api.nexspace365.com) — scope is the check. For an API-key-authenticated request, the permission guard is satisfied by scopes alone and the bound user’s RBAC is never consulted. A key carrying payroll:read passes the payroll.view guard on GET /api/payroll/payments even if the user it belongs to has no payroll permission in their role.
In both directions the scope set is a hard ceiling: a nex_pat_ bound to a super_admin still cannot do super_admin things unless that scope was explicitly granted at key creation. Treat a REST key as its own privilege grant, not a projection of its owner’s role, and grant the narrowest scope set that works. Core workforce scopes (gate the named MCP verbs and their REST equivalents): Category scopes (gate the platform tool registry over MCP, mirroring the AI-configuration categories): Connected apps & agents:

Permission names are not scope names

REST routes are gated on RBAC permission names (dot-separated, e.g. integrations.manage_webhooks). Credentials carry scopes (colon-separated, e.g. integrations:write). They are different vocabularies, and the error you get on a mismatch names the permission, not the scope you need to grant. The resolution rules, in order:
  1. Exact match — shifts:write satisfies shifts.write. The dot is normalized to a colon before comparison.
  2. Resource wildcard — crm:* satisfies any crm.* permission.
  3. Action aliasing — the coarse catalog actions read and write cover a set of finer RBAC verbs. read covers view, view_credentials, read, list. write covers create, edit, update, delete, deactivate, manage_credentials, manage_webhooks, write.
Worked examples:
agents:run and agents:approve gate the headless run endpoints (POST /api/agents/:id/runs and the approval resolution routes). They do not unlock the agent-builder routes gated on agents.manage.
“No catalog scope matches” is not the same as “unreachable.” Two things narrow that guarantee, and both apply equally to system.manage_integrations (/api/oauth-clients):
  • API keys accept un-cataloged scope strings. Key creation validates only the resource:action shape, not membership in the catalog, so a key can be minted with the resource wildcard agents:* (or system:*) — and the wildcard branch of the matcher satisfies agents.manage before the alias sets are ever consulted. The catalog constrains the consent UI and the scope picker, not what a key may hold.
  • OAuth/JWT bearers are authorized on RBAC, not on token scopes. Scope checking on REST short-circuits only for API-key callers. A bearer token sets the request’s user but not its API key, so requirePermission falls through to the bound user’s effective RBAC permissions and never consults the token’s scopes at all — and a super_admin bearer is admitted before any permission check runs.
What the catalog does guarantee: OAuth-consented scopes are filtered to it, so an OAuth grant can never itself carry agents:manage. Do not rely on scope absence as an authorization boundary — the boundary is the bound user’s RBAC.

Default connector scopes

MCP connector clients (claude.ai, ChatGPT, Gemini) typically omit scope from the OAuth request because they don’t know a server’s catalog up front. A scope-omitted request receives the default connector bundle: every read scope in the catalog and nothing else — no writes, no apps:*, no agents:*. The consent page shows exactly this list, and the issued token carries it explicitly, so omitting scope can never escalate to an unscoped token. To use write tools or connected-app actions from a connector, request those scopes explicitly.

Key Rotation

Rotate keys with zero downtime — the old key keeps authenticating for 24 hours after the rotation, then stops. Rotation is a dashboard-session operation by design. An API key must never be able to mint or rotate another key: API-key requests authorize on their scopes rather than their owner’s role, so a low-scope token could otherwise issue itself a fully-scoped one. Sending Authorization: Bearer nex_live_… to this endpoint returns 403 FORBIDDEN — “API keys cannot be managed with an API key — sign in to the dashboard”. Rotate from Settings → API Keys in the dashboard, or with a session cookie:
(The session cookie is named nexspace.sid on the shared-domain deployment; a local instance without SESSION_COOKIE_DOMAIN set uses connect.sid.) From the CLI, log in with the device-code flow first (nexspace login without --token) so the stored credential is a session-class token rather than an API key:
The response returns the new plaintext secret once, under key. Swap it into your deployment within the 24-hour window; after that the old key resolves as revoked. Like every other /api/api-keys route, rotation additionally requires NexSpace 365 organization membership (see the note above).

OAuth 2.1 + PKCE

For apps where users grant access through a browser flow.

Discovery

Dynamic Client Registration (RFC 7591)

Clients can self-register without operator intervention:

Authorization Flow

  1. Redirect user to /oauth/authorize with PKCE code_challenge (S256)
  2. User logs in and grants scopes
  3. Exchange code + code_verifier at /oauth/token
  4. Receive access_token + refresh_token

Device Code Flow (RFC 8628)

For CLI tools and environments without a browser.
The NexSpace CLI handles this automatically. nexspace login runs the device-code flow by default (opening your browser); pass --token nex_pat_… to store a personal access token instead:
Credentials are stored in your OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) when available, falling back to ~/.nexspace/config.json (mode 0600). The CLI refreshes the OAuth access token automatically as it nears expiry, rotating the stored refresh token.

Verify identity

GET /api/auth/me returns the identity a credential authenticates as. It accepts any supported credential — API key (nex_live_/nex_test_), personal access token (nex_pat_), OAuth 2.1 access token, or first-party session — so it’s the canonical “who am I” probe for SDKs, MCP clients, and the CLI (nexspace whoami).
A missing or revoked credential returns 401.

Token Introspection (RFC 7662)

Resource servers can check whether a token is active and inspect its metadata. The caller authenticates as the client that owns the token — public clients (PKCE: CLIs, SPAs) present client_id alone; confidential clients also present client_secret. Works for both access tokens (JWT) and refresh tokens.
An active token returns its metadata; an unknown, expired, revoked, or foreign-owned token returns { "active": false } (never an error):

Token Revocation (RFC 7009)

Revoke a token when a session ends or a credential is compromised. Revoking a refresh token invalidates it and its entire rotation chain; revoking an access token invalidates the associated refresh chain so it can’t be renewed (the stateless access JWT itself remains valid until its short 1-hour exp). Per RFC 7009 the response is always 200 with an empty body.
nexspace logout clears stored credentials locally (keychain + config file).

Rate Limits

Every API-key-authenticated response on api.nexspace365.com includes rate-limit headers: Default: 60 requests/minute per API key. Configurable per key up to 6,000/min. Session- and JWT-authenticated requests are excluded — they are governed by a separate role/IP limiter and carry none of these headers, so don’t build a throttle that depends on them when calling from a browser session. MCP requests are governed separately: mcp.nexspace365.com runs its own limiter with IETF RateLimit-* headers and a higher default budget (120/min unless the credential is an API key, which uses its own per-key limit). See Rate Limits.

Error Responses

Authentication errors return structured JSON with recovery hints: