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. 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.
| 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:
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 |
history | Historical queries and their estimates |
export | Exports |
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:
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.frontallowsNQ.frontand the contract it designates today (NQZ6);NQZ6allowsNQZ6, andNQ.frontas long as it designatesNQZ6. Over history, a query onNQ.frontreads the contract designated atfrom: for a range whenNQ.frontdesignatedNQU6, a key restricted toNQZ6is rejected with403 FORBIDDEN;NQ.frontstays 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
instrumentsentry is literally one of its own (a key restricted toNQZ6cannot manage a key restricted toNQ.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, 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: 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):
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. 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:
- your server requests a stream token with its key;
- it passes the token to the page;
- the page opens the WebSocket and sends the token.
On the server:
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:
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:
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
authgets no answer: send yoursubscribemessages right away. Any laterauthon 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 (keys, plans, billing, usage) has no password. On the Sign in 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.