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

# Authentication

> Consumer token for the read-only feed

The feed requires a **consumer token** on every read endpoint — the WebSocket, the history backfill, the sessions catalog, and the per-session health read. Present it as a Bearer token or a query parameter.

## Sending the token

<CodeGroup>
  ```bash Authorization header theme={null}
  curl https://mm.akaramarkets.com/api/history/T1 \
    -H "Authorization: Bearer $CONSUMER_TOKEN"
  ```

  ```bash Query parameter theme={null}
  curl "https://mm.akaramarkets.com/api/history/T1?token=$CONSUMER_TOKEN"
  ```
</CodeGroup>

The query-parameter form (`?token=`) exists because a browser `WebSocket` cannot set an `Authorization` header. REST endpoints accept either form; browser WebSocket clients must use `?token=`.

<Note>
  Browser clients making cross-origin POST requests (such as the scout UI) trigger a CORS preflight (`OPTIONS`). This is handled automatically by the browser and requires no action from a consumer. Consumer read paths (`GET`, `WebSocket`) are unaffected.
</Note>

## Failure modes

Every HTTP read endpoint (`/api/history/{session}`, `/api/sessions`, `/api/health/{session}`)
refuses the same way, so the table is condition × transport rather than per endpoint. On HTTP
`{session}` routes the id-format check runs **before** auth — a malformed id is `404` even with no
credential at all. The WebSocket differs on credential refusal: the upgrade request itself is
rejected with HTTP `403`, before any WebSocket exists — so no close code or frame ever arrives. A
browser `WebSocket` observes it as a failed connection (close `1006`); a non-browser client sees
the `403` response directly.

| Condition                                                            | HTTP endpoints                                   | `WS /api/subscribe/{session}`                                                                                                |
| -------------------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| Missing or invalid token                                             | `401 Unauthorized`                               | Handshake rejected, HTTP `403` — a browser observes a failed connection (`1006`)                                             |
| Invalid session id format                                            | `404 Not Found` (before auth)                    | Same `403` handshake rejection                                                                                               |
| Server cannot verify tokens (outage or misconfiguration on our side) | `401` with detail `"authentication unavailable"` | Accepted, one `{"type": "error", "code": "unavailable"}` frame, then close `1011` — retriable, your token is not the problem |

The `401` body's `detail` says which failure it was:

```json theme={null}
{
  "detail": "authentication required"
}
```

* `"authentication required"` — no token was presented (neither `Authorization` header nor `?token=`); some endpoints also answer this when the server cannot verify any token.
* `"invalid token"` — a token was presented but does not resolve to an account (expired, revoked, or malformed).
* `"authentication unavailable"` — the server itself has no way to verify tokens (a deployment misconfiguration, not something a consumer can fix — report it).

For the WebSocket's full close-code vocabulary — including the post-handshake `4404`/`4403`/`1011` refusals and the error frame that precedes them — see [WebSocket feed → Close codes](/api-reference/websocket#close-codes).

## Sessions

Every endpoint carries a `{session}` path segment that selects an independent match on the server — for example `/api/history/T1`. Sessions are isolated: a press on one never affects another. Your consumer token is valid across all sessions you are entitled to read, plus any session marked **public** — a public game is readable by any valid account regardless of entitlement (used for shareable promotional viewer links). Entitlements are granted **per session**: being able to read one session in a competition never implies access to another.

Session ids are alphanumeric slugs. An id that does not match the expected format returns `404` before auth is checked on HTTP routes; on the WebSocket the handshake is refused pre-accept instead (observed as a failed connection — see [Failure modes](#failure-modes)).
