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

# Authentication

> Send your key as a bearer token on every REST request and on the stream handshake.

Every request under `/v1` must carry your API key in the `Authorization` header, as a bearer token:

```http theme={null}
Authorization: Bearer chr_live_...
```

This is the only accepted form. The API rejects a key in any other header, in the query string, or in a cookie.

## REST requests

Send the header on every request:

```bash theme={null}
curl "https://{{API_HOST}}/v1/tokens/solana/DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" \
  -H "Authorization: Bearer $API_KEY"
```

The scheme name `Bearer` is case-insensitive. The key itself is case-sensitive.

## Stream handshake

The WebSocket at `/v1/stream` takes the same header on its opening HTTP request. The server checks the key before the upgrade, so a client without a valid key never gets a socket.

<CodeGroup>
  ```typescript Node.js (ws) theme={null}
  import WebSocket from "ws";

  const socket = new WebSocket("wss://{{API_HOST}}/v1/stream", {
    headers: { Authorization: `Bearer ${process.env.API_KEY}` },
  });
  // A refused handshake arrives as an `error` event. Without a listener, Node exits.
  socket.on("error", (error) => console.error("stream error:", error.message));
  ```

  ```python Python (websockets 13+) theme={null}
  import asyncio
  import os

  from websockets.asyncio.client import connect

  async def main():
      async with connect(
          "wss://{{API_HOST}}/v1/stream",
          additional_headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
      ) as socket:
          ...

  asyncio.run(main())
  ```
</CodeGroup>

## When authentication fails

A missing, malformed, unknown, revoked or expired key gets the same response:

```http theme={null}
HTTP/1.1 401 Unauthorized
X-Request-ID: 0b7e6a3c-2d4f-4c1a-9e8b-5f6a7b8c9d0e
X-RateLimit-Remaining: 59999
```

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing or invalid API key. Send it as `Authorization: Bearer <key>`."
  }
}
```

The response never says which of these cases applies, so it cannot reveal which keys exist. Do not retry a `401`: check that the key is set, sent in the right header, and not expired or revoked.

<Note>
  A `503 SERVICE_UNAVAILABLE` means the server could not check your key at that moment. It says nothing about your key. Retry with backoff and keep using the same key.
</Note>

## Keys belong on your backend

Call the API only from servers you control.

* **Browsers:** the API sends no CORS headers, so browser requests fail. A browser's WebSocket API also cannot set the `Authorization` header.
* **Mobile and desktop apps:** anyone can extract a key from a shipped app.

If your product runs in a browser or an app, call your own backend, and let the backend call this API with the key.

## Endpoints that need no key

`GET https://{{API_HOST}}/openapi.json` serves the OpenAPI document without a key, so you can generate a client before you have one.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.