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

# Authentication

> How users and devices authenticate with the Kair Voice API.

## Overview

The API uses two authentication mechanisms depending on who is making the request:

| Caller                   | Mechanism        | Header                          |
| ------------------------ | ---------------- | ------------------------------- |
| Web app users            | JWT bearer token | `Authorization: Bearer <token>` |
| Hardware devices (ESP32) | Device API key   | `X-API-Key: <key>`              |
| Guest participants       | Short-lived JWT  | `Authorization: Bearer <token>` |

## User authentication (JWT)

Users log in via the web app. On successful login the API returns a signed JWT. The web app stores this and sends it with every subsequent request.

**Login endpoint:** `POST /api/auth/login`

```json theme={null}
{
  "email": "user@example.com",
  "password": "••••••••",
  "remember_me": true
}
```

Returns:

```json theme={null}
{
  "token": "eyJ...",
  "refresh_token": "eyJ...",
  "user": { "id": "...", "email": "...", "role": "admin" }
}
```

### remember\_me & refresh tokens

When `remember_me: true` is passed, the API issues a **refresh token** alongside the access token. The refresh token lives for **12 hours**. Use it to get a new access token without re-entering credentials:

**Refresh endpoint:** `GET /api/auth/refresh-token`

Send the refresh token as a bearer token. Returns a fresh access token.

Without `remember_me`, the session ends when the browser tab closes (no refresh token issued).

### Magic link (passwordless)

Users can also authenticate without a password via a magic link sent to their email.

1. `POST /api/auth/request-magic-link` — sends the link
2. `POST /api/auth/validate-magic-link` — exchanges the token in the link for a JWT

### OAuth2

The web app supports OAuth2 for third-party identity providers. The OAuth2 flow follows the standard authorisation code pattern:

1. Redirect the user to the provider's authorisation URL
2. Provider redirects back with an authorisation code
3. The API exchanges the code for an access token and returns a Kair Voice JWT

OAuth2 providers are configured server-side; contact an admin to enable a provider for your workspace.

## Guest access

Participants joining a session without an account receive a guest token scoped to that session only.

**Guest login endpoint:** `POST /api/auth/guest`

```json theme={null}
{
  "session_id": "...",
  "display_name": "Jane"
}
```

Guest tokens can access the session they were issued for, nothing else.

## Device authentication (API key)

Physical devices authenticate with a device-scoped API key obtained during the pairing flow. See [Devices](/api/devices) for the full pairing process.

Protected device endpoints:

* `POST /api/devices/{device_id}/heartbeat`
* `POST /api/devices/{device_id}/session/start`
* `POST /api/devices/{device_id}/session/stop`
* `POST /api/sessions/{session_id}/upload-audio`

Device API keys expire at `expires_at`. The device must call `POST /api/devices/pair/claim-auth` again to get a fresh key.

## Roles

| Role          | Access                                                                                        |
| ------------- | --------------------------------------------------------------------------------------------- |
| `admin`       | All workspaces, all sessions, system config, user management                                  |
| `participant` | Workspaces they belong to, their own sessions                                                 |
| `moderator`   | Like participant, but also gets audio access to sessions in any workspace they're a member of |
| `guest`       | Single session they were invited to                                                           |

## Endpoints that don't require authentication

* `POST /api/auth/login`
* `POST /api/auth/guest`
* `POST /api/devices/pairing-codes` (device publishes pairing code)
* `GET /api/devices/pair/status` (device polls pairing status)
* `GET /api/health`
