Test network mode — top-ups use test tokens and have no real value.

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

EndpointCost
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/historyFree
GET /market-status, /account, /cost-previewFree
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.

StatusMeaning
LIVECurrent: the price is recent and still moving.
STALEThe market should be open, but no new price has arrived recently. Treat it as the last known price.
MARKET_CLOSEDOutside the instrument's trading hours; the price is the last one before the close.
UNKNOWNNot validated yet, for example right after a restart. Usually clears within seconds.
UNAVAILABLEThe 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/latestWebSocket: /stream
HowYou ask, you get the latest quoteQuotes are pushed to you the moment they change
Best forOccasional checks, scripts, spreadsheetsTrading bots, live dashboards
Billing1 credit per symbol returned1 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:

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