Forex API Documentation
Real-time and historical market data for forex, stocks and crypto — REST, WebSocket and MCP.
Introduction
ForexAPI serves 1,400+ forex pairs, 1,800+ crypto pairs and US stocks from a single REST API, with sub-50ms cached responses for live prices and full OHLC candle history.
Base URL
https://connect.forexapi.coFormats
JSON everywhere · UTF-8 · epoch-seconds timestampsInteractive testing
Try every endpoint in the Playground →All symbol names are case-insensitive and suffix-optional: aapl resolves to AAPL.
Quick Start
- Get an API key — sign in and create a key on the API Keys page, then export it:
export FXAPI_KEY="fxapi_live_...". - Make your first request — live prices for one or many symbols:
curl "https://connect.forexapi.co/v1/prices?symbols=EURUSD,USDJPY" \
-H "Authorization: Bearer $FXAPI_KEY"3. Read the response — prices keyed by symbol:
{
"EURUSD": {
"symbol": "EURUSD",
"category": "Forex",
"price": 1.105,
"change": 0.002,
"bid": 1.1049,
"ask": 1.1051,
"digits": 5
},
"USDJPY": {
"symbol": "USDJPY",
"category": "Forex",
"price": 157.261,
"change": -0.0661,
"bid": 157.26,
"ask": 157.264,
"digits": 3
}
}Authentication
Every endpoint requires an API key created on your API Keys page. Keys look like fxapi_live_Xk3m9QpT4vXa_…, are shown in full exactly once at creation (store them safely — only a hash is kept server-side), and support editing, rotation with a grace period, instant revocation, optional IP/CIDR allowlists and scopes.
Send the key in the Authorization header; keep it in an environment variable rather than in source code:
export FXAPI_KEY="fxapi_live_..." # from the API Keys page
curl "https://connect.forexapi.co/v1/prices?symbols=EURUSD" \
-H "Authorization: Bearer $FXAPI_KEY"Requests from an address outside a key's IP allowlist get 403 Forbidden. Requests outside a key's scopes (forex/crypto) also get 403. WebSocket and MCP use the same key — see their sections below. Legacy JWT tokens keep working until the deprecation date shown on the API Keys page.
Rate Limits & Plans
Fixed 60-second windows per API key, plus a monthly request quota that resets with your billing cycle. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; 429 responses add Retry-After. Rate-limited requests are not billed.
| Plan | Monthly | Per minute | History | Crypto | WebSocket |
|---|---|---|---|---|---|
| Free ($0/mo) | 1,000 | 10 | — | — | — |
| Starter ($19/mo) | 4,000 | 30 | 1 year | — | — |
| Builder ($29/mo) | 10,000 | 60 | 5 years | ✓ | — |
| Developer ($49/mo) | 15,000 | 100 | 5 years | ✓ | — |
| Professional ($149/mo) | 50,000 | 500 | 10 years | ✓ | ✓ |
Exceeding a limit returns a 429 whose message names your plan, the limit and the reset — see Errors.
Market Data
Live quotes and reference data for forex, metals, indices, commodities and stocks. Responses shown were captured from the live feed — to run your own requests, open the Playground in the dashboard.
GET /v1/pricesLatest Prices
Live quote for one or many symbols — forex, metals, indices, commodities and stocks. Up to 100 symbols per request, comma-separated.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbols* | string | — | Comma-separated symbol list, e.g. EURUSD,USDJPY,XAUUSD (required, max 100) |
Example Request
curl -G "https://connect.forexapi.co/v1/prices" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbols=EURUSD,USDJPYResponse
{
"EURUSD": {
"symbol": "EURUSD",
"category": "Forex",
"price": 1.13673,
"change": -0.0352,
"ask": 1.13673,
"askHigh": 1.13745,
"askLow": 1.13627,
"bid": 1.13673,
"bidHigh": 1.13743,
"bidLow": 1.13627,
"digits": 5
},
"USDJPY": {
"symbol": "USDJPY",
"category": "Forex",
"price": 157.261,
"change": -0.0661,
"ask": 157.264,
"askHigh": 157.588,
"askLow": 157.209,
"bid": 157.261,
"bidHigh": 157.585,
"bidLow": 157.206,
"digits": 3
}
}💡 Unknown symbols are returned as null instead of erroring, so one bad ticker never fails the batch. Quotes update on every tick — for continuous delivery use the WebSocket.
GET /v1/currenciesSupported Currencies
Every supported fiat currency with display metadata: localized names, flag artwork, sign, and circulating coins/banknotes — ready for building currency pickers.
Example Request
curl "https://connect.forexapi.co/v1/currencies" \
-H "Authorization: Bearer $FXAPI_KEY"Response
{
"AUD": {
"symbol": "AUD",
"name": "Australian Dollar",
"countryCode": "AU",
"countryName": "Australia",
"iso_code": "036",
"sign": "$",
"coins": [
"$2",
"$1",
"50¢",
"20¢",
"10¢",
"5¢"
],
"banknotes": [
"$100",
"$50",
"$20",
"$10",
"$5"
]
},
"EUR": {
"symbol": "EUR",
"name": "Euro",
"countryCode": "EU",
"countryName": "European Union",
"iso_code": "978",
"sign": "€",
"coins": [
"€2",
"€1",
"50¢",
"20¢",
"10¢",
"5¢",
"2¢",
"1¢"
],
"banknotes": [
"€500",
"€200",
"€100",
"€50",
"€20",
"€10",
"€5"
]
}
}GET /v1/symbolsSupported Symbols
Full instrument directory for a category with the latest price attached — stock entries carry exchange, ISIN, industry and sector.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
category* | string | — | One of: Forex, Stocks, Indices, Commodities, Crypto (required) |
Example Request
curl -G "https://connect.forexapi.co/v1/symbols" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d category=ForexResponse
{
"NOVOB": {
"symbol": "NOVOB.DK",
"name": "Novo Nordisk A/S",
"digits": 4,
"category": "Stocks",
"country": "DNK",
"isin": "DK0062498333",
"industry": "Biotechnology",
"sector": "Healthcare",
"exchange": "Nasdaq Copenhagen",
"price": 642.5
},
"DSV": {
"symbol": "DSV.DK",
"name": "DSV A/S",
"digits": 4,
"category": "Stocks",
"country": "DNK",
"isin": "DK0060079531",
"industry": "Integrated Freight & Logistics",
"sector": "Industrials",
"exchange": "Nasdaq Copenhagen",
"price": 1583
}
}Conversion
Currency conversion for fiat and crypto — cross pairs are routed automatically when no direct symbol exists. Responses shown were captured from the live feed — to run your own requests, open the Playground in the dashboard.
GET /v1/convertCurrency Conversion
Convert an amount between any two supported currencies (including cross pairs with no direct symbol) using the latest rates.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from* | string | — | Base currency code, e.g. EUR (required) |
to* | string | — | Quote currency code, e.g. USD (required) |
amount* | float | — | Amount to convert (required) |
Example Request
curl -G "https://connect.forexapi.co/v1/convert" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d from=EUR -d to=USD -d amount=100Response
{
"success": true,
"query": {
"from": "EUR",
"to": "USD",
"amount": 100
},
"info": {
"amount": 100,
"exchange_rate": 1.08913,
"timestamp": 1790654316
},
"result": 108.913
}GET /v1/crypto/convertCrypto Conversion
Convert between crypto assets (and USDT) using live aggregate rates — multi-hop routes are resolved automatically when no direct pair exists.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from_symbol | string | BTC | Asset code to convert from, e.g. BTC |
to_symbol | string | USDT | Asset code to convert to, e.g. USDT or USD |
amount | float | 5 | Amount to convert |
Example Request
curl -G "https://connect.forexapi.co/v1/crypto/convert" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d from_symbol=BTC -d to_symbol=USDT -d amount=0.5Response
{
"amount": 0.5,
"converted_amount": 204068,
"exchange_rate": 408136
}💡 Requires the Builder plan or higher. Unreachable pairs return a 400 "No conversion path found between {from} and {to}".
OHLC Candles
Open-high-low-close candles, from the last tick to years of history. Closed bars only. Responses shown were captured from the live feed — to run your own requests, open the Playground in the dashboard.
POST /v1/ohlc/lastLast OHLC Candle
The most recent candle for one symbol. Takes a small JSON body instead of query parameters.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol* | string (body) | — | Instrument symbol (required) |
period | string (body) | PERIOD_M1 | Candle timeframe, e.g. PERIOD_M5, PERIOD_H1, PERIOD_D1 |
Example Request
curl -X POST "https://connect.forexapi.co/v1/ohlc/last" \
-H "Authorization: Bearer $FXAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"symbol":"EURUSD","period":"PERIOD_H1"}'Response
[
{
"symbol": "EURUSD",
"open": 1.13675,
"close": 1.13667,
"high": 1.13676,
"low": 1.13666,
"period": "PERIOD_H1"
}
]GET /v1/ohlc/historyOHLC History
Historical closed candles in two modes: an epoch/ISO range (start + end) or the most recent N bars (bars). Only CLOSED bars are returned — the still-forming candle is never included. Identical windows are cached.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol* | string | — | Instrument symbol (required) |
period | string | PERIOD_H1 | Candle timeframe |
start* | int | ISO | — | Range mode: inclusive start (epoch seconds or ISO 8601) |
end* | int | ISO | — | Range mode: exclusive end (epoch seconds or ISO 8601) |
bars* | int | — | Count mode: number of most-recent closed bars (1-5000). Use either start+end or bars, never both. |
Example Request
curl -G "https://connect.forexapi.co/v1/ohlc/history" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbol=EURUSD -d period=PERIOD_H1 -d bars=3Response
{
"symbol": "EURUSD",
"requestedSymbol": "EURUSD",
"period": "PERIOD_H1",
"count": 2,
"gmtOffsetSeconds": 10800,
"bars": [
{
"time": 1758240000,
"open": 1.1042,
"high": 1.1051,
"low": 1.1038,
"close": 1.1049,
"tickVolume": 1234
},
{
"time": 1758243600,
"open": 1.1049,
"high": 1.1056,
"low": 1.1041,
"close": 1.1053,
"tickVolume": 1102
}
]
}💡 Depth is plan-gated: Free has no history, Starter 1 year, Builder/Developer 5 years, Professional 10 years — a request past your horizon returns a 403 naming the earliest available date. Bar timestamps are feed server time; convert with gmtOffsetSeconds.
Crypto
Aggregated crypto market data from Binance, Gate.io and Bybit. Requires the Builder plan or higher. Responses shown were captured from the live feed — to run your own requests, open the Playground in the dashboard.
GET /v1/crypto/pricesCrypto Prices
Live 24-hour statistics for crypto pairs aggregated from Binance, Gate.io and Bybit. Up to 10 symbols per request.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbols* | string | — | Comma-separated pair list, e.g. BTCUSDT,ETHUSDT (required, max 10) |
Example Request
curl -G "https://connect.forexapi.co/v1/crypto/prices" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbols=BTCUSDT,ETHUSDTResponse
{
"BTCUSDT": {
"price": 408136,
"change24h": -2150.4,
"change24hPct": -0.52,
"open24h": 410286.4,
"high24h": 412500,
"low24h": 406900,
"bid": 408135.9,
"ask": 408136.1,
"timestamp": 1790654300
},
"ETHUSDT": null
}💡 Requires the Builder plan or higher — Free and Starter receive a 403 naming their plan. Unavailable pairs return null per symbol.
GET /v1/crypto/currenciesSupported Crypto Currencies
Paginated directory of supported crypto assets with metadata (name, website, socials) sourced from LiveCoinWatch.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 100 | Page size, 1-100 |
page | int | 1 | Page number |
Example Request
curl -G "https://connect.forexapi.co/v1/crypto/currencies" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d limit=2 -d page=1Response
{
"data": {
"BTC": {
"name": "Bitcoin",
"sign": "BTC",
"webp32": "https://s3.coinmarketcap.com/…/btc.webp",
"website": "https://bitcoin.org",
"twitter": "https://twitter.com/bitcoin",
"telegram": null,
"discord": null
},
"ETH": {
"name": "Ethereum",
"sign": "ETH",
"webp32": "https://s3.coinmarketcap.com/…/eth.webp",
"website": "https://ethereum.org",
"twitter": "https://twitter.com/ethereum",
"telegram": null,
"discord": null
}
},
"page": 1,
"limit": 2,
"total": 1200,
"total_pages": 600
}💡 Requires the Builder plan or higher.
Technical Analytics
Indicators are computed inside the price terminal from CLOSED bars only — the still-forming candle is never included — so values are deterministic and match your charting platform. Each request consumes API credits like any other endpoint. The example responses below were captured from the live feed.
GET /v1/indicators/maMoving Averages
Called without extra parameters this returns the full MA matrix — SMA and EMA at 10, 25, 100 and 200, each computed from closed bars — with `result.price` being the last closed close the scores were measured against. Pass method + length (+ optional price) for one custom average instead.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol* | string | — | Instrument symbol, case-insensitive (required) |
period | string | PERIOD_H1 | Candle timeframe, e.g. PERIOD_M5, PERIOD_H1, PERIOD_D1 |
method | string | optional | sma, ema or wma — switches to single-MA mode (must be sent with length) |
length | int | optional | Bar window length 2–500 for single-MA mode |
price | string | close | Applied price for single-MA mode: close, open, high, low, hl2, hlc3, ohlc4 |
Example Request
curl -G "https://connect.forexapi.co/v1/indicators/ma" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbol=EURUSD -d period=PERIOD_H1Response
{
"symbol": "EURUSD",
"period": "PERIOD_H1",
"indicator": "MA",
"params": {},
"result": {
"ema10": {
"v": 1.13644,
"s": "Neutral",
"n": "EMA 10",
"score": -1
},
"ema25": {
"v": 1.13701,
"s": "Neutral",
"n": "EMA 25",
"score": -2
},
"ema100": {
"v": 1.13923,
"s": "Neutral",
"n": "EMA 100",
"score": -6
},
"ema200": {
"v": 1.14281,
"s": "Neutral",
"n": "EMA 200",
"score": -12
},
"sma10": {
"v": 1.1366,
"s": "Neutral",
"n": "SMA 10",
"score": -2
},
"sma25": {
"v": 1.137,
"s": "Neutral",
"n": "SMA 25",
"score": -2
},
"sma100": {
"v": 1.13843,
"s": "Neutral",
"n": "SMA 100",
"score": -5
},
"sma200": {
"v": 1.1427,
"s": "Neutral",
"n": "SMA 200",
"score": -12
},
"price": 1.13569
},
"timestamp": 1790664713
}💡 Each entry carries v (value), s (signal), n (display name) and score (-100..100) measuring how far price has moved away from that average: ±100 saturates at a 5% gap; ≥50 Strong Buy, ≥15 Buy, ±15 Neutral band, ≤-50 Strong Sell. The EMA is seeded with an SMA and computed over the full warm-up window for convergence.
GET /v1/indicators/oscillatorsOscillators Summary
Seven momentum / trend / volatility readings on fixed defaults in one call — RSI(14), STOCH %K(14), MACD level(12,26,9), ATR(14), ADX(14), Ultimate Oscillator(7/14/28) and Parabolic SAR(0.02/0.2) — computed from closed bars.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol* | string | — | Instrument symbol, case-insensitive (required) |
period | string | PERIOD_H1 | Candle timeframe, e.g. PERIOD_M5, PERIOD_H1, PERIOD_D1 |
Example Request
curl -G "https://connect.forexapi.co/v1/indicators/oscillators" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbol=EURUSD -d period=PERIOD_H1Response
{
"symbol": "EURUSD",
"period": "PERIOD_H1",
"indicator": "OSCILLATORS",
"params": {},
"result": {
"rsi": {
"v": 39.9457,
"s": "Buy",
"n": "RSI",
"score": 25
},
"stoch": {
"v": 20.8191,
"s": "Strong Buy",
"n": "STOCH K%",
"score": 73
},
"macd": {
"v": -0.00049933,
"s": "Neutral",
"n": "MACD Level",
"score": -6
},
"atr": {
"v": 0.00086964,
"s": "Neutral",
"n": "ATR",
"score": 0
},
"adx": {
"v": 12.6954,
"s": "Sell",
"n": "ADX",
"score": -25
},
"uo": {
"v": 33.7262,
"s": "Buy",
"n": "Ultimate Oscillator",
"score": 33
},
"psar": {
"v": 1.13862,
"s": "Neutral",
"n": "Parabolic SAR",
"score": -4
}
},
"timestamp": 1790666620
}💡 Every entry carries v (value), s (signal), n (display name) and score (-100..100; ≥50 Strong Buy, ≥15 Buy, ±15 Neutral, ≤-50 Strong Sell). RSI, STOCH and UO are mean-reversion: oversold reads as a buy signal, overbought as a sell. MACD rates the histogram in ATR units, ADX rates the +DI/−DI direction scaled by trend strength, PSAR scores the gap to the stop like the MA matrix — ATR is directionless and stays Neutral.
GET /v1/indicators/bbandsBollinger Bands
SMA middle band with upper/lower bands at `deviation` × population standard deviation of the window.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol* | string | — | Instrument symbol (required) |
period | string | PERIOD_H1 | Candle timeframe |
length | int | 20 | Window length, 2–500 |
deviation | float | 2.0 | Band width in standard deviations, 0–10 |
Example Request
curl -G "https://connect.forexapi.co/v1/indicators/bbands" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbol=EURUSD -d period=PERIOD_H1 -d length=20 -d deviation=2Response
{
"symbol": "EURUSD",
"period": "PERIOD_H1",
"indicator": "BBANDS",
"params": {
"length": 20,
"deviation": 2
},
"result": {
"length": 20,
"deviation": 2,
"upper": 1.13778062,
"middle": 1.1369505,
"lower": 1.13612038
},
"timestamp": 1790654283
}GET /v1/indicators/stochasticStochastic Oscillator
Raw %K (position of the close inside the k_length high/low range) and its simple-average %D.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol* | string | — | Instrument symbol (required) |
period | string | PERIOD_H1 | Candle timeframe |
k_length | int | 14 | %K lookback, 2–200 |
d_length | int | 3 | %D smoothing of %K, 1–100 |
Example Request
curl -G "https://connect.forexapi.co/v1/indicators/stochastic" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbol=EURUSD -d period=PERIOD_H1Response
{
"symbol": "EURUSD",
"period": "PERIOD_H1",
"indicator": "STOCH",
"params": {
"k_length": 14,
"d_length": 3
},
"result": {
"k_length": 14,
"d_length": 3,
"k": 22.57142857,
"d": 34.66666667
},
"timestamp": 1790654283
}GET /v1/pivot-pointsPivot Points
Floor-trader pivot levels derived from the previous completed DAILY bar. Called without parameters this returns every method at once — classic, fibonacci, camarilla (adds r4/s4), woodie and demark. Pass method for a single set.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol* | string | — | Instrument symbol, case-insensitive (required) |
method | string | optional | classic, fibonacci, camarilla, woodie or demark — returns just that set |
Example Request
curl -G "https://connect.forexapi.co/v1/pivot-points" \
-H "Authorization: Bearer $FXAPI_KEY" \
-d symbol=EURUSDResponse
{
"symbol": "EURUSD",
"period": "PERIOD_D1",
"indicator": "PIVOTS",
"params": {},
"result": {
"bar_time": 1790553600,
"classic": {
"r3": 1.14289,
"r2": 1.14101,
"r1": 1.13907,
"pp": 1.13719,
"s1": 1.13525,
"s2": 1.13337,
"s3": 1.13143
},
"fibonacci": {
"r3": 1.14101,
"r2": 1.13955076,
"r1": 1.13864924,
"pp": 1.13719,
"s1": 1.13573076,
"s2": 1.13482924,
"s3": 1.13337
},
"camarilla": {
"r3": 1.1381805,
"r2": 1.13783033,
"r1": 1.13748017,
"pp": 1.13719,
"s1": 1.13677983,
"s2": 1.13642967,
"s3": 1.1360795,
"r4": 1.139231,
"s4": 1.135029
},
"woodie": {
"r3": 1.14286,
"r2": 1.140995,
"r1": 1.13904,
"pp": 1.137175,
"s1": 1.13522,
"s2": 1.133355,
"s3": 1.1314
},
"demark": {
"r1": 1.13813,
"pp": 1.13672,
"s1": 1.13431
}
},
"timestamp": 1790665730
}💡 Keys are lowercase (r3/r2/r1/pp/s1/s2/s3). Levels are always computed on the DAILY timeframe — bar_time is the UTC midnight of the reference (previous completed) daily bar. Demark defines only p/r1/s1; camarilla additionally returns r4/s4.
WebSocket API
Real-Time WebSocket API
Stream live quotes for forex, metals, indices, commodities and stocks. Push updates arrive the moment the feed ticks.
Connection URL
wss://connect.forexapi.co/wsAuthentication & plans
wss://connect.forexapi.co/ws?token=$FXAPI_KEY Pass your fxapi_live_ key (export it as FXAPI_KEY and substitute, or read it from the environment in code). The WebSocket is a Professional-plan feature — keys on other plans are closed with 4403 (the close reason names your current plan). Invalid or revoked keys are closed with 4401, and exceeding the per-user connection limit with 4429. The key may also be passed in the Authorization: Bearer header instead of the query string.
How it works
- Open the socket with your token (query string or
Authorization: Bearerheader). - Send a
subscriberequest naming the symbols you want. - Receive pushes: a rates snapshot arrives immediately, then a new message on every tick.
- Use
listto audit your subscriptions,convertfor one-shot conversions,unsubscribeto stop updates. - Closing the socket drops all of that connection's subscriptions; per-token connection limits apply (see Errors).
Request envelope
All messages are single JSON objects over the text frame. Field rules:
| field | type | description |
|---|---|---|
id | string | number | Required on every request. Echoed back on replies and errors so you can correlate them. |
method | string | One of subscribe, unsubscribe, list, convert. Case-insensitive. |
type | string | rates or cross. Required by subscribe/unsubscribe/list, ignored by convert. |
symbols | string[] | Symbols to act on. Case-insensitive and suffix-optional for rates (aapl → AAPL.US); cross pairs use BASE/QUOTE form. Invalid entries are dropped, and a request with no valid symbols answers with an error. |
from, to, amount | string, string, number | Convert only. amount must be a number or the request errors. |
Client → Server Messages
Every request must include a unique id; replies echo it back so you can correlate them.
| method | type | fields | purpose |
|---|---|---|---|
subscribe | rates | symbols: string[] | Live quotes for direct pairs like EURUSD, XAUUSD, AAPL |
unsubscribe | rates | symbols: string[] | Stop updates for the given symbols |
subscribe / unsubscribe | cross | symbols: string[] | Cross pairs computed on the fly, e.g. EUR/JPY, GBP/CHF |
list | rates | cross | — | Returns your current subscriptions for that type |
convert | — | from, to, amount | One-shot conversion using the latest rates |
Symbols are case-insensitive and suffix-optional: requesting aapl delivers the data for AAPL.US (the payload's symbol field shows the canonical name).
Server → Client Messages
{type: "rates", symbol, data} Live quote — pushed on every tick for subscribed symbols
{type: "cross", data} Cross-rate matrix, pushed ~once per second — field reference below
{id, symbols} Reply to list requests
{id, type: "convert", data} Reply to convert requests
{error, id?} Protocol error — bad method, missing fields, unknown symbols. See Errors.
Message Format Examples
Subscribe — direct rates (client → server)
{
"id": "sub-1",
"method": "subscribe",
"type": "rates",
"symbols": [
"EURUSD",
"GBPUSD",
"XAUUSD"
]
}Rate update (server → client)
One message per tick per subscribed symbol. The data object carries the full quote: bid/ask with their session high/low, the last price, change, decimal precision (digits) and the market category.
{
"type": "rates",
"symbol": "EURUSD",
"data": {
"symbol": "EURUSD",
"category": "Forex",
"price": 1.105,
"change": 0.002,
"ask": 1.1051,
"askHigh": 1.1055,
"askLow": 1.104,
"bid": 1.1049,
"bidHigh": 1.1053,
"bidLow": 1.1038,
"digits": 5
}
}Subscribe — cross pairs (client → server)
{
"id": "cross-1",
"method": "subscribe",
"type": "cross",
"symbols": [
"EUR/JPY",
"GBP/CHF"
]
}Cross-rate update (server → client)
Delivered roughly once per second as one batch. data is a matrix: row i holds the conversions from your i-th subscribed pair to every subscribed pair, in the order you subscribed. Each cell has three fields: c — the target currency/pair, v — the rate (null when no conversion path exists or the cell is the diagonal), and s — tick direction: g went up, r went down, n unchanged since the last push.
{
"type": "cross",
"data": [
[
{
"c": "JPY",
"v": 163.42,
"s": "g"
},
{
"c": "CHF",
"v": 0.9213,
"s": "n"
}
],
[
{
"c": "JPY",
"v": 177.41,
"s": "r"
},
{
"c": "CHF",
"v": null,
"s": "n"
}
]
]
}Convert request (client → server)
{
"id": "conv-1",
"method": "convert",
"from": "USD",
"to": "EUR",
"amount": 100
}Convert reply (server → client)
{
"id": "conv-1",
"type": "convert",
"data": {
"query": {
"from": "USD",
"to": "EUR",
"amount": 100
},
"info": {
"amount": 100,
"exchange_rate": 0.9051,
"timestamp": 1789890000
},
"result": 90.51
}
}List subscriptions (client → server)
{
"id": "list-1",
"method": "list",
"type": "rates"
}List reply (server → client)
{
"id": "list-1",
"symbols": [
"EURUSD",
"GBPUSD",
"XAUUSD"
]
}WebSocket Code Examples
// Real-time rates over WebSocket
// Export your key once: export FXAPI_KEY="fxapi_live_..." (Node / bundlers)
const ws = new WebSocket(`wss://connect.forexapi.co/ws?token=${process.env.FXAPI_KEY}`);
ws.onopen = () => {
// Every message needs a unique "id" so replies can be correlated
ws.send(JSON.stringify({
id: 'sub-1',
method: 'subscribe',
type: 'rates',
symbols: ['EURUSD', 'GBPUSD', 'XAUUSD']
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'rates': // live quote for one symbol
console.log(msg.symbol, msg.data.price);
break;
case 'cross': // cross-rate batch
console.log(msg.data);
break;
default: // replies carry the request "id"
if (msg.error) console.error(msg.error);
else console.log('reply to', msg.id, msg);
}
};
// Stop receiving updates for symbols you no longer need
// ws.send(JSON.stringify({ id: 'unsub-1', method: 'unsubscribe', type: 'rates', symbols: ['GBPUSD'] }));MCP Server (for AI agents)
Connect your AI to live market data
ForexAPI ships a native Model Context Protocol server over Streamable HTTP. Point Claude Code, opencode, Cursor or any MCP client at it and the AI can pull live prices, candles and indicator values as tools — the same 13 endpoints documented above, with the same parameters and your same API key.
Server URL
https://connect.forexapi.co/mcpAuth
HeaderAuthorization: Bearer $FXAPI_KEY. The /mcp endpoint itself is gated — unauthenticated clients cannot connect or list tools.Available tools (11)
get prices · list currencies · list symbols · convert · OHLC last · OHLC history · moving average · RSI · MACD · ATR · Bollinger Bands · stochastic · pivot points
Tool calls count against your plan's rate limit and monthly quota exactly like REST requests, and the same plan gates apply (crypto data needs Builder+, history depth follows your plan).
Claude Code
# Add the ForexAPI MCP server to Claude Code (one-time)
claude mcp add --transport http forexapi https://connect.forexapi.co/mcp \
--header "Authorization: Bearer $FXAPI_KEY"
# Verify from the Claude Code prompt
> /mcp # lists the forexapi server and its 11 tools
> What is the current RSI(14) of EURUSD on H1?opencode
// opencode.json (project root) — https://opencode.ai/docs/mcp-servers/
// The key lives in an env var; keep it out of dotfiles you commit.
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"forexapi": {
"type": "remote",
"url": "https://connect.forexapi.co/mcp",
"headers": {
"Authorization": "Bearer ${FXAPI_KEY}"
},
"enabled": true
}
}
}Any other MCP client
# Any MCP client speaking "Streamable HTTP" can connect:
# URL: https://connect.forexapi.co/mcp
# Headers: Authorization: Bearer $FXAPI_KEY
#
# The server speaks JSON-RPC 2.0 over HTTP POST:
# 1) Initialize the session
curl -X POST https://connect.forexapi.co/mcp \
-H "Authorization: Bearer $FXAPI_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-03-26","capabilities":{},
"clientInfo":{"name":"my-client","version":"1.0"}}}'
# 2) tools/list → 11 tools (prices, currencies, symbols, convert,
# OHLC last/history, ma, oscillators,
# bbands, stochastic, pivot-points)
# 3) tools/call — same parameters as the REST endpoint
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{
"name":"get_oscillators_v1_indicators_oscillators_get",
"arguments":{"symbol":"EURUSD","period":"PERIOD_H1"}}}Errors
Error Response Format
Errors use conventional HTTP status codes and always carry a JSON body with a single detail string (or an array for validation errors).
{
"status": 404,
"body": {
"detail": "Symbol not found: AAPLQ"
}
}{
"status": 422,
"body": {
"detail": [
{
"loc": [
"query",
"symbol"
],
"msg": "Field required",
"type": "missing"
}
]
}
}HTTP Status Codes
Every status the API returns, with the exact messages to expect and when they occur.
The request was understood but its parameters are invalid.
Too many symbols. Maximum is 100. GET /v1/pricesPERIOD_CURRENT is not a valid period POST /v1/ohlc/last, GET /v1/ohlc/historyUse either start+end or bars, not both GET /v1/ohlc/historyProvide either start+end (range mode) or bars (count mode) GET /v1/ohlc/historyRange mode requires both start and end GET /v1/ohlc/historystart must be before end GET /v1/ohlc/history{period} bars are {seconds}s wide, so the window … works out to {n} bars, but a single request returns at most 5000 bars … GET /v1/ohlc/history — the message states the exact bar count and which periods would fitNo conversion path found between {from} and {to} GET /v1/convertAnalytics error: invalid_length / invalid_method / invalid_price / invalid_params GET /v1/indicators/* — a parameter is outside the documented rangeAuthentication failed. The key was malformed, unknown, revoked or expired.
Invalid API key. Create a new key on the API Keys page of your dashboard. User not found Token is no longer active legacy JWT tokens onlyJWT token has expired legacy JWT tokens onlyInvalid JWT token legacy JWT tokens onlyAuthenticated, but this request is not allowed.
Forbidden. You are not allowed to access this resource. No credentials supplied, or the caller IP is outside the key's allowlistThis API key is not scoped for crypto market data. Add the 'crypto' scope on the API Keys page of your dashboard. GET /v1/crypto/* with a key that excludes the crypto scopeCrypto market data requires the Builder plan or higher — crypto is not included in the {plan} plan. Compare plans at https://forexapi.co/pricing. GET /v1/crypto/* on Free or StarterHistorical market data is not included in the {plan} plan. Paid plans include 1-10 years of history — compare plans at https://forexapi.co/pricing. GET /v1/ohlc/history on FreeThe {plan} plan includes {n} year(s) of historical data — the earliest available start for this plan is {date}. Narrow the range or upgrade … GET /v1/ohlc/history with a start older than the plan's horizonThe requested symbol does not exist.
Symbol not found: {symbol} POST /v1/ohlc/last, GET /v1/ohlc/history, GET /v1/indicators/*, GET /v1/pivot-points — GET /v1/prices never 404s, it returns null for unknown symbols insteadA query parameter failed schema validation (missing, wrong type, out of range).
{"detail": [{"loc": ["query", "symbol"], "msg": "Field required", "type": "missing"}]} FastAPI validation errors list every offending parameterYou hit the per-minute rate limit or exhausted your monthly quota.
Rate limit exceeded: the {plan} plan allows {n} requests per minute. Try again in {s} seconds. Need more headroom? Compare plans at https://forexapi.co/pricing. Burst limit — wait for the Retry-After secondsMonthly quota exceeded: you have used {used} of the {quota} monthly requests included in the {plan} plan. Usage resets at the start of your next billing cycle … Monthly quota is used upSomething unexpected failed on our side. Safe to retry with backoff.
The upstream price data source returned an error.
Price feed error: {reason} POST /v1/ohlc/last, GET /v1/ohlc/historyA required data set is not ready yet (usually right after a cold start).
Currency list is not available yet, the price feed may still be starting up GET /v1/currenciesThe upstream price data source did not answer in time. Worth one retry.
Price feed is not responding, try again shortly All data endpointsWebSocket Errors
Connection Close Codes
4401 The provided key was invalid — reason: Invalid token4401 The provided key was revoked or expired — reason: Token is no longer active4403 The plan does not include WebSocket access — the close reason names your current plan and links to pricing4429 Too many simultaneous connections for this token (limit: 5 per key)1000 Clean close (you or the server ended the session normally)Protocol Errors (delivered as messages)
Missing 'id' field. Every request must carry a unique id.Missing 'method' field. Add method: subscribe, unsubscribe, list or convert.Invalid method: {method} Unknown method name.Invalid type: {type} type must be rates or cross.No valid symbols provided for rates subscription. symbols was empty or none of the symbols resolved.No symbols provided for cross subscription. symbols was empty.Missing 'from' or 'to' field for conversion. convert needs both currencies.'amount' must be a number for conversion. convert amount was absent or not numeric.No conversion path found between {from} and {to} No route exists between the two currencies.Malformed requests never kill the connection — the server replies with {error, id} and keeps the socket open.
Need help?
Seeing an error that isn't listed? The detail message always explains what went wrong — include it when contacting support. Test interactively in the Playground, check live system status, or manage your keys in the dashboard.