Reference

Updates and maintenance

We deploy updates during market hours without stopping the service. Your clients may see a reconnection or a pause of a few seconds; they never lose data, as long as they follow the rules on this page.

The TypeScript SDK applies every rule of this page by itself. Read on if you write your own client, or to know what your application will observe.

What happens during an update

What is updatedReal time (WebSocket)REST
The stream serversA notice (kind: "reconnect"), then the connection is closed with code 1012 within 20 seconds. Reconnect and resume from your cursors: nothing is lost, nothing is sent twice.—
The computation serversThe connection stays open. Each subscription receives a status source_delayed, then live once the computation has caught up, usually within 30 seconds. Late messages arrive then, in order.—
How an indicator is computedEach subscription receives a reset (engine_change), a new subscribed, then a new snapshot.A paginated query or an export started before the change cannot be continued: 410 CURSOR_EXPIRED. Start it again.
The API servers—Requests in progress complete. Nothing to do.

A computation change is rare and announced in advance with a maintenance notice. It is the only case where you must rebuild a state: every other update is a pause followed by the missed messages.

Maintenance notices

When we plan a maintenance with a visible effect (a pause longer than usual, a computation change), every connection receives a notice, as soon as it is announced:

JSON
{"t":"notice","kind":"maintenance","id":"6f1c…","effectiveAt":"1791295200000000000","endsAt":"1791295500000000000","message":"Computation update: each subscription restarts from a new snapshot."}
  • effectiveAt and endsAt: the window during which the service may pause, in nanoseconds since January 1, 1970 UTC, as strings. Show the delay to your users (effectiveAt minus now), or schedule your own jobs around it;
  • every new connection receives the notices of the windows not over yet, right after it opens: a client that connects after the announcement is warned too;
  • a window can be moved: you then receive a notice with the same id and new times. A cancellation is announced with {"t":"notice","kind":"maintenance-cancelled","id":"6f1c…"};
  • the windows announced and not over are also listed by GET /v1/status, in maintenance.

A notice is information: you have nothing to send back, and your subscriptions continue until the maintenance actually happens.

Rules for your WebSocket client

  1. Keep, for each subscription, the last cursor and the engine of its latest subscribed. The cursor tells the server where you stopped; engine tells it which computation produced your state.
  2. Reconnect on 1012, on any close not listed as final on the WebSocket page, and after 15 seconds without any message, with a delay that doubles after each failure (up to 30 seconds) plus random jitter.
  3. Resume each subscription with both values:
JSON
{"t":"subscribe","sub":"bt","instrument":"NQ.front","indicator":"big-trades","params":{"minimum":30},"mode":"confirmed","from":{"cursor":"20732.44294.1","engine":"3ee45f60a463ab09"}}

If the computation changed while you were away, you receive subscribed, a reset (engine_change) and a snapshot, instead of messages computed differently from your state. Without engine, the server cannot tell, and you could keep objects that the new computation no longer produces. 4. On a reset, discard the state of the subscription and start again from the snapshot that follows. 5. Treat source_delayed as a pause, not an error. Keep the connection: the stream resumes by itself and sends what was late. 6. Ignore any upsert or remove whose cursor is not after the last one you processed: after a reconnection, a message can arrive twice.

Rules for your REST client

  • Retry a 503 that carries a Retry-After header after the delay it gives: the request was not processed.
  • Retry a GET that failed without an answer (network error, connection closed) or with a 502, after a short delay that grows with each attempt: a read has no effect other than its answer. A query page or an export that failed without an answer may have been computed and charged: retrying it is safe, but may be charged again.
  • Paginated queries: a 410 CURSOR_EXPIRED on a next page means the computation changed between two pages. Start the query again from its first page, and drop the pages already read.
  • Exports: after a cut, resume with the X-Export-Token of the first response (see Exports). A 410 CURSOR_EXPIRED on the resume means the computation changed since the export started: start the export again, and drop the lines already received.

With the SDK

The SDK reconnects, resumes with the cursor and the engine of each subscription, drops duplicates, retries the REST requests the rules above allow, and resumes cut exports. Your code only sees events:

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

const stream = new FathomChartsStream({
  url: 'wss://stream.fathomcharts.com/v1',
  apiKey: process.env.FATHOM_CHARTS_API_KEY!,
  onNotice: (notice) => {
    if (notice.kind === 'maintenance') {
      const inMinutes = Math.round((Number(BigInt(notice.effectiveAt!) / 1_000_000n) - Date.now()) / 60_000);
      console.log(`Maintenance in ${inMinutes} min: ${notice.message}`);
    }
  },
});

for await (const event of stream.subscribe({ instrument: 'NQ.front', indicator: 'big-trades', params: { minimum: 30 } })) {
  switch (event.type) {
    case 'reset': /* discard the local state: a snapshot follows */ break;
    case 'snapshot': /* the new starting state */ break;
    case 'status': /* source_delayed: a pause, then live */ break;
    case 'notice': /* the same notices, in each subscription */ break;
  }
}
  • stream.maintenance lists the windows announced and not over;
  • to resume after your own process restarts, save sub.cursor and sub.engine, then subscribe again with from: { cursor, engine } (see SDK);
  • exportNdjson() resumes a cut export by itself; it throws CURSOR_EXPIRED when the computation changed since the export started.