API Documentation
Everything you need to integrate PriceFeed Pro into your application.
Getting Started
1. Create an account and get your API key.
2. Include your API key in every request via the X-API-Key header.
3. Base URL: https://your-domain.com
Authentication
All API requests require an API key. Pass it as a header:
GET /api/v1/market/symbols HTTP/1.1
Host: api.example.com
X-API-Key: $PRICEFEED_API_KEYKeep your API key on the server side only. Never expose it in client-side JavaScript or public repositories.
API keys are 36 characters: pfp_ followed by 32 letters and digits. Only a hash is stored, so a lost key cannot be shown again — create a new one in the portal.
REST API
/api/v1/market/symbols?search=§or=&page=1&limit=50List available symbols, optionally filtered by search term and sector. Paginated.
// Response (the object itself — there is no {success, data} wrapper)
{
"symbols": [
{ "symbol": "XAUUSD", "source_id": "ORBEX-live-1", "digits": 2, "sector": "Commodities",
"base_currency": "USD", "profit_currency": "USD", "trade_mode": 4, "...": "..." },
{ "symbol": "EURUSD", "source_id": "ORBEX-live-1", "digits": 5, "sector": "Currency",
"base_currency": "EUR", "profit_currency": "USD", "trade_mode": 4, "...": "..." }
],
"total": 150,
"page": 1,
"limit": 50
}/api/v1/market/history/{symbol}?tf=60&limit=500&from=&to=Get OHLC historical bars. Timeframes (in minutes): 1, 5, 15, 30, 60, 240, 1440, 10080, 43200. Requires Pro or Enterprise plan.
Bars are returned newest-first and capped by limit (default 500, max 5000). With only from set, the response contains the newest limit bars at or after from — page backward by setting the next to to the oldest time returned. from/to must be full RFC 3339 (e.g. 2026-02-10T00:00:00Z). There is no page= parameter on this endpoint.
// GET /api/v1/market/history/XAUUSD?tf=60&limit=1
{
"symbol": "XAUUSD",
"source_id": "ORBEX-live-1",
"timeframe": 60,
"broker_tz": "Europe/Athens",
"bars": [
{
"source_id": "ORBEX-live-1",
"symbol": "XAUUSD",
"timeframe": 60,
"time": "2026-05-04T01:00:00Z",
"broker_time": "2026-05-04T04:00:00+03:00",
"open": 2650.5, "high": 2655.0, "low": 2648.0, "close": 2652.0,
"volume": 123
}
],
"count": 1
}/api/v1/market/analysis/{symbol}?source=Deterministic multi-timeframe technical analysis derived from OHLC history + the latest tick — no AI. For one symbol it reads nine timeframes (1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w, 1mo) independently, combines their indicator votes into one weighted multi-timeframe (MTF) bias, and returns the pivot grid (floor/camarilla/woodie/fibonacci) plus nearest support/resistance. Requires Pro or Enterprise plan.
Same candles in → same analysis out. The timeframe scoring is cached for up to 90 seconds and rebuilt as soon as a new 1h or daily bar appears; the live price, levels, targets, the verdict's line and risk/reward, and the freshness fields (as_of, is_stale) update on every request. Each timeframe carries a status: ok, insufficient_history or unavailable. A timeframe that was not read still shows "direction": "NEUTRAL" and a score of 0, so check status first. completeness (full / partial / insufficient) summarises all nine. If no timeframe can be read at all, the endpoint answers 503 SERVICE_UNAVAILABLE with a Retry-After header.
mtf.agreement is the share of timeframe weight leaning one way — it is not a probability, a confidence level, or a success rate, and we have no evidence it predicts direction. Please do not surface it as one. When no timeframe has enough history, bias is an empty string and mtf.sufficient is false; render that as “no reading”, never as neutral.
Reading the analysis — full field guide: analysis-field-guide.md
direction,scoreanddetectorsare the pattern model: seven detectors that are silent most of the time.NEUTRALthere usually means no pattern fired, not a balanced market.summaryandconsensusare the indicator vote (14 oscillators + 6 moving averages).mtf.bias,agreement,alignmentand theverdictare built fromconsensus, never fromdirection. Write your rules onconsensusif they should match the headline.scoreis the pattern weight on the leading side, not net strength: opposing weight is not subtracted.MIXEDcovers conflicting, balanced and weakly leaning timeframes alike.mtf.net(−1 to +1) shows the lean;mtf.coverageshows how much of the timeframe weight was actually read.targets,levels,stopandrisk_rewardare pivot and ATR geometry, not forecasts or trade instructions.- Nothing in the payload is a probability. To test a rule before relying on it,
/api/v1/market/analysis/{symbol}/replayreturns the analysis at past instants with the forward returns that followed. No trading costs are included; test out of sample.
// GET /api/v1/market/analysis/BTCUSD (abbreviated; values illustrative)
{
"schema_version": "3.1",
"symbol": "BTCUSD",
"source_id": "ORBEX-live-1",
"live_price": 67301.08,
"timeframes": {
"1h": {
"status": "ok", "bars": 299, "bars_required": 30,
"data_through": "2026-09-24T14:00:00Z",
"score": 0, "direction": "NEUTRAL", "consensus": "BUY",
"detectors": null, "sufficient": true,
"summary": { "overall": { "buy": 11, "sell": 4, "neutral": 5, "label": "BUY", "rows": null }, ... },
"detectors_active": 0, "detectors_directional": 0, "detectors_total": 7
},
"1d": {
"status": "ok", "bars": 299, "bars_required": 30,
"data_through": "2026-09-24T00:00:00Z",
"score": 6.5, "direction": "BUY", "consensus": "BUY",
"detectors": [
{ "name": "trend.ema.stack", "direction": "BUY", "weight": 2.5, "hit": true, "group": "trend" },
{ "name": "bb.squeeze", "direction": "NEUTRAL", "weight": 0.75, "hit": true, "group": "" }
],
"sufficient": true,
"detectors_active": 2, "detectors_directional": 1, "detectors_total": 7
},
"1mo": {
"status": "insufficient_history", "bars": 45, "bars_required": 60,
"data_through": "2026-09-01T01:00:00Z",
"score": 0, "direction": "NEUTRAL", "consensus": "", "detectors": null, "sufficient": false
}
// ... 1m, 5m, 15m, 30m, 4h, 1w
},
"mtf": {
"sufficient": true, "bias": "BULLISH", "agreement": 0.86, "net": 0.86, "coverage": 0.76,
"alignment": "1m SELL, 5m NEUTRAL, 15m NEUTRAL, 30m BUY, 1h BUY, 4h BUY, 1d BUY, 1w BUY"
},
"levels": { "nearest_support": 65703.44288, "nearest_resistance": 69292.3863, "distance_pct": 2.37 },
"targets": { "upside": [69292.3863, 71371.05731], "downside": [65703.44288, 63624.77186], "pivot": 65703.44288 },
"verdict": {
"short_term": "BULLISH", "underlying": "STRONGLY BULLISH", "stance": "BULLISH ABOVE",
"level": 65703.44288, "stop": 64098.88888, "atr": 3209.108, "risk_reward": 0.62186, ...
},
"completeness": "partial",
"opinion": "Bullish bias across timeframes (1m SELL, 5m NEUTRAL, 15m NEUTRAL, 30m BUY, 1h BUY, 4h BUY, 1d BUY, 1w BUY).",
"as_of": "2026-09-24T14:37:18Z", "data_age_seconds": 2, "is_stale": false, "market_closed": false
}/api/v1/market/tick/{symbol}Get the latest tick snapshot for a single symbol. Also available: /api/v1/market/ticks?symbols=EURUSD,GBPUSD for multiple symbols.
Each response includes broker_time (RFC 3339 with the broker's offset, e.g. 2026-05-04T04:00:00+03:00) and broker_tz (IANA zone, e.g. Europe/Athens). market_closed reflects the broker's schedule; data_stale goes true when no fresh tick has arrived for ~2 minutes and is decoupled from market_closed.
// GET /api/v1/market/tick/XAUUSD
{
"symbol": "XAUUSD",
"source": "ORBEX-live-1",
"bid": "2654.21",
"ask": "2654.45",
"high": "2660.10",
"low": "2649.80",
"spread": 24,
"trade_mode": 4,
"time": "2026-05-04T01:00:00Z",
"broker_time": "2026-05-04T04:00:00+03:00",
"broker_tz": "Europe/Athens",
"market_closed": false,
"data_stale": false,
"trading_hours": "Sun:;Mon:00:02-23:59;..."
}/api/v1/account/profileGet your subscriber profile, subscription details, and usage stats. Requires JWT Bearer token (not API key).
WebSocket Streaming
Connect via native WebSocket to receive real-time price ticks and OHLC bars.
Connection URL: wss://your-host/ws/v1/stream?api_key=YOUR_API_KEY
Authentication is done via the api_key query parameter during the initial connection.
JavaScript (Node.js / Browser)
// Load API key from environment variable — never hardcode it
const ws = new WebSocket(
"wss://api.example.com/ws/v1/stream?api_key=" + process.env.PRICEFEED_API_KEY
);
ws.onopen = () => {
// Subscribe to tick data
ws.send(JSON.stringify({
action: "subscribe",
symbols: ["XAUUSD", "EURUSD"]
}));
// Subscribe to OHLC bars (optional)
ws.send(JSON.stringify({
action: "subscribe_bars",
symbols: ["XAUUSD"],
timeframes: [1, 5, 60]
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "connected":
console.log("Plan:", msg.plan, "Sources:", msg.sources);
break;
case "tick":
// msg fields: symbol, source, bid, ask, high, low, spread, trade_mode,
// time (UTC), broker_time (RFC 3339 + offset), broker_tz, market_closed,
// data_stale (true when no fresh tick in ~2 min), trading_hours.
console.log(msg.symbol, msg.bid, msg.ask, "spread:", msg.spread,
msg.data_stale ? "(stale)" : "");
break;
case "error":
console.error(msg.code + ":", msg.message);
break;
}
};
// Unsubscribe
ws.send(JSON.stringify({
action: "unsubscribe",
symbols: ["EURUSD"]
}));Python
import os
import asyncio
import websockets
import json
async def connect():
# Load API key from environment variable — never hardcode it
api_key = os.environ["PRICEFEED_API_KEY"]
uri = f"wss://api.example.com/ws/v1/stream?api_key={api_key}"
async with websockets.connect(uri) as ws:
# Subscribe to tick data
await ws.send(json.dumps({
"action": "subscribe",
"symbols": ["XAUUSD", "EURUSD"]
}))
# Listen for messages
async for message in ws:
data = json.loads(message)
if data["type"] == "connected":
print(f"Connected - Plan: {data['plan']}")
elif data["type"] == "tick":
# Each tick also carries broker_time, broker_tz, market_closed,
# data_stale (True when no fresh tick in ~2 min), trading_hours.
stale = " (stale)" if data.get("data_stale") else ""
print(f"{data['symbol']}: {data['bid']}/{data['ask']} spread={data['spread']}{stale}")
elif data["type"] == "error":
print(f"Error [{data['code']}]: {data['message']}")
asyncio.run(connect())Rate Limits
| Tier | Requests/min | WebSocket symbols | Smallest timeframe | History, analysis & replay |
|---|---|---|---|---|
| Free | 60 | 3 | 30m | — |
| Basic | 60 | 20 | 15m | — |
| Pro | 300 | 100 | 1m | Yes |
| Enterprise | 9,999 | Unlimited | 1m | Yes |
Exceeding limits returns 429 Too Many Requests (RATE_LIMITED) with a Retry-After header. Independently of your plan, each IP address is limited at the edge to 30 requests/second with bursts of up to 50 — spread watchlist fan-outs over a second or two, or fetch many symbols in one call with /api/v1/market/ticks (up to 1,000 symbols per request). The per-plan symbol limit applies to WebSocket subscriptions, not to REST.
Error Codes
| Code | Status | Description |
|---|---|---|
| MISSING_API_KEY | 401 | No API key provided |
| INVALID_API_KEY | 401 | API key is invalid or inactive |
| API_KEY_REVOKED | 401 | API key has been revoked |
| ACCOUNT_INACTIVE | 403 | Subscriber account is inactive |
| TRIAL_EXPIRED | 403 | Free trial has expired, please upgrade |
| CONNECTION_LIMIT | 429 | Maximum WebSocket connections exceeded for your plan |
| SYMBOL_LIMIT | WS | Maximum symbol subscriptions exceeded for your plan |
| RATE_LIMITED | 429 | Per-minute request limit exceeded (your plan's limit, or the per-IP burst limit). Wait for the Retry-After seconds, then retry |
| QUOTA_EXCEEDED | 429 | Daily request quota reached; resets at 00:00 UTC (Retry-After counts down to it) |
| SERVICE_UNAVAILABLE | 503 | A dependency is down or the service is restarting; always carries Retry-After. Retry with backoff |