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

# Authentication

> Exchange a publishable key for an access token, then send it as a Bearer token.

The Apollo API uses short-lived **access tokens** obtained from an OAuth 2.0
token endpoint. You exchange a credential once, then send the token on every
request:

```bash theme={"dark"}
Authorization: Bearer <access_token>
```

## Get an access token

Exchange your **publishable key** at the token endpoint. The endpoint itself is
unauthenticated — the credential travels in the body:

```bash cURL theme={"dark"}
curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/auth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "publishable_key",
    "publishable_key": "pk_..."
  }'
```

```json Response theme={"dark"}
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "agent_id": "6a344d186d3160971ec62eb6",
  "organization_id": "5f2b8c1a9d4e3f0012ab34cd"
}
```

<ResponseField name="access_token" type="string">
  The token to send as `Authorization: Bearer <access_token>`.
</ResponseField>

<ResponseField name="expires_in" type="integer">
  Token lifetime in seconds. Request a new token when it expires — exchanges
  are cheap, so exchanging shortly before expiry is the simplest strategy.
</ResponseField>

<ResponseField name="agent_id" type="string">
  The agent your messaging calls will run against. The token carries the agent,
  so messaging requests never include an `agent_id` in the body.
</ResponseField>

<Info>
  Your publishable key is issued in the AUI Console. Publishable keys are safe
  to use from client-side code — that's what makes them "publishable" — and can
  additionally be restricted to allowed web origins and IP ranges.
</Info>

## Which credential for which surface

| Surface    | Paths                | Accepted credentials                                            |
| ---------- | -------------------- | --------------------------------------------------------------- |
| Messaging  | `/messaging/v1/...`  | Bearer token (from a publishable key)                           |
| Management | `/management/v1/...` | Bearer token (from a login session) **or** organization API key |

### Organization API key (management only)

Server-to-server management calls can skip the token flow and authenticate with
your organization API key in a header:

```bash theme={"dark"}
curl "https://api-v3.aui.io/apollo-api/management/v1/projects" \
  -H "x-organization-api-key: <org_api_key>"
```

<Warning>
  Unlike publishable keys, the organization API key must stay server-side.
  Never ship it in client code.
</Warning>

## WebSocket authentication

The WebSocket session authenticates on the upgrade request with the same
Bearer token:

```
GET wss://api-v3.aui.io/apollo-api/messaging/v1/session
Authorization: Bearer <access_token>
```

Invalid or expired credentials close the connection with WebSocket close code
`1008` (policy violation). See [WebSocket](/api/messaging/websocket).

## Failed authentication

Requests without a valid credential return `401` in the standard
[error envelope](/api/errors):

```json 401 theme={"dark"}
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid credentials",
    "request_id": "req_..."
  }
}
```

Token-endpoint failures follow the OAuth 2.0 error convention instead:

```json 400 theme={"dark"}
{
  "error": "invalid_grant",
  "error_description": "invalid_publishable_key"
}
```

## Best practices

<Steps>
  <Step title="Store credentials in environment variables">
    Keep keys out of source control.

    ```bash theme={"dark"}
    export AUI_PUBLISHABLE_KEY="pk_..."
    ```
  </Step>

  <Step title="Reuse tokens for their lifetime">
    Exchange once and reuse the token until `expires_in` elapses rather than
    exchanging per request.
  </Step>

  <Step title="Scope keys to origins and IPs">
    If your publishable key is used from a browser, restrict it to your web
    origins in the Console so it can't be replayed elsewhere.
  </Step>
</Steps>
