---
title: Authentication
excerpt: Login, registration, logout, token switching and identity endpoints for both the central and workspace APIs.
order: 2
---

## Overview

CX-Engine uses **OAuth 2.0 Bearer tokens**. Tokens are scoped to either the central platform or a specific workspace. Use `POST /api/switch-to` to move between scopes without logging in again.

---

## OAuth 2.0

CX-Engine supports two OAuth 2.0 grant types for delegated and machine-to-machine access:

| Grant type | Use case |
|---|---|
| **Authorization Code** | Interactive, user-delegated access via browser redirect |
| **Client Credentials** | Server-to-server access without user interaction |

> **Before you begin:** To use any OAuth 2.0 flow, you must first create an OAuth client in your CX-Engine dashboard. This gives you a `client_id` and `client_secret` required for all grant types.

### Authorization Code Grant

Used when your application acts on behalf of a user. The user approves access in their browser and your application receives an authorization code to exchange for tokens.

**Step 1 — Redirect the user**

`GET /oauth/authorize`

| Parameter | Type | Description |
|---|---|---|
| `client_id` | string | Your application's client ID |
| `redirect_uri` | string | URI to redirect to after authorization |
| `response_type` | string | Must be `code` |
| `scope` | string | Space-separated list of requested scopes (optional) |
| `state` | string | Random string for CSRF protection (recommended) |

> The `redirect_uri` must exactly match one of the redirect URIs registered on your OAuth client. Requests with an unregistered URI will be rejected.

The user is shown an approval screen. On approval they are redirected to your `redirect_uri` with a `code` query parameter.

**Step 2 — Exchange the code for tokens**

`POST /oauth/token`

| Field | Type | Description |
|---|---|---|
| `grant_type` | string | `authorization_code` |
| `client_id` | string | Your client ID |
| `client_secret` | string | Your client secret |
| `redirect_uri` | string | Must match the URI used in Step 1 |
| `code` | string | The authorization code received |

**Response `200`**

```json
{
  "token_type": "Bearer",
  "expires_in": 31536000,
  "access_token": "eyJ0eXAiOiJKV1Q...",
  "refresh_token": "def50200..."
}
```

---

### Client Credentials Grant

Used for server-to-server communication where no user interaction is involved.

`POST /oauth/token`

| Field | Type | Description |
|---|---|---|
| `grant_type` | string | `client_credentials` |
| `client_id` | string | Your client ID |
| `client_secret` | string | Your client secret |
| `scope` | string | Space-separated list of requested scopes (optional) |

**Response `200`**

```json
{
  "token_type": "Bearer",
  "expires_in": 31536000,
  "access_token": "eyJ0eXAiOiJKV1Q..."
}
```

> Client credentials tokens do not include a refresh token.

---

### Refresh a Token

`POST /oauth/token`

| Field | Type | Description |
|---|---|---|
| `grant_type` | string | `refresh_token` |
| `refresh_token` | string | The refresh token from a previous token response |
| `client_id` | string | Your client ID |
| `client_secret` | string | Your client secret |
| `scope` | string | Must be equal to or a subset of the original scopes |

**Response `200`**

```json
{
  "token_type": "Bearer",
  "expires_in": 31536000,
  "access_token": "eyJ0eXAiOiJKV1Q...",
  "refresh_token": "def50200..."
}
```

> **Refresh token rotation:** Each call to this endpoint invalidates the previous refresh token and issues a new one. You must persist the `refresh_token` returned in every response — reusing an old refresh token will result in an error.

---

### Revoke a Token

`DELETE /oauth/tokens/{token_id}`

**Headers:** `Authorization: Bearer {access_token}`

**Response `204`** — token revoked.

---

## Central API

### POST /api/login

Authenticates a user against the central platform and returns a central-scoped Bearer token.

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `email` | string | yes | User email address |
| `password` | string | yes | User password |

**Response `200`**

```json
{
  "message": "success",
  "token": "eyJ0eXAiOiJKV1Q...",
  "user": { "id": 1, "name": "Jane Doe", "email": "jane@example.com" },
  "workspaces": [...],
  "pending_invites": [...]
}
```

**Response `401`**

```json
{ "response": "invalid username and/or password." }
```

---

### POST /api/register

Creates a new user account and returns a central-scoped Bearer token.

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Full name |
| `email` | string | yes | Email address |
| `password` | string | yes | Password |
| `phone` | string | no | Phone number |
| `mobile_phone` | string | no | Mobile phone number |

**Response `200`**

```json
{
  "message": "success",
  "token": "eyJ0eXAiOiJKV1Q...",
  "user": { ... },
  "workspaces": [],
  "pending_invites": []
}
```

**Response `422`** — email already in use

```json
{ "message": "An user with this e-mail address already exists." }
```

---

### POST /api/logout

Revokes all tokens for the authenticated user across the central platform and all workspaces.

**Headers:** `Authorization: Bearer {central-token}`

**Response `200`**

```json
{ "message": "success" }
```

---

### GET /api/whoami

Returns the identity of the currently authenticated user along with their workspaces.

**Headers:** `Authorization: Bearer {central-token}`

**Response `200`**

```json
{
  "message": "success",
  "user": { "id": 1, "name": "Jane Doe", "email": "jane@example.com" },
  "workspaces": [...],
  "current_workspace": null
}
```

When called with a workspace-scoped token, `current_workspace` includes the workspace name, group, roles and permissions:

```json
{
  "current_workspace": {
    "name": "Acme Corp",
    "group": "admin",
    "roles": ["manager"],
    "permissions": ["customer.view", "customer.create"]
  }
}
```

---

### POST /api/switch-to

Issues a new token scoped to a different workspace, or back to central, without requiring re-authentication.

**Headers:** `Authorization: Bearer {current-token}`

**Request body — switch to a workspace**

| Field | Type | Required | Description |
|---|---|---|---|
| `workspace_id` | string | yes* | Target workspace ID |

**Request body — switch back to central**

| Field | Type | Required | Description |
|---|---|---|---|
| `central` | boolean | yes* | Pass `true` to return to central scope |

*One of `workspace_id` or `central` is required.

**Response `200`**

```json
{
  "message": "success",
  "user": { ... },
  "workspace": {
    "id": "acme",
    "name": "Acme Corp",
    "group": "admin",
    "roles": ["manager"],
    "permissions": ["customer.view", ...]
  },
  "domains": ["acme.cx-engine.app"],
  "token": "eyJ0eXAiOiJKV1Q..."
}
```

**Response `403`** — workspace not accessible

```json
{ "message": "You do not have access to this workspace." }
```

---

## Workspace API

### POST /api/login *(workspace)*

Authenticates a user against a specific workspace and returns a workspace-scoped Bearer token. Send this request to the workspace subdomain.

**Request body**

| Field | Type | Required |
|---|---|---|
| `email` | string | yes |
| `password` | string | yes |

**Response `200`**

```json
{
  "message": "success",
  "token": "eyJ0eXAiOiJKV1Q...",
  "user": { ... }
}
```

---

### POST /api/logout *(workspace)*

Revokes the current workspace token only.

**Headers:** `Authorization: Bearer {workspace-token}`

**Response `200`**

```json
{ "message": "success" }
```
