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

# Authentication

> API keys, OAuth 2.1, scopes, and device-code flow

# Authentication

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

| Method               | Best For                         | How                                  |
| -------------------- | -------------------------------- | ------------------------------------ |
| **API Key**          | Server-to-server, agents, CI/CD  | `Authorization: Bearer nex_live_...` |
| **OAuth 2.1 + PKCE** | Browser-based apps, plugins      | Authorization code flow with S256    |
| **Device Code**      | CLI tools, headless environments | Poll-based flow (RFC 8628)           |

## API Keys

API keys are the simplest way to authenticate. Each key has scopes that control
what it can access.

### Key Prefixes

| Prefix      | Environment | Purpose                                       |
| ----------- | ----------- | --------------------------------------------- |
| `nex_live_` | Production  | Server-to-server integrations                 |
| `nex_test_` | Sandbox     | Development and testing                       |
| `nex_pat_`  | Production  | Personal access tokens (acts as issuing user) |

Sandbox 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)](/concepts/sandbox) for isolation rules, TTL, and scope limits.

### Scopes

Scopes follow the pattern `resource:action`. Wildcard `resource:*` grants all
actions on a resource.

| Scope                | Description                     |
| -------------------- | ------------------------------- |
| `shifts:read`        | Read shifts and schedules       |
| `shifts:write`       | Create and modify shifts        |
| `shifts:assign`      | Assign staff to shifts          |
| `staff:read`         | Read staff profiles             |
| `staff:write`        | Create and modify staff records |
| `credentials:read`   | Read credentials                |
| `credentials:verify` | Run credential verification     |
| `payroll:read`       | Read payroll data               |
| `payroll:run`        | Execute payroll                 |
| `crm:*`              | All CRM operations              |
| `facilities:read`    | Read facility data              |
| `*`                  | Full access (use sparingly)     |

### Key Rotation

Rotate keys with zero downtime — the old key remains valid for 24 hours:

```bash theme={null}
curl -X POST https://api.nexspace365.com/api/api-keys/{id}/rotate \
  -H "Authorization: Bearer nex_live_YOUR_KEY"
```

## OAuth 2.1 + PKCE

For apps where users grant access through a browser flow.

### Discovery

```bash theme={null}
curl https://api.nexspace365.com/.well-known/oauth-authorization-server
```

### Dynamic Client Registration (RFC 7591)

Clients can self-register without operator intervention:

```bash theme={null}
curl -X POST https://api.nexspace365.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My App",
    "redirect_uris": ["https://myapp.com/callback"],
    "scope": "shifts:read staff:read facilities:read"
  }'
```

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

```bash theme={null}
# 1. Request a device code
curl -X POST https://api.nexspace365.com/oauth/device/code \
  -H "Content-Type: application/json" \
  -d '{"client_id": "YOUR_CLIENT_ID", "scope": "shifts:read staff:read"}'

# Response includes user_code and verification_uri
# 2. User opens verification_uri in browser, enters user_code
# 3. CLI polls /oauth/token with device_code until approved
```

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:

```bash theme={null}
nexspace login                 # browser OAuth (device code)
nexspace login-device          # explicit alias for the same flow
nexspace login --token nex_pat_xxxxxxxxxxxxxxxxxxxxxxxx
```

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

```bash theme={null}
curl https://api.nexspace365.com/api/auth/me \
  -H "Authorization: Bearer nex_live_YOUR_KEY"
```

```json theme={null}
{
  "user": {
    "id": 42,
    "email": "ops@acme.com",
    "username": "acme-ops",
    "firstName": "Ada",
    "lastName": "Lovelace",
    "role": "facility_admin"
  }
}
```

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.

```bash theme={null}
curl -X POST https://api.nexspace365.com/oauth/introspect \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=ACCESS_OR_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID"
```

An active token returns its metadata; an unknown, expired, revoked, or
foreign-owned token returns `{ "active": false }` (never an error):

```json theme={null}
{
  "active": true,
  "scope": "shifts:read staff:read",
  "client_id": "nexspace_cli_public",
  "token_type": "Bearer",
  "sub": "42",
  "exp": 1751990400,
  "iat": 1751986800
}
```

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

```bash theme={null}
curl -X POST https://api.nexspace365.com/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=REFRESH_TOKEN&client_id=YOUR_CLIENT_ID"
```

`nexspace logout` clears stored credentials locally (keychain + config file).

## Rate Limits

Every API response includes rate-limit headers:

| Header                  | Description                             |
| ----------------------- | --------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed per window             |
| `X-RateLimit-Remaining` | Requests remaining                      |
| `X-RateLimit-Reset`     | When the window resets (Unix timestamp) |

Default: 60 requests/minute per API key. Configurable per key up to 6,000/min.

## Error Responses

Authentication errors return structured JSON with recovery hints:

```json theme={null}
{
  "error": {
    "message": "Invalid API key",
    "code": "INVALID_API_KEY",
    "suggestion": "Check that your key starts with nex_live_, nex_test_, or nex_pat_ and has not been revoked.",
    "retryable": false
  }
}
```
