API quick start
Credit price: $0.001 (1000 free credits on sign-up). Base URL https://api.marketapi.app/v1. Send your key in the Authorization header. Keys in URLs are rejected.
curl -H "Authorization: Bearer $KEY" "https://api.marketapi.app/v1/prices/latest?symbols=EURUSD,XAUUSD&require_live=true"
curl -H "Authorization: Bearer $KEY" "https://api.marketapi.app/v1/bars?symbol=EURUSD&timeframe=M1&from=2026-09-01T00:00:00Z&to=2026-09-02T00:00:00Z"
curl -H "Authorization: Bearer $KEY" "https://api.marketapi.app/v1/symbols/EURUSD"
Endpoints
| Endpoint | Cost |
|---|---|
GET /prices/latest?symbols= | 1 credit per symbol returned |
GET /ticks?symbol=&from=&to= | 1 credit per started 1000 rows |
GET /bars?symbol=&timeframe=&from=&to= | 1 credit per started 1000 rows |
GET /symbols, /symbols/{symbol}, /symbols/{symbol}/properties/history | Free |
GET /market-status, /account, /cost-preview | Free |
WSS /stream (subscribe to live quotes) | 1 credit per ticker per started minute |
Price status
Every price carries a status that says only one thing: how current the price is.
| Status | Meaning |
|---|---|
LIVE | Current: the price is recent and still moving. |
STALE | The market should be open, but no new price has arrived recently. Treat it as the last known price. |
MARKET_CLOSED | Outside the instrument's trading hours; the price is the last one before the close. |
UNKNOWN | Not validated yet, for example right after a restart. Usually clears within seconds. |
UNAVAILABLE | The source has stopped quoting this instrument. |
Add require_live=true to receive only LIVE prices; excluded symbols are not billed.
Every requested ticker appears in exactly one list: data (has a price), no_data (known ticker, no price yet) or unknown (not offered).
Instrument specifications
GET /v1/symbols/{symbol} returns the instrument's complete specification as reported by the source, including currencies, contract size, tick size and volume limits, together with its trading sessions. GET /v1/symbols/{symbol}/properties/history lists every change to it. The ticker list (CSV) includes the main specification fields.
Safe retries
Send an Idempotency-Key header. Repeating a request with the same key returns the data again without charging.
Real-time prices (trading bots, dashboards)
Two ways to get the freshest price. Both deliver the same quotes; choose by how your program works.
REST: GET /prices/latest | WebSocket: /stream | |
|---|---|---|
| How | You ask, you get the latest quote | Quotes are pushed to you the moment they change |
| Best for | Occasional checks, scripts, spreadsheets | Trading bots, live dashboards |
| Billing | 1 credit per symbol returned | 1 credit per symbol per started minute |
How fresh is a price?
Symbols you are streaming, or have requested via /prices/latest in the last 5 minutes, are refreshed from the source about every second (up to such symbols across all customers at a time). All other symbols are refreshed continuously in the background, less often. So: request or subscribe to the symbols you trade, and they become fast.
Every quote carries time, the moment the source produced it (UTC), and a status. A trading program should always check both:
- Use
require_live=trueon REST calls: onlyLIVEprices are returned (others are listed separately and not billed). - Compare
timewith your clock and ignore quotes older than your strategy tolerates. Markets can be quiet: a price may legitimately not change for a while.
REST example
curl -H "Authorization: Bearer $API_KEY" \
"https://api.marketapi.app/v1/prices/latest?symbols=EURUSD,XAUUSD&require_live=true"
{"data": [{"symbol": "EURUSD", "bid": "1.12946", "ask": "1.12967", "last": null,
"time": "2026-10-02T09:15:42.381Z", "status": "LIVE"}],
"not_live": [], "unknown": [], "no_data": []}
Polling faster than once per second gains nothing: that is how often the source is read for active symbols.
WebSocket protocol
Connect to /stream with the header Authorization: Bearer <your key> (scope read:latest). Then send JSON messages:
> {"action": "subscribe", "symbols": ["EURUSD", "XAUUSD"]}
< {"type": "subscribed", "symbols": ["EURUSD", "XAUUSD"], "rejected": []}
< {"type": "quote", "symbol": "EURUSD", "bid": "1.12946", "ask": "1.12967", "last": null,
"time": "2026-10-02T09:15:42.381Z", "status": "LIVE"}
> {"action": "unsubscribe", "symbols": ["XAUUSD"]}
< {"type": "error", "code": "...", "detail": "..."}
Limits: up to 50 symbols per connection. Close codes: 4401 invalid key, 4402 balance used up (top up, then reconnect), 4429 too many open connections. The server sends pings every 20 seconds; most WebSocket libraries answer them automatically.
Python example (pip install websockets)
import asyncio, json, os, websockets
async def main():
url = "/stream"
headers = {"Authorization": "Bearer " + os.environ["API_KEY"]}
while True: # reconnect with backoff
try:
async with websockets.connect(url, additional_headers=headers) as ws:
await ws.send(json.dumps({"action": "subscribe", "symbols": ["EURUSD"]}))
async for raw in ws:
msg = json.loads(raw)
if msg["type"] == "quote" and msg["status"] == "LIVE":
print(msg["symbol"], msg["bid"], msg["ask"], msg["time"])
except websockets.ConnectionClosed as e:
if e.code in (4401, 4402): # wrong key / no balance: retrying will not help
raise
await asyncio.sleep(5)
asyncio.run(main())
Node.js example (npm install ws)
const WebSocket = require("ws");
const ws = new WebSocket("/stream", { headers: { Authorization: "Bearer " + process.env.API_KEY } });
ws.on("open", () => ws.send(JSON.stringify({ action: "subscribe", symbols: ["EURUSD"] })));
ws.on("message", (raw) => {
const m = JSON.parse(raw);
if (m.type === "quote" && m.status === "LIVE") console.log(m.symbol, m.bid, m.ask, m.time);
});
ws.on("close", (code) => console.log("closed", code)); // reconnect after a few seconds unless 4401/4402
Keep your API key on your server or in your bot. Never put it in web pages or apps that other people can open: anyone who has the key can spend your balance.
Good to know
- Prices are the source's own bid and ask;
price_basisin/symbolsstates which price the bars are built from. They are indicative market data, not an execution venue: your orders are filled at your own broker's prices. - Times are always UTC. Decimals are strings with the instrument's own precision, so no rounding is lost.
- For history (bars and ticks) see the endpoints above; real-time and history share the same symbols and keys.