Most FX integrations start the same way: a developer needs a clean EUR/USD chart for the last three months, a live mid-price for the current session, and a single auth scheme that works for both. The retail path is to wire up one vendor for historical bars and another for streaming ticks, then spend an afternoon reconciling timestamps that don't agree. The SiftingIO Forex endpoints exist so that work goes away. One bearer token, one JSON shape, REST for history, WebSocket for live.
This post walks through the practical wiring: pulling historical FX bars over REST, subscribing to live quotes over WebSocket, and joining the two so a dashboard or a backtest sees a continuous price series across the boundary.
Pulling historical FX bars over REST#
The REST surface for Forex follows the same /v1/<asset_class>/<resource> pattern as the rest of the API. A typical request for one-minute EUR/USD bars looks like this:
curl -H "Authorization: Bearer $SIFTING_KEY" \
"https://api.sifting.io/v1/fx/bars?symbol=EURUSD&interval=1m&from=2026-05-01T00:00:00Z&to=2026-05-08T00:00:00Z"
The response is a list of bar objects. Each bar carries the symbol, the asset class, an open timestamp, OHLCV fields, and a venue tag that identifies which liquidity aggregation produced the bar. FX bars are sourced from institutional liquidity providers, not retail broker feeds, so the mid-price at any given second is closer to what a prime broker would quote than what a retail MT4 widget shows.
A Python script that loads a week of EUR/USD into a pandas DataFrame is short:
import os, requests, pandas as pd
headers = {"Authorization": f"Bearer {os.environ['SIFTING_KEY']}"}
params = {
"symbol": "EURUSD",
"interval": "1m",
"from": "2026-05-01T00:00:00Z",
"to": "2026-05-08T00:00:00Z",
}
r = requests.get("https://api.sifting.io/v1/fx/bars", headers=headers, params=params)
r.raise_for_status()
bars = pd.DataFrame(r.json()["bars"])
bars["ts"] = pd.to_datetime(bars["ts"], utc=True)
bars = bars.set_index("ts").sort_index()
The DataFrame is the natural home for any further work: resampling to 5-minute bars, computing rolling volatility, or joining against an economic-event series for a CPI or NFP study. Because the JSON shape matches the equities and crypto endpoints, the same loader function works for an AAPL pull or a BTC/USDT pull with no schema branching.
For heavier pulls (a full year of 1-minute bars on a major pair) the response is paginated and gzip-compressed. Request Accept-Encoding: gzip explicitly when calling from a language whose HTTP client does not negotiate compression by default; the payload shrinks by roughly an order of magnitude on bar data.
Subscribing to live FX quotes over WebSocket#
Live quotes go over a WebSocket stream rather than a REST poll. The endpoint is:
wss://stream.sifting.io/v1/fx?symbols=EURUSD,GBPUSD,USDJPY
Authentication is the same bearer token, passed as an Authorization header during the initial upgrade. Once connected, the server pushes JSON messages with the same field names a REST quote uses: symbol, asset_class, venue, ts, bid, ask, and a derived mid. A minimal Node consumer:
import WebSocket from "ws";
const ws = new WebSocket(
"wss://stream.sifting.io/v1/fx?symbols=EURUSD,GBPUSD",
{ headers: { Authorization: `Bearer ${process.env.SIFTING_KEY}` } },
);
ws.on("message", (raw) => {
const q = JSON.parse(raw);
if (q.symbol === "EURUSD") {
const mid = (q.bid + q.ask) / 2;
console.log(q.ts, q.symbol, mid.toFixed(5));
}
});
ws.on("close", () => {
setTimeout(reconnect, 1000);
});
The stream is incremental: a quote is delivered when the bid or ask changes, not on a fixed cadence. For a chart that needs a tick every second regardless of activity, debounce on the client side rather than expecting the server to fill quiet periods.
A polling-REST approach would work for low-frequency use cases (a once-a-minute portfolio refresh, say) but the latency and cost shape changes quickly. Polling /v1/fx/quotes?symbol=EURUSD once a second on ten pairs is ten requests per second against the rate limit; the same data over WebSocket is one open connection and effectively zero per-tick budget. The exchange rate between polling cost and streaming convenience tilts toward streaming any time the dashboard or strategy cares about sub-minute movement.
Joining REST history and live ticks#
A common pattern is a chart or a strategy that loads a historical window on startup and then keeps itself current from the live stream. The trick is making sure the two halves meet without a gap or an overlap.
The cleanest approach is to open the WebSocket first and start buffering, then issue the REST call for the historical window up to the moment the socket connected. When the REST response returns, append it to the front of the buffer and replay buffered ticks on top. Because both the REST bars and the WebSocket quotes carry UTC ISO timestamps with millisecond precision, dedup is a single comparison on ts. A research script that flips this order, REST first then socket, will miss every tick that arrived during the REST roundtrip, and the gap is silent because nothing throws an error.
For a dashboard rendering candles, aggregate ticks into open OHLCV buckets on the client. Lightweight Charts and similar libraries accept either bar updates or tick updates, and the SiftingIO quote shape maps directly onto either path. The same WebSocket feed can power a candle chart and a price ticker without a second connection.
Common pitfalls#
A few things catch developers the first time they wire this up.
The interval parameter on /v1/fx/bars is strict about the values it accepts. Passing interval=1minute or interval=60s returns a 400 with an invalid_interval code; the canonical values are 1m, 5m, 15m, 1h, 1d. Check the docs page for the full set before extending a list of intervals in a UI dropdown.
WebSocket disconnects are normal during low-volume hours and across infrastructure events. The client must reconnect, and it must resubscribe (the server does not remember the symbol list across connections). A bare ws.on("close", reconnect) without a backoff will hammer the endpoint during an outage and trip the per-IP rate limit; an exponential backoff capped at 30 seconds is the usual shape.
FX session boundaries are not the same as equity session boundaries. The market trades effectively 24 hours from Sunday evening UTC to Friday evening UTC, with a brief gap over the weekend. A backtest that assumes weekday-only bars will silently drop Sunday evening data and produce odd-looking gaps every Monday morning. Filter on ts if you need a strict weekday view, but do not assume the data is already filtered for you.
One auth token, one JSON shape, REST and WebSocket sharing the same field names. That is the point of the unified Forex endpoints: less plumbing, more time on the signal.

