How do you get a clean, consistent price for EURUSD when forex has no central exchange and every venue quotes a slightly different number? Unlike listed stocks, the foreign exchange market is over-the-counter and fragmented across many independent venues, so "the" FX price is really a question of whose feed you trust. A Forex Market Data API answers that by handing you one normalized price and one schema for every currency pair, instead of a separate integration per venue.
This guide walks through what a Forex API actually returns, how to pull live quotes and historical bars for real pairs, how to stream ticks over WebSocket, and the specific mistakes that trip up developers on their first integration.
What a Forex API gives you#
A Forex Market Data API exposes three kinds of data: live snapshots (the latest bid/ask quote for a pair), historical OHLCV bars (open, high, low, close, and volume over an interval), and a streaming feed for real-time updates. For major, minor, and exotic pairs, SiftingIO serves live snapshots and historical OHLCV bars over REST, and streams live ticks over WebSocket. The same interfaces cover EURUSD, GBPUSD, USDJPY, and exotic pairs such as USDTRY.
The important part for FX specifically is the price itself. Because there is no single exchange of record, SiftingIO aggregates and normalizes derived quotes from multiple sources rather than redistributing a raw single-venue feed. These are reference values, not executable prices at your broker. The data methodology explains how to interpret the output.
Pulling live and historical FX quotes#
Set the SIFTING_KEY environment variable to your API key before running these examples. REST calls use the X-API-Key header, not a Bearer token. To get the latest best bid/ask for a pair, hit the live quote endpoint with the forex venue:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/last/quote/forex/EURUSD"
That returns the current best bid and ask plus a timestamp. For historical analysis, backtests, or charting, you want OHLCV bars instead. The historical endpoint groups by asset class, and forex bars require gzip:
# Python 3 selects a recent window within Free history.
START_DATE=$(python3 -c 'from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) - timedelta(days=7)).date())')
curl --get --compressed -H "X-API-Key: $SIFTING_KEY" \
-H "Accept-Encoding: gzip" \
--data-urlencode "start=$START_DATE" \
--data-urlencode "interval=1h" \
--data-urlencode "limit=100" \
"https://api.sifting.io/v1/hist/forex/EURUSD/bars"
The first historical request needs start; end is optional and defaults to now. Bounds accept YYYY-MM-DD or an RFC 3339 timestamp. The example uses the last seven days so its dates remain inside the Free plan's one-month history window. --compressed decodes the compressed response; sending the gzip header alone does not do that.
Supported intervals are 1m, 5m, 15m, 30m, 1h, 1d, 1w, and 1mo; inspect meta.interval for the interval actually returned. Each bar's t is its opening time in Unix epoch milliseconds (UTC), not an RFC 3339 string. The o, h, l, c, and v values are JSON numbers. The v field contains aggregated traded volume for the bar, summed across the contributing sources SiftingIO consolidates.
Large history pulls page with an opaque cursor. When meta.next_cursor is present and non-empty, pass it as the next request's cursor parameter on the same pair endpoint. Preserve the other query parameters and URL-encode the cursor. Stop when it is absent or empty; do not wait specifically for null. Forex pages default to 1,000 bars and are capped at 2,000. The example requests 100. Historical depth is one month on Free, one year on Builder, and full available history on Pro and above; availability varies by pair. See the Forex bars reference.
If you'd rather use a client library, official Python, JavaScript, and Go SDKs cover the REST and WebSocket interfaces. Install Python with pip install siftingio or JavaScript with npm install @siftingio/sdk, then follow the language-specific reference in the docs.
Streaming live forex ticks over WebSocket#
Polling /v1/last/quote in a loop works for a dashboard that refreshes every few seconds, but it burns your monthly call quota and adds latency. For live prices, open a WebSocket and subscribe to the fx product. The example below runs in Node.js 22 or later and uses the key query parameter for authentication. Keep the key server-side; do not paste it into a public website's JavaScript:
const key = process.env.SIFTING_KEY;
if (!key) throw new Error("Set SIFTING_KEY before running this example");
const ws = new WebSocket(
"wss://stream.sifting.io/ws/v1?key=" + encodeURIComponent(key)
);
let keepalive;
ws.onopen = () => {
ws.send(JSON.stringify({
op: "subscribe",
product: "fx",
symbols: ["EURUSD", "USDJPY", "GBPUSD"]
}));
keepalive = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ op: "ping" }));
}
}, 30_000);
};
ws.onclose = () => clearInterval(keepalive);
ws.onerror = () => console.error("WebSocket connection error");
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.f === "tick") {
console.log(msg.s, msg.p, msg.t); // symbol, price, epoch ms
}
};
Server frames discriminate on the f field. A tick frame carries the symbol s, the consensus price p, bid and ask, and a timestamp t in Unix epoch milliseconds. On subscribe, the server first replays the last cached value for each symbol, then sends live updates, so your UI paints immediately instead of waiting for the next market move.
Update availability depends on the pair, market session, and incoming observations. Plan limits describe access, concurrent connections, and symbol subscriptions; they are not a promise that every pair produces a new observation at a fixed frequency. Check timestamps and the current plan limits when sizing your integration.
Common pitfalls#
A few things reliably catch developers on the first pass.
Forgetting gzip on historical bars. These endpoints require an Accept-Encoding value that permits gzip. Without it, the API returns HTTP 406 with error: "gzip_required". Use both the header and --compressed with curl, as above. Many HTTP clients negotiate and decode compression automatically; check your client's behavior if the response looks like binary data.
Requesting historical bars over WebSocket. Historical OHLCV bars are available only through REST. The WebSocket feed streams live ticks, not OHLCV bars. Fetch historical bars over REST when you need to backfill a chart or run a backtest.
Letting the WebSocket idle out. Send regular client keepalives, as the 30-second timer in the example does. Receiving market ticks is not a substitute for sending them. The minimal example stops at connection close; in production, reconnect with backoff and resubscribe after connecting. The WebSocket protocol covers connection handling.
Treating the last quote as a new observation. Always inspect its t timestamp before labeling it live. A cached value may predate the current session, especially across weekends. Handle non-success HTTP responses separately from successful responses whose timestamps are too old for your application.
Start with the free tier, wire up a single pair, and confirm the bid/ask and bar shapes before you scale to a full pair list. The Forex API product page brings together pair coverage, REST and WebSocket examples, and plan information. Start building free.



