# Authentication

## API keys

Server-side calls (pushing pages, listing content, and optional widget access) use an **API key** for your organisation. Create keys on the **API keys** page in the dashboard.

Send the key on every request:

```http
Authorization: Bearer YOUR_API_KEY
```

You can choose permissions for each key, and optionally limit it to specific sources.

### Permissions

| Permission | Allows |
|---|---|
| `pages.read` | List pages for a source |
| `pages.write` | Create, update, and delete pages |
| `search` | Call search on a widget with this key |
| `chat` | Call chat on a widget with this key |
| `summarize` | Call summarize on a widget with this key |

If a key is missing the permission for a request, the API returns **403**.

### Source limits

When you create a key you can restrict it to one or more sources. A limited key:

- only lists those sources from [`GET /me`](/docs/me)
- is rejected with **403** if it targets any other source

Keys without a source limit can access every source in the organisation.

### Auth errors

| Status | Meaning |
|---|---|
| **401** | Missing, invalid, or expired API key |
| **403** | Key is for another organisation, lacks a permission, or is outside its source limit |
| **404** | Unknown organisation or source |

## Widget authentication

Widget endpoints (`/widgets/{widget}/…`) accept any of the following:

1. **Your website domain.** The request comes from a domain on the widget's website list. This is how the public embed works in the browser.
2. **Widget secret key.** `Authorization: Bearer …` with the secret shown once when you create the widget. Use this from your server.
3. **Organisation API key.** A key that has the matching permission (`search`, `chat`, or `summarize`) and can access every source that widget uses.

A search widget cannot handle chat requests, and a chat widget cannot handle search-only endpoints that require the other type.

### Signed visitor identity

To load conversation history or keep conversations for a logged-in visitor, prove the visitor id on **your** server. Sign it with the widget's signing secret:

```php
$hash = hash_hmac('sha256', $userId, $signingSecret);
```

On chat, send:

```json
{ "user": { "id": "user-42", "hash": "…" } }
```

On conversation routes, send `user` and `hash` as query parameters or in the body. If the hash is wrong, the request fails. Datalumo will not treat a failed proof as an anonymous visitor.
