adona-robot — market data by capacity

A live forex WebSocket API

Live bid and ask quotes, and candles as they close, for 46 currency pairs and 5 metals, on one WebSocket opened from your server or straight from a browser.

Connecting
Your server exchanges the API key for a token that lives 12 hours. With the token, POST /v1/ws-ticket gives a single-use ticket, offered as the second subprotocol next to adona.data.v1, never in the URL. Then one subscribe frame names the channels.
Channels
quote:SYMBOL for the live bid and ask, candle:SYMBOL:TIMEFRAME for bars as they close, on S1, M1, M5, M15, H1, H4, D1. Up to 64 channels in one subscribe frame. On subscribe, each candle channel first sends its recent bars, billed in slices like a REST page: every reconnect pays for them again.
Connections
A paid plan never refuses a connection: the month's peak of simultaneous connections sets the next invoice's plan. The trial allows 3 simultaneous connections. A close with code 1008 and a reason, such as a revoked key or an ended trial, is an answer: do not reconnect in a loop on it. Other closes are temporary: reconnect after a pause.
What it costs
Plans from CHF 500 a month. Redistribution is allowed on every plan: a symbol your connections follow live for more than 80% of its open hours in a month adds CHF 150 to the invoice, on top of the plan.

Facts as of 03.10.2026.

The code: the live stream, in JavaScript

stream.mjs
// Node 22+: live quotes and closed bars on one WebSocket. ADONA_API_KEY=... node stream.mjs
// In a browser the WebSocket part is the same; the token comes from your server, never the key.
const API = "https://api.adona-robot.com";
const STREAM = "wss://api.adona-robot.com/v1/stream";

async function post(path, bearer, body) {
  const response = await fetch(`${API}${path}`, {
    method: "POST",
    headers: { Authorization: `Bearer ${bearer}`, "Content-Type": "application/json" },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`refused: ${response.status} ${await response.text()}`);
  return response.json();
}

// your server exchanges the key for a token
const { access_token: token } = await post("/v1/tokens", process.env.ADONA_API_KEY, { end_user_id: "u-42" });
// a single-use ticket, offered as the second subprotocol, never in the URL
const { ticket } = await post("/v1/ws-ticket", token);

const ws = new WebSocket(STREAM, ["adona.data.v1", `ticket.${ticket}`]);
ws.onopen = () =>
  ws.send(JSON.stringify({ type: "subscribe", request_id: "sub", payload: { channels: ["quote:EURUSD", "candle:EURUSD:M1"] } }));
ws.onmessage = (event) => {
  const { type, payload } = JSON.parse(event.data);
  if (type === "quote.update" || type === "quote.snapshot") console.log(type, payload.symbol, payload.bid, payload.ask);
  else if (type === "candle.snapshot") console.log(type, payload.channel, payload.candles.length, "bars");
  else if (type === "candle.bar") console.log(type, payload.symbol, payload.tf, payload.ts, payload.c); // short keys
  else console.log(type, payload);
};
// 1008 with a reason (revoked, trial ended) is an answer: do not reconnect in a loop on it
ws.onclose = (event) => console.log("closed", event.code, event.reason);

The quickstart: from the key to the live stream, step by step

Open an account

Related guides

Pairs and metals