Skip to content

Quickstart

The examples use fetch and the browser’s WebSocket, which Node.js has too.

  1. Ask for tokens, with only the fields you need.

    GET https://api.intea.rs/tokens?ids=wrap.near&fields=symbol,price_usd
    {
    "height": 219969220,
    "timestamp_millis": 1792003477277,
    "columns": ["account_id", "symbol", "price_usd"],
    "rows": [["wrap.near", "wNEAR", "4.815189744118901"]],
    "total": 1,
    "missing": []
    }

    Tokens come as a table: the column names once, then a row per token, in the same order. account_id always comes first.

  2. Turn the table into objects, if that’s easier to work with.

    /**
    * Turns a table into `{ <key>: { <column>: value } }`, keyed by its first column
    */
    function tableToObjects({ columns, rows }) {
    const objects = {};
    for (const row of rows) {
    const object = {};
    columns.slice(1).forEach((column, i) => (object[column] = row[i + 1]));
    objects[String(row[0])] = object;
    }
    return objects;
    }
    const params = new URLSearchParams({
    ids: 'wrap.near,usdt.tether-token.near',
    fields: 'symbol,price_usd,liquidity_usd,price_change_24h',
    });
    const response = await fetch(`https://api.intea.rs/tokens?${params}`);
    const result = await response.json();
    if (!response.ok) throw new Error(result.error.message);
    const tokens = tableToObjects(result);
    // tokens['wrap.near'] is { symbol: 'wNEAR', price_usd: '4.81', ... }

Every method that answers once works like this over HTTP. See Using HTTP for how params go in a URL.

  1. Connect. The server’s first message is a hello, with the latest block and the limits:

    { "hello": { "version": 2, "height": 219969289, "timestamp_millis": 1792003517185, "limits": { ... } }, "credits_used": 0 }
  2. Subscribe. Send a request with an id you choose. The reply has the same id, and every update after it has sub set to that id:

    → { "id": 1, "method": "tokens.subscribe", "params": { "fields": ["symbol", "price_usd"], "ids": ["wrap.near"] } }
    ← { "id": 1, "result": { "height": 219969289, "columns": ["account_id", "symbol", "price_usd"], "rows": [["wrap.near", "wNEAR", "4.81"]], ... } }
    ← { "sub": 1, "height": 219969292, "set": { "wrap.near": { "price_usd": "4.82" } }, ... }

    Updates only carry what changed: here, only price_usd of wrap.near.

  3. Keep a list up to date. This follows the 20 tokens with the most volume in the last 24 hours. Prices only update once they’ve moved 0.1%, and tokens that drop out of the top 20 are in left:

    The examples on this site use this small client. It matches replies to requests by id, and passes each subscription’s updates to its callback:

    /** A minimal Intear API client: requests resolve with their result, and pushes go to their subscription's callback */
    export class IntearApi {
    #socket;
    #nextId = 1;
    #pending = new Map();
    #subscriptions = new Map();
    ready;
    constructor(url = 'wss://api.intea.rs/ws') {
    this.#socket = new WebSocket(url);
    this.ready = new Promise((resolve, reject) => {
    this.#socket.addEventListener('error', () => reject(new Error('Connection failed')));
    this.#socket.addEventListener('message', (event) => {
    const message = JSON.parse(event.data);
    if ('hello' in message) {
    resolve(message.hello);
    } else if ('sub' in message) {
    this.#subscriptions.get(message.sub)?.(message);
    } else if ('id' in message) {
    const request = this.#pending.get(message.id);
    this.#pending.delete(message.id);
    if ('error' in message) {
    this.#subscriptions.delete(message.id);
    const error = message.error;
    request?.reject(new Error(`${error.code}: ${error.message}`));
    } else {
    request?.resolve(message.result);
    }
    }
    });
    });
    }
    /**
    * Sends a request, and resolves with its result
    */
    async request(method, params) {
    await this.ready;
    const id = this.#nextId++;
    const result = new Promise((resolve, reject) => this.#pending.set(id, { resolve, reject }));
    this.#socket.send(JSON.stringify({ id, method, params }));
    return result;
    }
    /**
    * Makes a subscription. `onPush` gets each of its pushes, after the result
    */
    async subscribe(method, params, onPush) {
    await this.ready;
    const id = this.#nextId++;
    this.#subscriptions.set(id, onPush);
    const result = new Promise((resolve, reject) => this.#pending.set(id, { resolve, reject }));
    this.#socket.send(JSON.stringify({ id, method, params }));
    return { sub: id, result: await result };
    }
    async unsubscribe(sub) {
    await this.request('unsubscribe', { sub });
    this.#subscriptions.delete(sub);
    }
    close() {
    this.#socket.close();
    }
    }
    const tokens = new Map();
    const { result } = await api.subscribe(
    'tokens.subscribe',
    {
    fields: ['symbol', 'icon_hash', 'price_usd', 'volume_usd_24h'],
    where: { min_reputation: 'NotFake' },
    sort: { by: 'volume_usd_24h', desc: true },
    limit: 20,
    min_change: { price_usd: '0.1%', volume_usd_24h: '1%' },
    precision: 6,
    },
    (push) => {
    if (push.end) throw new Error(`Subscription ended: ${push.end.message}`);
    for (const [token, fields] of Object.entries(push.set ?? {})) {
    tokens.set(token, { ...tokens.get(token), ...fields });
    }
    for (const token of push.left ?? []) tokens.delete(token);
    render(tokens);
    },
    );
    for (const [token, fields] of Object.entries(tableToObjects(result))) tokens.set(token, fields);
    render(tokens);
  • Connecting: how the protocol works, and what to do after a reconnect.
  • Live token lists: thresholds, intervals, and following only the tokens on screen.
  • History, then live: show past trades and keep the list live without a gap.