# Authentication

Your server authenticates with an API key. A web page never holds a key: your server hands it a short-lived token to open the stream.

## API keys

Create your keys in your [account](https://fathomcharts.com/account). A key is shown **only once**, when it is created: copy it right away. We keep no readable copy, so a lost key cannot be recovered, only [rotated](#rotating-a-key).

| Key         | Data                                                                                                                         | Use                                             |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `fc_test_…` | Sandbox rights: delayed by 10 minutes, the last 7 days of history                                                            | Building and testing your integration, for free |
| `fc_live_…` | The rights of your plans: real time with Live (no history), full history with Historical. Without a plan, the Sandbox rights | Production                                      |

A test key always has the Sandbox rights, whatever your plan. Apart from the delay, it behaves exactly like a live key: same messages, same cursors, same responses.

Send the key in the `Authorization` header, on every REST request and when opening the WebSocket:

```bash
curl https://api.fathomcharts.com/v1/usage -H "Authorization: Bearer $FATHOM_CHARTS_API_KEY"
```

The word `Bearer` is case-insensitive, but required: a bare key without `Bearer` is refused (`401` over REST, code `4001` on the WebSocket).

A missing, invalid, expired (`API key expired`) or revoked key is rejected with `401 UNAUTHORIZED` over REST, with the header `WWW-Authenticate: Bearer realm="fathomcharts"` (plus `error="invalid_token"` when a key was sent). On the WebSocket, the connection opens first (a browser cannot read why an opening was refused), then it is closed right away with code `4001`. A valid key that lacks the required scope is rejected with `403 FORBIDDEN`, or code `4003` on the WebSocket.

## Scopes and restrictions

A key only grants what you allow it to. If it leaks, the damage stays contained: a key that only reads the stream has no business starting exports.

| Scope (`scopes`) | Allows                                                                             |
| ---------------- | ---------------------------------------------------------------------------------- |
| `stream`         | The real-time stream, and creating [stream tokens for browsers](#browser-access)   |
| `history`        | [Historical queries](https://fathomcharts.com/docs/history.md) and their estimates |
| `export`         | [Exports](https://fathomcharts.com/docs/exports.md)                                |
| `keys`           | Managing keys through the API                                                      |

You can also restrict a key to specific instruments or IP addresses, and give it an expiry date. Through the API, with a key that has the `keys` scope:

```bash
curl https://api.fathomcharts.com/v1/keys \
  -H "Authorization: Bearer $FATHOM_CHARTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"collector","environment":"live","scopes":["stream","history"],"instruments":["NQ.front"],"allowedIps":["203.0.113.0/24"]}'
```

| Field         | Content                                                                                                                    |
| ------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `name`        | Key name, 1 to 64 characters                                                                                               |
| `environment` | `live` or `test`                                                                                                           |
| `scopes`      | At least one of `stream`, `history`, `export`, `keys`                                                                      |
| `instruments` | Optional: allowed instruments (up to 32), each a symbol or an alias of `GET /v1/instruments`, case-sensitive. Omitted: all |
| `allowedIps`  | Optional: allowed IP addresses or CIDR ranges (up to 32). Omitted: any                                                     |
| `expiresAt`   | Optional: expiry date, in the future, as a timestamp in nanoseconds since January 1, 1970 (UTC), as a string               |

The response contains the full key in the `key` field, once, and its id in `id`. An invalid field is rejected with `400 INVALID_PARAMETERS`, and `errors` points at the faulty entry (`/instruments/0` for an unknown instrument, `/allowedIps/1` for a malformed address, `/expiresAt`).

`GET /v1/keys` lists every key of the account, without the secret: the active keys, and those revoked or expired in the last 30 days. The `state` field of each key is:

| `state`    | Meaning                                                                                                                                                                  |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `active`   | A valid key, with no expiry date.                                                                                                                                        |
| `expiring` | A key valid until its `expiresAt`, in the future.                                                                                                                        |
| `expired`  | `expiresAt` has passed: the key is rejected with `401` (`API key expired`), and the WebSocket connections opened with it are closed (code `4003`, reason `KEY_REVOKED`). |
| `revoked`  | A revoked key, through `DELETE` or a rotation without grace period.                                                                                                      |

### Instrument restriction

Each `instruments` entry allows the instrument it names in `GET /v1/instruments`, with the same rule over REST and on the WebSocket:

- `NQ.front` allows `NQ.front` and the contract it designates today (`NQZ6`);
- `NQZ6` allows `NQZ6`, and `NQ.front` as long as it designates `NQZ6`. Over history, a query on `NQ.front` reads the contract designated at `from`: for a range when `NQ.front` designated `NQU6`, a key restricted to `NQZ6` is rejected with `403 FORBIDDEN`;
- `NQ.front` stays allowed over history for any range, even when it designated another contract (`NQU6`); that contract, requested by its symbol, is not;
- an expired contract is only allowed by an identical entry (`NQU6`).

### Managing keys with a key

A key with the `keys` scope can only create, rotate or revoke keys that have no more rights than its own, on every criterion:

- the same kind (test or live);
- scopes included in its own;
- if it is restricted to instruments, a key restricted too, whose every `instruments` entry is literally one of its own (a key restricted to `NQZ6` cannot manage a key restricted to `NQ.front`, nor the reverse);
- if it is restricted to IP addresses, a key restricted too, whose every range lies within one of its own;
- if it expires, a key that expires no later than it does.

Otherwise the request is rejected with `403 FORBIDDEN`, and the message names the criterion, for example `Cannot create a key with scopes beyond this key's own: stream` or `Cannot revoke a key without instrument restriction: this key is restricted to NQ.front`. From your [account](https://fathomcharts.com/account), you manage every key of the account, without these limits.

An account can have up to **10 valid keys** (`state` `active` or `expiring`). All your keys share the same [limits](https://fathomcharts.com/docs/limits.md): creating more does not raise your quotas.

## Rotating a key

To change a key without interrupting your service, rotate it (`<id>`: the key’s `id` field, from `GET /v1/keys`):

```bash
curl -X POST https://api.fathomcharts.com/v1/keys/<id>/rotate \
  -H "Authorization: Bearer $FATHOM_CHARTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"gracePeriodSeconds":172800}'
```

`gracePeriodSeconds` is required, from `0` to `604800` (7 days). The response contains the new key, with the same scopes, restrictions and expiry date, and, in `previous`, the state of the old one. The old key keeps working during the grace period (2 days here), giving you time to deploy the new key everywhere (`state` `expiring`, with `expiresAt` at the end of the grace period), then it expires (`state` `expired`): it is rejected over REST, and the WebSocket connections still open with it are closed within seconds (code `4003`, reason `KEY_REVOKED`). With `0`, it is revoked immediately (`state` `revoked`). A key can only be rotated once: a second rotation is rejected with `409 CONFLICT`.

## Revoking a key

`DELETE /v1/keys/<id>`, or from your [account](https://fathomcharts.com/account). Revocation is immediate: WebSocket connections opened with the key are closed within a second (code `4003`, reason `KEY_REVOKED`).

## Browser access

An API key must never appear in a web page: any visitor could read it. To show the stream in a browser:

1. your server requests a **stream token** with its key;
2. it passes the token to the page;
3. the page opens the WebSocket and sends the token.

On the server:

```bash
curl -X POST https://api.fathomcharts.com/v1/stream-tokens -H "Authorization: Bearer $FATHOM_CHARTS_API_KEY"
```

The response contains the token (`token`) and its expiry (`expiresAt`). With the SDK: `rest.streamToken()`, `rest` being an instance of `FathomChartsRest`.

In the page, give the SDK a function that fetches a token from your server (here a `/api/fathom-charts-token` route of your backend that makes the call above). The SDK calls it on every connection and handles the rest:

```ts
import { FathomChartsStream } from '@fathom-charts/sdk';

const stream = new FathomChartsStream({
  url: 'wss://stream.fathomcharts.com/v1',
  streamToken: async () => {
    const response = await fetch('/api/fathom-charts-token', { method: 'POST' });
    const { token } = (await response.json()) as { token: string };
    return token;
  },
});

for await (const event of stream.subscribe({ instrument: 'NQ.front', indicator: 'big-trades', params: { minimum: 30 } })) {
  console.log(event.type, event);
}
```

Without the SDK, send the token in an `auth` message, first, within 5 seconds of opening the connection:

```ts
const { token } = (await fetch('/api/fathom-charts-token', { method: 'POST' }).then((r) => r.json())) as { token: string };
const ws = new WebSocket('wss://stream.fathomcharts.com/v1');
ws.onopen = () => {
  ws.send(JSON.stringify({ t: 'auth', token }));
  ws.send(JSON.stringify({
    t: 'subscribe', sub: 'bt', instrument: 'NQ.front', indicator: 'big-trades',
    params: { minimum: 30 }, mode: 'live', from: 'live',
  }));
};
ws.onmessage = (e) => console.log(JSON.parse(e.data));
```

Good to know:

- a token is valid for **60 seconds** and works **only once**. It opens the connection, which then stays open as long as you need. Every reconnection needs a new token;
- the token has the same scopes and limits as the key that created it, and stops working if that key is revoked;
- any website can use a token: only hand tokens to your own users, once they are authenticated;
- the key’s allowed IP addresses (`allowedIps`) apply to the token request, made by your server, not to the browser that opens the connection with the token;
- an accepted `auth` gets no answer: send your `subscribe` messages right away. Any later `auth` on the same connection is ignored.

If the first message is not a valid `auth`, or does not arrive within 5 seconds, the connection is closed with code `4001`.

## Signing in to the portal

Your [account](https://fathomcharts.com/account) (keys, plans, billing, usage) has no password. On the [Sign in](https://fathomcharts.com/login) page, enter your email address: you receive a sign-in link **valid for 15 minutes, usable once**. You can request up to 5 links per hour.

Link rejected? It has expired or was already used: request a new one. Once signed in, your session stays open for 30 days in that browser.
