[{"data":1,"prerenderedAt":223},["ShallowReactive",2],{"search-api":3},[4,18,31,44,57,68,80,90,99,107,127,141,153,164,175,189,199,216],{"id":5,"path":6,"dir":7,"title":8,"description":9,"keywords":10,"body":17},"content:1.getting-started:0.introduction.md","\u002Fgetting-started\u002Fintroduction","getting-started","Introduction","Overview of the Forex Market Data API — capabilities, supported data, and platform architecture",[11,12,13,14,15,16],"What is this API?","Available Data","Technical Architecture","Symbol Format","SLA and Limits","Next Steps","  Introduction  What is this API?  The Forex Market Data API is a REST + WebSocket service that provides programmatic access to live and historical forex market data. Data is collected in real-time directly from MetaTrader 5 terminals connected to live broker accounts, and delivered to you with no intermediate aggregators.  Available Data  Feeds  Data is organised into   feeds , one per broker account. Your API key is granted access to specific feeds;   GET \u002Fv1\u002Fsymbols  returns only what you are entitled to, and anything else returns   403 .     Feed  Broker  Symbols  Timestamps     WMMARKETS:ZERO  WM Markets Ltd  230  Broker time (UTC+3)    WMMARKETS:PRIME  WM Markets Ltd  230  Broker time (UTC+3)    WMMARKETS:VIP  WM Markets Ltd  230  Broker time (UTC+3)    OANDA:LIVE  OANDA Global   1,622   True UTC    The timestamp clock differs per feed  — WM bars are stamped in broker time (3 hours ahead of UTC), OANDA bars in true UTC. If you consume both, normalise before comparing or plotting. See   History  for the full table.  Instruments   WM Markets feeds — 230 symbols each     Category  Count  Examples    Forex Majors  ~28  EURUSD, GBPUSD, USDJPY   Forex Minors  ~60  AUDCAD, EURGBP, GBPJPY   Crypto  ~10  BTCUSD, ETHUSD, LTCUSD   Global Indices  ~15  US30, US500, GER40   CFD Stocks  ~100  AAPL, MSFT, TSLA   Commodities  ~15  XAUUSD, XAGUSD, USOIL   OANDA:LIVE — 1,622 symbols     Category  Count  Examples    Share CFDs  ~1,540   AAPL_CFD.US ,   ASML_CFD.NL ,   MC_CFD.FR ,   ERICb_CFD.SE   Forex  ~41  EURUSD, GBPUSD, EURNZD   Global Indices  ~17  US30, US100, US500   Crypto  ~13  BTCUSD, ETHUSD   Commodities & Metals  ~10  XAUUSD, XAGUSD, USOIL, UKOIL  OANDA's value over the WM feeds is   equities : roughly 1,540 share CFDs across 12 exchanges in the US, UK and Europe. Their symbols contain a dot and are sometimes mixed case — see   Symbol Format  before parsing them.  Monthly history reaches back to   2016  for the long-established instruments.  Supported Resolutions     API Code  Name  Bar Duration     1  M1  1 minute    5  M5  5 minutes    10  M10  10 minutes    15  M15  15 minutes    30  M30  30 minutes    60  H1  1 hour    120  H2  2 hours    240  H4  4 hours    1D  Daily  1 day    1W  Weekly  1 week    1M  Monthly  1 month  History Depth     Resolution  Depth     M1  ~3 months (broker limit)    M5 – M15  ~12 months    H1 and above  From January 2024 onwards    Daily  From January 2024 onwards  Technical Architecture   Your Application\n    │\n    ├── REST API  →  https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002F*\n    │   ├── GET \u002Fv1\u002Fconfig      ← TradingView configuration\n    │   ├── GET \u002Fv1\u002Fsymbols     ← Symbol list and search\n    │   ├── GET \u002Fv1\u002Fhistory     ← Historical OHLCV bars\n    │   ├── GET \u002Fv1\u002Fcoverage    ← Available data date ranges\n    │   ├── GET \u002Fv1\u002Fstatus      ← Live feed health\n    │   └── GET \u002Fv1\u002Fusage       ← Your API key usage stats\n    │\n    └── WebSocket  →  wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream\n        └── Live M1 bar updates as each candle closes\n  Symbol Format  Every instrument is identified by a   compound key  with three colon-separated parts:   WMMARKETS:ZERO:EURUSD\n│         │     │\n│         │     └─ Display symbol (no broker suffix)\n│         └─ Account type\n└─ Broker name\n  Use this compound key in all   symbol  parameters across the REST and WebSocket APIs.    Timestamps:  All timestamps in API responses are   Unix epoch seconds . Candle open times reflect broker time (UTC+3) stored as epoch — factor this in when converting to local time.  SLA and Limits     Metric  Value    Uptime SLA  99.5%   REST latency  \u003C 200ms (95th percentile)   WebSocket latency  \u003C 100ms   Max   countback  per request  10,000 bars   WebSocket symbols per connection  Plan-dependent  Next Steps    Make your first API call in under 5 minutes.   Learn how to authenticate every request.",{"id":19,"path":20,"dir":7,"title":21,"description":22,"keywords":23,"body":30},"content:1.getting-started:1.quick-start.md","\u002Fgetting-started\u002Fquick-start","Quick Start","Make your first API call in under 5 minutes",[24,25,26,27,28,29,16],"Prerequisites","Step 1 — First Request","Step 2 — Fetch Historical Data","Step 3 — Live Prices via WebSocket","Step 4 — Check Service Health","Endpoint Summary","  Quick Start  Prerequisites   A valid API key (starts with   fxd_ )  Any HTTP client:   curl , Python, or JavaScript    No API key yet?  Contact our team to get one. The key is shown only once at creation — save it somewhere safe immediately.   Step 1 — First Request  The simplest test: list available symbols.      curl   -H   \"X-API-Key: fxd_your_key_here\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fsymbols?limit=5\"\n     import requests\n   \n   API_KEY = \"fxd_your_key_here\"\n   BASE_URL = \"https:\u002F\u002Fapi.pipdata.net\"\n   \n   headers = {\"X-API-Key\": API_KEY}\n   resp = requests.get(f\"{BASE_URL}\u002Fv1\u002Fsymbols\", headers=headers, params={\"limit\": 5})\n   print(resp.json())\n     const   API_KEY   =   \"fxd_your_key_here\"  ;\n   const   BASE_URL   =   \"https:\u002F\u002Fapi.pipdata.net\"  ;\n   \n   const   resp   =   await   fetch  (  `${  BASE_URL  }\u002Fv1\u002Fsymbols?limit=5`  , {\n     headers: {   \"X-API-Key\"  :   API_KEY   }\n   });\n   const   data   =   await   resp.  json  ();\n   console.  log  (data);\n   Sample response:     {\n     \"symbols\"  : [\n       {\n         \"symbol\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n         \"display_symbol\"  :   \"EUR\u002FUSD\"  ,\n         \"description\"  :   \"EUR\u002FUSD — WM Markets Ltd\"  ,\n         \"type\"  :   \"forex\"  ,\n         \"digits\"  :   5\n       }\n     ],\n     \"total\"  :   230  ,\n     \"limit\"  :   5  ,\n     \"offset\"  :   0\n   }\n   Step 2 — Fetch Historical Data  Get OHLCV bars for EUR\u002FUSD at H1 resolution (last 500 candles):      curl   -H   \"X-API-Key: fxd_your_key_here\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:EURUSD&resolution=60&countback=500\"\n     params = {\n       \"symbol\": \"WMMARKETS:ZERO:EURUSD\",\n       \"resolution\": \"60\",   # H1\n       \"countback\": 500\n   }\n   resp = requests.get(f\"{BASE_URL}\u002Fv1\u002Fhistory\", headers=headers, params=params)\n   data = resp.json()\n   \n   if data[\"s\"] == \"ok\":\n       bars = list(zip(data[\"t\"], data[\"o\"], data[\"h\"], data[\"l\"], data[\"c\"]))\n       print(f\"{len(bars)} bars received\")\n       print(f\"Latest bar: open={bars[-1][1]:.5f}, close={bars[-1][4]:.5f}\")\n     const   params   =   new   URLSearchParams  ({\n     symbol:   \"WMMARKETS:ZERO:EURUSD\"  ,\n     resolution:   \"60\"  ,\n     countback:   \"500\"\n   });\n   const   resp   =   await   fetch  (  `${  BASE_URL  }\u002Fv1\u002Fhistory?${  params  }`  , {\n     headers: {   \"X-API-Key\"  :   API_KEY   }\n   });\n   const   data   =   await   resp.  json  ();\n   \n   if   (data.s   ===   \"ok\"  ) {\n     console.  log  (  `${  data  .  t  .  length  } bars received`  );\n     console.  log  (  `Latest close: ${  data  .  c  [  data  .  c  .  length   -   1  ]  }`  );\n   }\n   Sample response:     {\n     \"s\"  :   \"ok\"  ,\n     \"t\"  : [  1749556800  ,   1749560400  ,   1749564000  ],\n     \"o\"  : [  1.08234  ,   1.08198  ,   1.08215  ],\n     \"h\"  : [  1.08267  ,   1.08241  ,   1.08259  ],\n     \"l\"  : [  1.08190  ,   1.08182  ,   1.08198  ],\n     \"c\"  : [  1.08198  ,   1.08215  ,   1.08241  ],\n     \"v\"  : [  1250  ,   980  ,   1100  ],\n     \"meta\"  : {\n       \"symbol\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n       \"resolution\"  :   \"60\"  ,\n       \"bar_count\"  :   3\n     }\n   }\n   Arrays   t ,   o ,   h ,   l ,   c ,   v  are aligned by index and ordered oldest → newest.   t  values are Unix epoch seconds.   Step 3 — Live Prices via WebSocket  Subscribe to real-time M1 bar updates as each candle closes:     const   ws   =   new   WebSocket  (\n     \"wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key=fxd_your_key_here\"\n   );\n   \n   ws.  onopen   =   ()   =>   {\n     ws.  send  (  JSON  .  stringify  ({\n       action:   \"subscribe\"  ,\n       symbols: [  \"WMMARKETS:ZERO:EURUSD\"  ,   \"WMMARKETS:ZERO:XAUUSD\"  ],\n       type:   \"bar\"\n     }));\n   };\n   \n   ws.  onmessage   =   (  event  )   =>   {\n     const   msg   =   JSON  .  parse  (event.data);\n   \n     if   (msg.type   ===   \"connected\"  ) {\n       console.  log  (  \"Connected — session:\"  , msg.session_id);\n     }\n   \n     if   (msg.type   ===   \"bar\"  ) {\n       console.  log  (  `${  msg  .  symbol  }: close = ${  msg  .  bar  .  close  }`  );\n     }\n   };\n   Step 4 — Check Service Health     curl   -H   \"X-API-Key: fxd_your_key_here\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstatus\"\n     {\n     \"status\"  :   \"ok\"  ,\n     \"server_time\"  :   1749600000  ,\n     \"feeds\"  : {\n       \"WMMARKETS:ZERO\"  : {\n         \"status\"  :   \"live\"  ,\n         \"last_candle_age_seconds\"  :   45  ,\n         \"symbol_count\"  :   230\n       }\n     }\n   }\n   Endpoint Summary     Endpoint  Description     GET \u002Fv1\u002Fconfig  TradingView configuration object    GET \u002Fv1\u002Fsymbols  Symbol list and search    GET \u002Fv1\u002Fhistory  Historical OHLCV bars    GET \u002Fv1\u002Fcoverage  Available data date ranges per symbol    GET \u002Fv1\u002Fstatus  Live feed health status    GET \u002Fv1\u002Fusage  Your API key usage statistics    WS \u002Fv1\u002Fstream  Real-time M1 bar WebSocket stream  Next Steps  Read the   Authentication  guide to understand all auth methods, rate-limit headers, and error handling.  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":32,"path":33,"dir":7,"title":34,"description":35,"keywords":36,"body":43},"content:1.getting-started:2.authentication.md","\u002Fgetting-started\u002Fauthentication","Authentication","How to authenticate API requests — headers, query params, rate-limit headers, and error codes",[37,38,39,40,41,42],"API Key Format","Authentication Methods","Per-Key Access Controls","Rate-Limit Response Headers","Authentication Errors","Best Practices","  Authentication  All endpoints (except   GET \u002Fv1\u002Fconfig ) require authentication via an API key.  API Key Format   fxd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n    Prefix:  always   fxd_   Suffix:  32 random hex characters   Total length:  36 characters   Example:    fxd_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4    Security:  Your key is displayed   once  at creation time. Store it in a secrets manager or environment variable immediately. If lost, request a replacement key — old keys cannot be recovered.   Authentication Methods  Method 1 — HTTP Header (Recommended)  Pass the key in the   X-API-Key  header. This is the preferred method for all server-to-server calls.     X-API-Key: fxd_your_key_here\n     curl   -H   \"X-API-Key: fxd_your_key_here\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fsymbols\"\n  Method 2 — Authorization Bearer  Standard Bearer token format, compatible with OAuth tooling.     Authorization: Bearer fxd_your_key_here\n     curl   -H   \"Authorization: Bearer fxd_your_key_here\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fsymbols\"\n  Method 3 — Query Parameter  Pass the key as a URL query parameter. Convenient for browser environments or WebSocket connections where custom headers cannot be set.   GET \u002Fv1\u002Fsymbols?api_key=fxd_your_key_here\n   Use the query parameter for WebSocket connections from a browser — standard   WebSocket()  does not support custom headers. In all other environments, prefer the   X-API-Key  header to avoid the key appearing in server access logs.  WebSocket Authentication     const   ws   =   new   WebSocket  (\n     \"wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key=fxd_your_key_here\"\n   );\n   Per-Key Access Controls  Each key can be configured with the following restrictions:     Restriction  Description     Feed permissions  Key only accesses specific feeds (e.g.,   WMMARKETS:ZERO  only)    RPM  Maximum requests per minute    RPD  Maximum requests per day    IP allowlist  If set, only listed IP addresses are accepted    Expiry date  Key is automatically deactivated after this date    WS connections  Maximum simultaneous WebSocket connections    WS symbols  Maximum symbols per WebSocket connection   Rate-Limit Response Headers  Every API response includes these headers so you can track consumption in real time:     X-RateLimit-Limit-Minute: 120\n   X-RateLimit-Remaining-Minute: 115\n   X-RateLimit-Reset-Minute: 1749600060\n   X-RateLimit-Limit-Day: 50000\n   X-RateLimit-Remaining-Day: 49885\n     Header  Meaning     X-RateLimit-Limit-Minute  Per-minute request cap    X-RateLimit-Remaining-Minute  Requests remaining this minute    X-RateLimit-Reset-Minute  Unix timestamp when the minute window resets    X-RateLimit-Limit-Day  Daily request cap    X-RateLimit-Remaining-Day  Requests remaining today   Authentication Errors  401 — Missing or invalid key     {\n     \"error\"  : {\n       \"code\"  :   \"UNAUTHORIZED\"  ,\n       \"message\"  :   \"API key is required.\"\n     }\n   }\n     Error Code  Cause     UNAUTHORIZED  No key provided    KEY_INVALID  Key not found in the system    KEY_INACTIVE  Key has been revoked    KEY_EXPIRED  Key expiry date has passed    IP_NOT_ALLOWED  Your IP is not on the allowlist  403 — Feed access denied     {\n     \"error\"  : {\n       \"code\"  :   \"SYMBOL_NOT_PERMITTED\"  ,\n       \"message\"  :   \"Your key does not have access to feed WMMARKETS:PRIME.\"\n     }\n   }\n  429 — Rate limit exceeded     {\n     \"error\"  : {\n       \"code\"  :   \"RATE_LIMIT_EXCEEDED\"  ,\n       \"message\"  :   \"Rate limit exceeded (minute). Retry after 45 seconds.\"\n     }\n   }\n  The   Retry-After  header tells you exactly how long to wait:     Retry-After: 45\n   X-RateLimit-Exceeded: minute\n   Best Practices  Store keys in environment variables     import os\n   API_KEY = os.environ[\"FOREX_API_KEY\"]\n     const   API_KEY   =   process.env.  FOREX_API_KEY  ;\n  Never commit keys to git     # .gitignore\n   .env\n   *  .env\n   config.local.js\n   secrets.json\n  Handle 429 with exponential backoff     import time\n   import requests\n   \n   def request_with_retry(url, headers, max_retries=3):\n       for attempt in range(max_retries):\n           resp = requests.get(url, headers=headers)\n           if resp.status_code == 429:\n               retry_after = int(resp.headers.get(\"Retry-After\", 60))\n               wait = retry_after + (2 ** attempt)  # add jitter\n               print(f\"Rate limited — waiting {wait}s (attempt {attempt + 1})\")\n               time.sleep(wait)\n               continue\n           return resp\n       raise Exception(\"Max retries exceeded\")\n  Monitor remaining quota proactively     resp = requests.get(url, headers=headers)\n   remaining = int(resp.headers.get(\"X-RateLimit-Remaining-Minute\", 999))\n   if remaining \u003C 10:\n       print(f\"Warning: only {remaining} requests left this minute\")\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":45,"path":46,"dir":47,"title":48,"description":49,"keywords":50,"body":56},"content:2.api-reference:0.config.md","\u002Fapi-reference\u002Fconfig","api-reference","Config — GET \u002Fv1\u002Fconfig","Retrieve platform configuration — supported resolutions, exchanges, and symbol types",[51,52,53,54,55],"Request","Response","Response Fields","Examples","TradingView Integration","  GET \u002Fv1\u002Fconfig  Returns the platform configuration object used to initialise the TradingView Charting Library DataFeed.   This is the   only endpoint that does not require authentication . It is safe to call without an API key.   Request   GET \u002Fv1\u002Fconfig\n   Parameters:  None   Auth:  Not required   Response     {\n     \"supported_resolutions\"  : [\n       \"1\"  ,   \"5\"  ,   \"10\"  ,   \"15\"  ,   \"30\"  ,\n       \"60\"  ,   \"120\"  ,   \"240\"  ,\n       \"1D\"  ,   \"1W\"  ,   \"1M\"\n     ],\n     \"supports_group_request\"  :   false  ,\n     \"supports_marks\"  :   false  ,\n     \"supports_search\"  :   true  ,\n     \"supports_timescale_marks\"  :   false  ,\n     \"exchanges\"  : [\n       {\n         \"value\"  :   \"WMMARKETS\"  ,\n         \"name\"  :   \"WM Markets Ltd\"  ,\n         \"desc\"  :   \"WM Markets Forex Broker\"\n       }\n     ],\n     \"symbols_types\"  : [\n       {   \"name\"  :   \"Forex\"  ,        \"value\"  :   \"forex\"    },\n       {   \"name\"  :   \"Stock\"  ,        \"value\"  :   \"stock\"    },\n       {   \"name\"  :   \"Crypto\"  ,       \"value\"  :   \"crypto\"   },\n       {   \"name\"  :   \"Index\"  ,        \"value\"  :   \"index\"    },\n       {   \"name\"  :   \"CFD Stock\"  ,    \"value\"  :   \"cfd\"      }\n     ]\n   }\n   Response Fields     Field  Type  Description     supported_resolutions  string   All valid values for the   resolution  parameter    supports_search  boolean  Symbol search is available via   \u002Fv1\u002Fsymbols    exchanges  object   Broker list;   value  is the key used in symbol compound keys    symbols_types  object   Instrument type filter options   Examples     # No API key needed\n   curl   \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fconfig\"\n   TradingView Integration  Pass this response directly to TradingView's   onReady  callback:     const   datafeed   =   {\n     onReady  (  callback  ) {\n       fetch  (  \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fconfig\"  , {\n         headers: {   \"X-API-Key\"  :   API_KEY   }\n       })\n         .  then  (  r   =>   r.  json  ())\n         .  then  (  config   =>   setTimeout  (()   =>   callback  (config),   0  ));\n     }\n   };\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":58,"path":59,"dir":47,"title":60,"description":61,"keywords":62,"body":67},"content:2.api-reference:1.symbols.md","\u002Fapi-reference\u002Fsymbols","Symbols — GET \u002Fv1\u002Fsymbols","List and search available market symbols with full metadata",[51,63,64,65,54,66],"Response — Single Symbol (?symbol=...)","Response — Symbol List (no symbol param)","Symbol Fields","Error Codes","  GET \u002Fv1\u002Fsymbols  Returns the market symbols accessible to your API key. Use the   symbol  parameter to fetch a single symbol's full metadata, or omit it to list and search across all accessible symbols.   Request   GET \u002Fv1\u002Fsymbols\n   Auth:  Required  Query Parameters     Parameter  Type  Default  Description     symbol  string  —  Compound key (e.g.   WMMARKETS:ZERO:EURUSD ). If provided, returns full metadata for that single symbol.    search  string  —  Filter by display name (e.g.   EUR  returns all symbols containing \"EUR\")    limit  integer  100  Number of results to return (max: 1000)    offset  integer  0  Pagination offset   Response — Single Symbol (  ?symbol=... )     {\n     \"symbol\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n     \"full_name\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n     \"display_symbol\"  :   \"EUR\u002FUSD\"  ,\n     \"description\"  :   \"EUR\u002FUSD — WM Markets Ltd\"  ,\n     \"exchange\"  :   \"WMMARKETS\"  ,\n     \"type\"  :   \"forex\"  ,\n     \"digits\"  :   5  ,\n     \"price_scale\"  :   100000  ,\n     \"feed\"  :   \"WMMARKETS:ZERO\"  ,\n     \"supported_resolutions\"  : [  \"1\"  ,  \"5\"  ,  \"10\"  ,  \"15\"  ,  \"30\"  ,  \"60\"  ,  \"120\"  ,  \"240\"  ,  \"1D\"  ,  \"1W\"  ,  \"1M\"  ],\n     \"has_intraday\"  :   true  ,\n     \"has_daily\"  :   true  ,\n     \"has_weekly_and_monthly\"  :   true  ,\n     \"session\"  :   \"24x7\"  ,\n     \"timezone\"  :   \"UTC\"  ,\n     \"data_status\"  :   \"streaming\"  ,\n     \"delay_seconds\"  :   0\n   }\n  Response — Symbol List (no   symbol  param)     {\n     \"symbols\"  : [\n       {\n         \"symbol\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n         \"display_symbol\"  :   \"EUR\u002FUSD\"  ,\n         \"description\"  :   \"EUR\u002FUSD — WM Markets Ltd\"  ,\n         \"exchange\"  :   \"WMMARKETS\"  ,\n         \"type\"  :   \"forex\"  ,\n         \"digits\"  :   5  ,\n         \"price_scale\"  :   100000  ,\n         \"feed\"  :   \"WMMARKETS:ZERO\"\n       }\n     ],\n     \"total\"  :   230  ,\n     \"limit\"  :   100  ,\n     \"offset\"  :   0\n   }\n   Symbol Fields     Field  Type  Description     symbol  string  Compound key — use this in   \u002Fv1\u002Fhistory  and WebSocket subscriptions    display_symbol  string  Human-readable name (e.g.   EUR\u002FUSD )    description  string  Full description including broker    exchange  string  Broker identifier    type  string   forex ,   crypto ,   index , or   cfd    digits  integer  Decimal places in the price    price_scale  integer  Price scale factor (  10^digits )    feed  string  Feed key:   BROKER:ACCOUNT    session  string  Trading session —   24x7  (always open) or   24x5  (weekdays only)    data_status  string   streaming  (live) or   endofday    delay_seconds  integer  How far the broker's own quotes for this symbol lag real time.   0  = real time. A few brokers quote exchange-listed shares from a   delayed exchange feed  — e.g. Alpari's single-stock CFDs (  ALPARI:LIVE:APPLE_CFD  …) are   900  (15 minutes). For such a symbol the newest candle is always about this far behind the clock, and tick timestamps (  tick.t ) carry the time the price is from, not the time it was delivered. Use this field to tell \"delayed by design\" from \"stalled\".   Examples  Search for symbols     # Find all symbols containing \"USD\"\n   curl   -H   \"X-API-Key: fxd_your_key\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fsymbols?search=USD&limit=20\"\n  Paginate through all symbols     import requests\n   \n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   BASE = \"https:\u002F\u002Fapi.pipdata.net\"\n   \n   all_symbols = []\n   offset = 0\n   limit = 100\n   \n   while True:\n       r = requests.get(\n           f\"{BASE}\u002Fv1\u002Fsymbols\",\n           headers=headers,\n           params={\"limit\": limit, \"offset\": offset}\n       )\n       data = r.json()\n       all_symbols.extend(data[\"symbols\"])\n   \n       if offset + limit >= data[\"total\"]:\n           break\n       offset += limit\n   \n   print(f\"Total symbols fetched: {len(all_symbols)}\")\n  Fetch a specific symbol     curl   -H   \"X-API-Key: fxd_your_key\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fsymbols?symbol=WMMARKETS:ZERO:XAUUSD\"\n   Error Codes     HTTP Status  Code  Description    401   UNAUTHORIZED  API key missing or invalid   403   SYMBOL_NOT_PERMITTED  Your key does not have access to this feed   404   SYMBOL_NOT_FOUND  Symbol does not exist   429   RATE_LIMIT_EXCEEDED  Too many requests   500   SERVER_ERROR  Internal error  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":69,"path":70,"dir":47,"title":71,"description":72,"keywords":73,"body":79},"content:2.api-reference:2.history.md","\u002Fapi-reference\u002Fhistory","History — GET \u002Fv1\u002Fhistory","Fetch historical OHLCV candles for any symbol and resolution",[51,74,75,76,77,53,54,78,66],"Which clock are from and to on?","Response — Data Available","Response — No Data","Response — Error","Important Notes","  GET \u002Fv1\u002Fhistory  Returns historical OHLCV bars for a given symbol and resolution. The response format is fully compatible with the   TradingView UDF protocol .   Request   GET \u002Fv1\u002Fhistory\n   Auth:  Required  Query Parameters     Parameter  Type  Required  Description     symbol  string  ✅  Compound key (e.g.   WMMARKETS:ZERO:EURUSD )    resolution  string  ✅  Bar interval — see table below    from  integer  —  Range start, Unix epoch seconds   on the feed's broker clock  (see below)    to  integer  —  Range end, Unix epoch seconds   on the feed's broker clock  (see below)    countback  integer  —  Number of bars to return (max: 10,000)   You must provide either    from  +   to  (time range) or    countback  (latest N bars). For TradingView scroll-back pagination, send    countback  +   to  — this avoids TradingView stopping at weekend boundaries because the backend uses a   DESC LIMIT  query instead of a time range.  Resolution Values     Value  Timeframe  Bar Duration     1  M1  1 minute    5  M5  5 minutes    10  M10  10 minutes    15  M15  15 minutes    30  M30  30 minutes    60  H1  1 hour    120  H2  2 hours    240  H4  4 hours    1D  Daily  1 day    1W  Weekly  1 week    1M  Monthly  1 month   Which clock are   from  and   to  on?   from  and   to  are interpreted on the   same clock the response uses  — the feed's\nbroker clock, not real UTC. This matters enormously on feeds whose broker clock is far\nfrom UTC.    A real-UTC window against an ISE symbol returns an empty series, not an error.  ISE runs on an exchange terminal whose clock is roughly 4018 days behind UTC. Asking for\n\"the last 30 days\" in real UTC therefore asks for a window about eleven years after any\nbar we hold, and you get:     {   \"s\"  :   \"no_data\"   }\n  No error, no warning — just nothing. Measured: the same request with the window shifted\nonto the broker clock returned 22 bars.  Two safe approaches:   1. Use   countback  and omit   from \u002F  to .  The window is then resolved server-side\nagainst the feed's own clock, so it is correct for every feed with no arithmetic on your\nside. This is the recommended default.   GET \u002Fv1\u002Fhistory?symbol=ISE:TADIL:FMLY&resolution=1D&countback=30\n   2. If you need an explicit window, shift it  by that feed's   clock_offset_seconds \nfrom    \u002Fv1\u002Fstatus :   feed_from = real_utc_from - clock_offset_seconds\nfeed_to   = real_utc_to   - clock_offset_seconds\n  which is the inverse of the rule for reading timestamps back\n(  real_utc = feed_timestamp + clock_offset_seconds ).   This applies to every feed, not only ISE — WM feeds run 3 hours ahead, so an unshifted\nreal-UTC window silently clips 3 hours from each edge. On a 30-day request that is easy\nto miss; on a 1-hour request it is most of the data.   Response — Data Available     {\n     \"s\"  :   \"ok\"  ,\n     \"t\"  : [  1749556800  ,   1749560400  ,   1749564000  ],\n     \"o\"  : [  1.08234  ,   1.08198  ,   1.08215  ],\n     \"h\"  : [  1.08267  ,   1.08241  ,   1.08259  ],\n     \"l\"  : [  1.08190  ,   1.08182  ,   1.08198  ],\n     \"c\"  : [  1.08198  ,   1.08215  ,   1.08241  ],\n     \"v\"  : [  1250  ,   980  ,   1100  ],\n     \"meta\"  : {\n       \"symbol\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n       \"resolution\"  :   \"60\"  ,\n       \"bar_count\"  :   3\n     }\n   }\n  Response — No Data     {\n     \"s\"  :   \"no_data\"\n   }\n  Response — Error     {\n     \"s\"  :   \"error\"  ,\n     \"errmsg\"  :   \"symbol not found\"\n   }\n   Response Fields     Field  Type  Description     s  string  Status:   \"ok\" ,   \"no_data\" , or   \"error\"    t  integer   Bar open times as Unix epoch seconds (oldest → newest)    o  float   Open prices    h  float   High prices    l  float   Low prices    c  float   Close prices    v  integer   Tick volume    meta.bar_count  integer  Number of bars returned  All arrays are aligned by index —   t[0] ,   o[0] ,   h[0] ,   l[0] ,   c[0] ,   v[0]  all describe the same bar.   Examples  Last 1000 H1 bars     curl   -H   \"X-API-Key: fxd_your_key\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:EURUSD&resolution=60&countback=1000\"\n  Specific date range     import requests\n   from datetime import datetime, timezone\n   \n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   \n   # January 2025\n   from_ts = int(datetime(2025, 1, 1, tzinfo=timezone.utc).timestamp())\n   to_ts   = int(datetime(2025, 1, 31, 23, 59, 59, tzinfo=timezone.utc).timestamp())\n   \n   resp = requests.get(\n       \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fhistory\",\n       headers=headers,\n       params={\n           \"symbol\": \"WMMARKETS:ZERO:EURUSD\",\n           \"resolution\": \"60\",\n           \"from\": from_ts,\n           \"to\": to_ts\n       }\n   )\n   data = resp.json()\n   if data[\"s\"] == \"ok\":\n       first = datetime.fromtimestamp(data[\"t\"][0], tz=timezone.utc)\n       last  = datetime.fromtimestamp(data[\"t\"][-1], tz=timezone.utc)\n       print(f\"{len(data['t'])} bars from {first.date()} to {last.date()}\")\n  Convert to pandas DataFrame     import pandas as pd\n   \n   if data[\"s\"] == \"ok\":\n       df = pd.DataFrame({\n           \"time\":   pd.to_datetime(data[\"t\"], unit=\"s\", utc=True),\n           \"open\":   data[\"o\"],\n           \"high\":   data[\"h\"],\n           \"low\":    data[\"l\"],\n           \"close\":  data[\"c\"],\n           \"volume\": data[\"v\"]\n       })\n       df.set_index(\"time\", inplace=True)\n       print(df.tail())\n  Daily bars — last 365 days     curl   -H   \"X-API-Key: fxd_your_key\"   \\\n     \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:GBPUSD&resolution=1D&countback=365\"\n   Important Notes    The timestamp convention depends on the feed. Check this table before doing any date maths.  All   t  values are Unix epoch seconds representing the candle open time, but the clock they are expressed in is a property of the broker:     Feed   t  is expressed in  Offset vs real UTC     WMMARKETS:ZERO ,   WMMARKETS:PRIME ,   WMMARKETS:VIP  Broker time (EET, UTC+3)   +3 hours    OANDA:LIVE   True UTC   none    ALPARI:LIVE   True UTC   none    LITEFINANCE:LIVE   True UTC   none    AMARKETS:LIVE   True UTC   none    ISE:TADIL ,   ISE:NOTADIL  Exchange session clock (see the ISE notes)  n\u002Fa  Every broker onboarded from August 2026 stores   true UTC ; only WM Markets is grandfathered on broker time. So a WM bar and an OANDA, Alpari, LiteFinance or AMarkets bar describing the   same instant  carry timestamps three hours apart. If you plot both on one chart, normalise first — subtract 10800 from WM timestamps to get true UTC, or add 10800 to OANDA's to put both on the broker clock. Do not assume one global offset.   Display:  for OANDA, Alpari, LiteFinance and AMarkets,   new Date(t * 1000)  and   pd.to_datetime(t, unit='s', utc=True)  are already correct. For WM, the same call shows broker-local time, which is 3 hours ahead of UTC.   TradingView   getServerTime  — critical, and it is per-feed.  Your DataFeed's   getServerTime  must return a clock consistent with the bars you are drawing. If it returns real UTC while drawing WM bars, TradingView treats every bar timestamped after real-now as future data and hides them — the chart appears frozen 3 hours before the present. If it returns broker time while drawing OANDA bars, you get the mirror image: a 3-hour dead zone at the right edge.     \u002F\u002F The offset is a property of the SYMBOL being drawn, not a global constant.\n   const   STORE_OFFSET   =   { WMMARKETS:   10800  , OANDA:   0  , ALPARI:   0  , LITEFINANCE:   0  , AMARKETS:   0  , ISE:   0   };\n   \n   getServerTime  (callback) {\n     const   broker   =   this  ._currentSymbol.  split  (  ':'  )[  0  ];\n     callback  (Math.  floor  (Date.  now  ()   \u002F   1000  )   +   (  STORE_OFFSET  [broker]   ||   0  ));\n   }\n    M1 history depth:  M1 data is limited to approximately 3 months (broker constraint). For longer backtests, use M5 (  resolution=5 ) or higher, which has up to 12 months of depth.   Error Codes     HTTP Status  Code  Description    401   UNAUTHORIZED  API key missing or invalid   403   SYMBOL_NOT_PERMITTED  Feed access not allowed for this key   422   INVALID_PARAMS  Missing or invalid parameters   500   SERVER_ERROR  Internal error  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":81,"path":82,"dir":47,"title":83,"description":84,"keywords":85,"body":89},"content:2.api-reference:3.coverage.md","\u002Fapi-reference\u002Fcoverage","Coverage — GET \u002Fv1\u002Fcoverage","Check available data date ranges for each symbol before requesting history",[51,86,87,53,88,66],"Response — Single Symbol","Response — All Symbols","Example — Check Before Fetching History","  GET \u002Fv1\u002Fcoverage  Returns the available data date ranges stored in the database. Use this endpoint to check what history exists before making a   \u002Fv1\u002Fhistory  request, especially for less common instruments.   Request   GET \u002Fv1\u002Fcoverage\n   Auth:  Required  Query Parameters     Parameter  Type  Required  Description     symbol  string  ❌  Compound key. If omitted, returns coverage for all symbols accessible to your key.   Response — Single Symbol     {\n     \"symbol\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n     \"coverage\"  : [\n       {\n         \"from\"  :   1740960000  ,\n         \"to\"  :     1749600000\n       }\n     ]\n   }\n  Response — All Symbols     {\n     \"coverage\"  : {\n       \"WMMARKETS:ZERO:EURUSD\"  : [\n         {   \"from\"  :   1740960000  ,   \"to\"  :   1749600000   }\n       ],\n       \"WMMARKETS:ZERO:GBPUSD\"  : [\n         {   \"from\"  :   1740960000  ,   \"to\"  :   1749600000   }\n       ],\n       \"WMMARKETS:ZERO:XAUUSD\"  : [\n         {   \"from\"  :   1740960000  ,   \"to\"  :   1749600000   }\n       ]\n     }\n   }\n  Each entry in the   coverage  array is a contiguous data range. Multiple entries indicate gaps in the data (e.g. after a server outage period).   Response Fields     Field  Type  Description     coverage[].from  integer  Start of data range (Unix epoch seconds)    coverage[].to  integer  End of data range (Unix epoch seconds)   Example — Check Before Fetching History     import requests\n   from datetime import datetime, timezone\n   \n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   BASE = \"https:\u002F\u002Fapi.pipdata.net\"\n   \n   # Check coverage first\n   cov = requests.get(\n       f\"{BASE}\u002Fv1\u002Fcoverage\",\n       headers=headers,\n       params={\"symbol\": \"WMMARKETS:ZERO:EURUSD\"}\n   ).json()\n   \n   if cov[\"coverage\"]:\n       start = datetime.fromtimestamp(cov[\"coverage\"][0][\"from\"], tz=timezone.utc)\n       end   = datetime.fromtimestamp(cov[\"coverage\"][0][\"to\"],   tz=timezone.utc)\n       print(f\"Data available from {start.date()} to {end.date()}\")\n   \n       # Now safely fetch within that range\n       resp = requests.get(\n           f\"{BASE}\u002Fv1\u002Fhistory\",\n           headers=headers,\n           params={\n               \"symbol\": \"WMMARKETS:ZERO:EURUSD\",\n               \"resolution\": \"60\",\n               \"from\": cov[\"coverage\"][0][\"from\"],\n               \"to\":   cov[\"coverage\"][0][\"to\"]\n           }\n       )\n       data = resp.json()\n       print(f\"Fetched {data.get('meta', {}).get('bar_count', 0)} bars\")\n   else:\n       print(\"No data available for this symbol\")\n   Error Codes     HTTP Status  Code  Description    401   UNAUTHORIZED  API key missing or invalid   403   SYMBOL_NOT_PERMITTED  Feed access not allowed   404   SYMBOL_NOT_FOUND  Symbol does not exist   500   SERVER_ERROR  Internal error  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":91,"path":92,"dir":47,"title":93,"description":94,"keywords":95,"body":98},"content:2.api-reference:4.status.md","\u002Fapi-reference\u002Fstatus","Status — GET \u002Fv1\u002Fstatus","Check live feed health and data freshness for all accessible feeds",[51,52,53,96,97],"Feed Status Meaning","Example — Health Monitor","  GET \u002Fv1\u002Fstatus  Returns the real-time health status of all data feeds accessible to your API key. Use this to monitor data freshness and detect feed outages before they affect your application.   Request   GET \u002Fv1\u002Fstatus\n   Auth:  Required   Parameters:  None   Response     {\n     \"status\"  :   \"ok\"  ,\n     \"server_time\"  :   1749600000  ,\n     \"feeds\"  : {\n       \"WMMARKETS:ZERO\"  : {\n         \"status\"  :   \"live\"  ,\n         \"last_candle_time\"  :   1749599940  ,\n         \"last_candle_age_seconds\"  :   60  ,\n         \"last_heartbeat_age_seconds\"  :   1  ,\n         \"liveness_source\"  :   \"heartbeat\"  ,\n         \"clock_offset_seconds\"  :   -10800  ,\n         \"symbol_count\"  :   230\n       },\n       \"ISE:TADIL\"  : {\n         \"status\"  :   \"live\"  ,\n         \"last_candle_time\"  :   0  ,\n         \"last_candle_age_seconds\"  :   0  ,\n         \"last_heartbeat_age_seconds\"  :   1  ,\n         \"liveness_source\"  :   \"heartbeat\"  ,\n         \"clock_offset_seconds\"  :   347142601  ,\n         \"symbol_count\"  :   972  ,\n         \"clock_note\"  :   \"timestamps use the broker clock (4018 days behind UTC); use last_heartbeat_age_seconds for liveness\"\n       }\n     }\n   }\n   Response Fields     Field  Type  Description     status  string  Overall platform status:   \"ok\"  or   \"degraded\"    server_time  integer  Current server time (Unix epoch)    feeds  object  Per-feed health data, keyed by feed compound key    feeds.*.status  string   \"live\" ,   \"stale\" , or   \"offline\"  — see table below    feeds.*.last_candle_time  integer  Open time of the most recent bar received (Unix epoch)    feeds.*.last_candle_age_seconds  integer  Seconds elapsed since the last bar was received    feeds.*.last_heartbeat_age_seconds  integer  Seconds since the feed last delivered data to us.   This is the liveness signal  — present only for feeds whose collector reports to this endpoint    feeds.*.symbol_count  integer  Number of active symbols in this feed    feeds.*.liveness_source  string   Always present.  Which signal   status  was derived from:   heartbeat  (the feed's own ingest signal),   candle  (newest stored bar), or   none    feeds.*.clock_offset_seconds  integer   Measured  broker-clock offset for this feed.   real_utc = feed_timestamp + clock_offset_seconds .   Absent means unknown — never treat a missing value as 0    feeds.*.clock_offset_age_seconds  integer  How long ago that offset was last measured    feeds.*.clock_note  string  Present only on feeds stored on a broker clock (ISE). Explains why   last_candle_time  is not comparable to   server_time    Timestamps on some feeds are on the broker's clock, not UTC — and this applies to\nthe WebSocket stream as well as to stored history.  Every timestamp we return for a feed —   last_candle_time , REST   \u002Fv1\u002Fhistory  bars,\nand WebSocket   tick.t  and   bar.time  — is on that feed's own broker clock. Convert\nwith:   real_utc = feed_timestamp + clock_offset_seconds\n  Measured values differ enormously. WM feeds run 3 hours ahead\n(  clock_offset_seconds: -10800 ). ISE feeds run on an exchange terminal whose clock is\nroughly 4018 days behind (  clock_offset_seconds: 347142601 ). Feeds that already store\ntrue UTC report   0 .   Read the value; never hardcode it.  The ISE offset is not a whole number of days —\nit carries Iran Standard Time on top of the date shift, so subtracting a round 4018\ndays lands about 3.5 hours out. The value is measured from the feed's own live\ntimestamps, so it stays correct if a broker clock is ever corrected.   Refresh it on every status poll , not only at startup — it costs nothing and means\nyou never run on a value we have since corrected.    If   clock_offset_seconds  is absent, the offset is UNKNOWN. Do not default to 0.  A missing field means we have never measured that feed. Substituting 0 would leave ISE\ntimestamps mis-converted by about eleven years, with no error anywhere — the failure is\nsilent in both directions. Treat absent as \"cannot convert yet\" and retry.  In practice it should not be absent: the value is measured from live traffic but\n  persisted for 30 days , so it survives both our restarts and a long market close.\nThat matters because Tehran trades roughly four hours a day, five days a week — an\nISE feed is quiet for most of the week, and an offset that only existed in memory would\nvanish after any deploy until the market reopened.   clock_offset_age_seconds  tells you\nhow old the measurement is; a large age on a closed market is expected and the value is\nstill correct, because the offset is a property of the broker's clock rather than of\nrecent activity.    Use   last_heartbeat_age_seconds , not   last_candle_time , to decide whether a feed is alive.  Two reasons:    Some feeds are stored on their broker's own clock.  ISE timestamps come from the exchange terminal, which runs   4018 days behind UTC . Subtracting   last_candle_time  from   server_time  for an ISE feed yields an age of roughly eleven years on a perfectly healthy feed. Those feeds carry   last_candle_time: 0  and a   clock_note .   A stored bar can advance while live collection is stopped , because historical repair writes through a different path. A moving   last_candle_time  is therefore not proof that a feed is live; the heartbeat is.   status  already accounts for all of this — if you only need a verdict, read   status .   Feed Status Meaning     Status  Meaning  Condition     live  Feed is healthy and up to date  Last bar received \u003C 5 minutes ago    stale  Minor delay detected  Last bar received 5–30 minutes ago    offline  Feed disconnected  Last bar received > 30 minutes ago   During forex market closed hours (Friday night → Sunday evening UTC+3),   stale  or   offline  status is   expected  — no new candles are produced when markets are closed. Crypto feeds (BTCUSD, ETHUSD, etc.) run 24\u002F7 and should always show   live .   Example — Health Monitor     import requests\n   import time\n   \n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   \n   def check_status():\n       resp = requests.get(\n           \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstatus\",\n           headers=headers\n       )\n       data = resp.json()\n   \n       icons = {\"live\": \"✅\", \"stale\": \"⚠️\", \"offline\": \"❌\"}\n       for feed_name, feed in data[\"feeds\"].items():\n           icon = icons.get(feed[\"status\"], \"?\")\n           age  = feed[\"last_candle_age_seconds\"]\n           print(f\"{icon} {feed_name}: {feed['status']} — last bar {age}s ago\")\n   \n       return data[\"status\"] == \"ok\"\n   \n   # Check every 60 seconds\n   while True:\n       ok = check_status()\n       if not ok:\n           print(\"⚠️  Platform degraded — check feeds\")\n       time.sleep(60)\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":100,"path":101,"dir":47,"title":102,"description":103,"keywords":104,"body":106},"content:2.api-reference:5.usage.md","\u002Fapi-reference\u002Fusage","Usage — GET \u002Fv1\u002Fusage","Query your API key usage statistics — request counts, limits, and WebSocket session data",[51,52,53,105],"Example","  GET \u002Fv1\u002Fusage  Returns usage statistics for your API key — total request counts broken down by endpoint, rate limit details, and WebSocket activity.   Request   GET \u002Fv1\u002Fusage\n   Auth:  Required  Query Parameters     Parameter  Type  Default  Description     days  integer  7  Number of days of history to include (max: 90)   Response     {\n     \"api_key\"  :   \"fxd_1234567890abcdef...\"  ,\n     \"tier\"  :   \"standard\"  ,\n     \"limits\"  : {\n       \"rpm\"  :   120  ,\n       \"rpd\"  :   50000  ,\n       \"ws_connections\"  :   10  ,\n       \"ws_symbols\"  :   200\n     },\n     \"current\"  : {\n       \"ws_connections_in_use\"  :   4\n     },\n     \"today\"  : {\n       \"requests\"  :        1234  ,\n       \"history\"  :          800  ,\n       \"symbols\"  :          200  ,\n       \"coverage\"  :          50  ,\n       \"status\"  :            30  ,\n       \"config\"  :            10  ,\n       \"usage\"  :              5  ,\n       \"ws_connect\"  :         3  ,\n       \"ws_session\"  :         1  ,\n       \"rate_limited\"  :       0\n     },\n     \"history\"  : [\n       {\n         \"date\"  :            \"2025-06-09\"  ,\n         \"requests\"  :         5280  ,\n         \"history\"  :          3100  ,\n         \"symbols\"  :           650  ,\n         \"coverage\"  :          120  ,\n         \"status\"  :            200  ,\n         \"config\"  :             60  ,\n         \"usage\"  :             150  ,\n         \"ws_connect\"  :         15  ,\n         \"ws_session\"  :          8  ,\n         \"rate_limited\"  :        0\n       }\n     ]\n   }\n   Response Fields  Key Information     Field  Type  Description     api_key  string  Prefix of your key    tier  string  Plan tier:   basic ,   standard ,   pro , or   enterprise    limits.rpm  integer  Max requests per minute    limits.rpd  integer  Max requests per day    limits.ws_connections  integer  Max simultaneous WebSocket connections    limits.ws_symbols  integer  Max subscriptions   per WebSocket connection    current.ws_connections_in_use  integer  WebSocket connections open   right now    Sizing a WebSocket pool.    limits.ws_symbols  is per connection and feed-agnostic — one\nsocket can carry symbols from any mix of your granted feeds — so the sockets you need is\n  ceil(total_symbols \u002F limits.ws_symbols) . Note that subscribing to a symbol for both\n  bar  and   tick  consumes   two  of that allowance.  Check   current.ws_connections_in_use  before opening a connection rather than discovering\nthe cap at the upgrade. A slot left by an ungraceful disconnect is reclaimed within 90\nseconds, so this can briefly read higher than the sockets you have open. Both values are\nper-key settings that can be raised — read them at runtime instead of hardcoding.  Daily Statistics (  today  and   history[] )     Field  Description     requests  Total REST requests    history  Calls to   \u002Fv1\u002Fhistory    symbols  Calls to   \u002Fv1\u002Fsymbols    coverage  Calls to   \u002Fv1\u002Fcoverage    status  Calls to   \u002Fv1\u002Fstatus    config  Calls to   \u002Fv1\u002Fconfig    usage  Calls to   \u002Fv1\u002Fusage    ws_connect  Number of WebSocket connections opened    ws_session  Number of active WebSocket sessions    rate_limited  Requests rejected for exceeding a limit — includes REST 429s   and  WebSocket upgrades refused with   TOO_MANY_CONNECTIONS   Example     import requests\n   \n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   \n   resp = requests.get(\n       \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fusage\",\n       headers=headers,\n       params={\"days\": 30}\n   )\n   data = resp.json()\n   \n   print(f\"Plan: {data['tier']}\")\n   print(f\"Daily cap: {data['limits']['rpd']:,} requests\")\n   print(f\"Today: {data['today']['requests']:,} requests used\")\n   print(f\"Rate limited: {data['today']['rate_limited']:,}\")\n   \n   pct = data[\"today\"][\"requests\"] \u002F data[\"limits\"][\"rpd\"] * 100\n   print(f\"Utilisation: {pct:.1f}%\")\n   \n   if data[\"today\"][\"rate_limited\"] > 0:\n       print(\"⚠️  You hit rate limits today — consider upgrading your plan\")\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":108,"path":109,"dir":47,"title":110,"description":111,"keywords":112,"body":126},"content:2.api-reference:6.websocket.md","\u002Fapi-reference\u002Fwebsocket","WebSocket — WS \u002Fv1\u002Fstream","Real-time M1 bar streaming via WebSocket — connection flow, message types, and full client examples",[113,114,115,116,117,118,119,120,121,122,123,124,125],"Connection","Message Flow","Server → Connected","Timestamps on the stream","Client → Subscribe","Server → Subscribed","Server → Bar","Client → Unsubscribe","Client → Ping","Connection Errors","Full JavaScript Client","Python Client (asyncio)","Limits","  WebSocket — \u002Fv1\u002Fstream  Subscribe to   live M1 bar updates  delivered the moment each one-minute candle closes. Typical latency is under 100ms.   Connection   wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream\n  Authenticate via query parameter (standard   WebSocket()  does not support custom headers):     const   ws   =   new   WebSocket  (\n     \"wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key=fxd_your_key_here\"\n   );\n   Message Flow   Client                          Server\n  │                               │\n  │  ← { type: \"connected\" }      │  Immediately on connect\n  │                               │\n  │  → { action: \"subscribe\" }    │  Subscribe to symbols\n  │                               │\n  │  ← { type: \"subscribed\" }     │  Subscription confirmed\n  │                               │\n  │  ← { type: \"bar\" }            │  New M1 bar (every minute)\n  │  ← { type: \"bar\" }            │\n  │  ...                          │\n  │                               │\n  │  → { action: \"ping\" }         │  Keepalive\n  │  ← { type: \"pong\" }           │\n   Server → Connected  Sent immediately after the connection is established:     {\n     \"type\"  :   \"connected\"  ,\n     \"session_id\"  :   \"s_a1b2c3d4\"  ,\n     \"server_time\"  :   1749600000  ,\n     \"limits\"  : {\n       \"max_symbols\"  :   200\n     }\n   }\n   max_symbols  is the number of subscriptions   this connection  may hold. Two things\nabout it are easy to get wrong:    It is per connection, not per feed.  One connection can carry symbols from any mix\nof feeds you are granted. You do not need a separate socket per broker.    bar  and   tick  count separately.  Subscribing to the same symbol for both uses\n  two  of your allowance.    max_symbols  is a per-key setting and can be raised — do not hardcode it. Read it from\nthis frame (or from   limits.ws_symbols  on    \u002Fv1\u002Fusage ) and size\nyour connection pool from that value at runtime.   Timestamps on the stream   tick.t  and   bar.time  are   Unix seconds , and they are on the   feed's broker\nclock  — the same convention as   candle_time  in the database and as REST\n  \u002Fv1\u002Fhistory . They are   not  real UTC for every feed.  Convert with the per-feed offset from    \u002Fv1\u002Fstatus :   real_utc = t + clock_offset_seconds\n     Feed   clock_offset_seconds  Meaning     WMMARKETS:*   -10800  broker runs 3 h ahead of UTC    ISE:TADIL ,   ISE:NOTADIL   347142601  exchange terminal runs ~4018 days behind    OANDA:LIVE ,   ALPARI:LIVE ,   LITEFINANCE:LIVE ,   AMARKETS:LIVE   0  already true UTC   Do not hardcode these numbers, and in particular do not assume the ISE offset is a\nwhole number of days — it is not. Read   clock_offset_seconds  from   \u002Fv1\u002Fstatus  at\nstartup. An ISE timestamp taken at face value is roughly eleven years old and will\nlook like a dead feed rather than a clock offset.   Client → Subscribe     {\n     \"action\"  :   \"subscribe\"  ,\n     \"symbols\"  : [\n       \"WMMARKETS:ZERO:EURUSD\"  ,\n       \"WMMARKETS:ZERO:GBPUSD\"  ,\n       \"WMMARKETS:ZERO:XAUUSD\"\n     ],\n     \"type\"  :   \"bar\"\n   }\n     Field  Value  Description     action   \"subscribe\"  Required    symbols  string   Compound keys to subscribe to    type   \"bar\"  Data type — only   bar  is currently supported   Server → Subscribed     {\n     \"type\"  :   \"subscribed\"  ,\n     \"symbols\"  : [  \"WMMARKETS:ZERO:EURUSD\"  ,   \"WMMARKETS:ZERO:GBPUSD\"  ],\n     \"rejected\"  : []\n   }\n  If a symbol is rejected (no feed access or symbol cap reached):     {\n     \"type\"  :   \"subscribed\"  ,\n     \"symbols\"  : [  \"WMMARKETS:ZERO:EURUSD\"  ],\n     \"rejected\"  : [  \"WMMARKETS:ZERO:GBPUSD\"  ]\n   }\n   Server → Bar  Delivered each time an M1 candle closes:     {\n     \"type\"  :   \"bar\"  ,\n     \"symbol\"  :   \"WMMARKETS:ZERO:EURUSD\"  ,\n     \"bar\"  : {\n       \"time\"  :     1749599940  ,\n       \"open\"  :     1.08215  ,\n       \"high\"  :     1.08241  ,\n       \"low\"  :      1.08198  ,\n       \"close\"  :    1.08228  ,\n       \"volume\"  :   145\n     }\n   }\n     Field  Type  Description     bar.time  integer  Bar open time — Unix epoch seconds, in the   same clock as   \u002Fv1\u002Fhistory  for that feed  (WM: broker UTC+3; OANDA: true UTC). See the table below.    bar.open  float  Open price    bar.high  float  High price    bar.low  float  Low price    bar.close  float  Close price    bar.volume  integer  Tick volume     bar.time  uses the same clock as   \u002Fv1\u002Fhistory  for the same feed — and that clock is per-broker.     Feed   bar.time  \u002F   tick.t  is in  Offset vs real UTC     WMMARKETS:*  Broker time (EET, UTC+3)  +3 hours    OANDA:LIVE  True UTC  none    ALPARI:LIVE  True UTC  none    LITEFINANCE:LIVE  True UTC  none    AMARKETS:LIVE  True UTC  none   TradingView   subscribeBars :  pass   bar.time * 1000  straight through — never add or subtract an offset here. Live and historical bars for a given feed already agree with each other; applying a correction to one and not the other is what produces \"time order violation\" errors.    OANDA live prices are mid-quoted.  OANDA's MT5 bars are quoted at the mid rather than the bid, so the live   bar.close  we broadcast is   (bid + ask) \u002F 2  — matching the basis of the bar's own open\u002Fhigh\u002Flow, and matching what the broker's own terminal draws. One consequence: on a 5-decimal pair a live close can carry a sixth decimal (a half-pip, e.g.   1.164745 ), because the mid of two 5-decimal quotes falls on a half unit. Closed bars carry the broker's own values at instrument precision. WM feeds are bid-quoted and unaffected.    Fixed 2026-07-08:  prior to this date,   bar.time  (and   tick.t  on   type:\"tick\"  subscriptions) was incorrectly broadcast 3 hours ahead of the convention described above — i.e. 6 hours ahead of real UTC instead of the intended 3. If you built a workaround for that (e.g. subtracting an extra offset), remove it —   bar.time \u002F  tick.t  now match   \u002Fv1\u002Fhistory  exactly, with no adjustment required.   Client → Unsubscribe     {\n     \"action\"  :   \"unsubscribe\"  ,\n     \"symbols\"  : [  \"WMMARKETS:ZERO:EURUSD\"  ]\n   }\n   Client → Ping  Send every 30 seconds to keep the connection alive:     {   \"action\"  :   \"ping\"   }\n  Response:     {   \"type\"  :   \"pong\"  ,   \"t\"  :   1749600000   }\n   The server closes idle connections after   60 seconds of inactivity . Always send a ping at least every 30 seconds.   Connection Errors   These are rejected   during the HTTP upgrade, before the WebSocket exists . Your client\nwill surface them as a transport-level error such as   Unexpected server response: 401 \nor   Unexpected server response: 429  —   not  as a JSON message on an open socket. Read\nthe HTTP status code; a WebSocket   onmessage  handler will never see these.     HTTP status  Body code  Meaning    401   UNAUTHORIZED  No API key supplied   401   KEY_INVALID  Key not found   401   KEY_INACTIVE  Key disabled   401   KEY_EXPIRED  Key past its expiry date   403   IP_NOT_ALLOWED  Client IP not in the key's allowlist   429   TOO_MANY_CONNECTIONS  Concurrent connection cap reached  Handling 429   TOO_MANY_CONNECTIONS  A 429 upgrade carries headers telling you exactly where you stand, so you do not have to\nguess:     Retry-After: 5\n   X-Ws-Connections-Limit: 10\n   X-Ws-Connections-In-Use: 10\n   HTTP header names are case-insensitive and most clients handle this for you, but\nnote the casing actually sent is   X-Ws-... , not   X-WS-... . If your client matches\nheader names exactly, look them up case-insensitively.  Closing a socket frees its slot immediately. A slot left behind by an ungraceful\ndisconnect (process killed, network drop) is reclaimed automatically   within 90\nseconds  — so if   in-use  looks higher than the number of sockets you believe you have\nopen, wait out that window rather than reconnecting harder.   Check    \u002Fv1\u002Fusage  before opening connections: it reports both\nyour cap and your live in-use count. Polling that once is cheaper and more reliable than\ndiscovering the limit at the upgrade.  Refused upgrades are counted in your   rate_limited  usage metric.   Full JavaScript Client     class   ForexWebSocket   {\n     constructor  (  apiKey  ) {\n       this  .apiKey        =   apiKey;\n       this  .ws            =   null  ;\n       this  .handlers      =   {};\n       this  .pingTimer     =   null  ;\n       this  .reconnectMs   =   5000  ;\n     }\n   \n     connect  () {\n       this  .ws   =   new   WebSocket  (\n         `wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key=${  this  .  apiKey  }`\n       );\n   \n       this  .ws.  onopen   =   ()   =>   {\n         console.  log  (  \"WebSocket connected\"  );\n         this  .reconnectMs   =   5000  ;    \u002F\u002F reset backoff\n   \n         this  .pingTimer   =   setInterval  (()   =>   {\n           if   (  this  .ws.readyState   ===   WebSocket.  OPEN  ) {\n             this  .ws.  send  (  JSON  .  stringify  ({ action:   \"ping\"   }));\n           }\n         },   30_000  );\n       };\n   \n       this  .ws.  onmessage   =   (  event  )   =>   {\n         const   msg   =   JSON  .  parse  (event.data);\n   \n         switch   (msg.type) {\n           case   \"connected\"  :\n             console.  log  (  `Session: ${  msg  .  session_id  } | Max symbols: ${  msg  .  limits  .  max_symbols  }`  );\n             break  ;\n   \n           case   \"subscribed\"  :\n             console.  log  (  `Subscribed to ${  msg  .  symbols  .  length  } symbols`  );\n             if   (msg.rejected.  length  ) {\n               console.  warn  (  \"Rejected:\"  , msg.rejected);\n             }\n             break  ;\n   \n           case   \"bar\"  :\n             this  .handlers[msg.symbol]?.(msg.bar);\n             break  ;\n   \n           case   \"error\"  :\n             console.  error  (  \"Server error:\"  , msg.code);\n             if   ([  \"KEY_INVALID\"  ,   \"KEY_INACTIVE\"  ,   \"KEY_EXPIRED\"  ].  includes  (msg.code)) {\n               this  .ws.  close  ();    \u002F\u002F don't reconnect on auth errors\n             }\n             break  ;\n         }\n       };\n   \n       this  .ws.  onclose   =   ()   =>   {\n         clearInterval  (  this  .pingTimer);\n         console.  log  (  `Disconnected — reconnecting in ${  this  .  reconnectMs   \u002F   1000  }s`  );\n         setTimeout  (()   =>   {\n           this  .reconnectMs   =   Math.  min  (  this  .reconnectMs   *   2  ,   60_000  );\n           this  .  connect  ();\n         },   this  .reconnectMs);\n       };\n     }\n   \n     subscribe  (  symbols  ,   onBar  ) {\n       symbols.  forEach  (  sym   =>   {   this  .handlers[sym]   =   onBar; });\n       if   (  this  .ws?.readyState   ===   WebSocket.  OPEN  ) {\n         this  .ws.  send  (  JSON  .  stringify  ({\n           action:   \"subscribe\"  ,\n           symbols,\n           type:   \"bar\"\n         }));\n       }\n     }\n   \n     disconnect  () {\n       clearInterval  (  this  .pingTimer);\n       this  .ws?.  close  ();\n     }\n   }\n   \n   \u002F\u002F Usage\n   const   client   =   new   ForexWebSocket  (  \"fxd_your_key_here\"  );\n   client.  connect  ();\n   \n   client.  subscribe  (\n     [  \"WMMARKETS:ZERO:EURUSD\"  ,   \"WMMARKETS:ZERO:XAUUSD\"  ],\n     (  bar  )   =>   {\n       const   time   =   new   Date  (bar.time   *   1000  ).  toISOString  ();\n       console.  log  (  `close: ${  bar  .  close  } at ${  time  }`  );\n     }\n   );\n   Python Client (asyncio)     import asyncio\n   import json\n   import websockets\n   \n   API_KEY = \"fxd_your_key_here\"\n   URI     = f\"wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key={API_KEY}\"\n   \n   SYMBOLS = [\n       \"WMMARKETS:ZERO:EURUSD\",\n       \"WMMARKETS:ZERO:GBPUSD\",\n       \"WMMARKETS:ZERO:XAUUSD\",\n   ]\n   \n   async def stream():\n       reconnect_delay = 5\n       while True:\n           try:\n               async with websockets.connect(URI, ping_interval=30) as ws:\n                   print(\"Connected ✓\")\n                   reconnect_delay = 5\n   \n                   await ws.send(json.dumps({\n                       \"action\": \"subscribe\",\n                       \"symbols\": SYMBOLS,\n                       \"type\": \"bar\"\n                   }))\n   \n                   async for raw in ws:\n                       msg = json.loads(raw)\n                       if msg[\"type\"] == \"bar\":\n                           bar = msg[\"bar\"]\n                           sym = msg[\"symbol\"].split(\":\")[-1]\n                           print(f\"{sym}: close={bar['close']:.5f}\")\n                       elif msg[\"type\"] == \"error\":\n                           print(f\"Error: {msg['code']}\")\n                           if msg[\"code\"] in (\"KEY_INVALID\", \"KEY_INACTIVE\"):\n                               return  # stop on auth errors\n   \n           except websockets.exceptions.ConnectionClosed as e:\n               print(f\"Disconnected ({e}) — retrying in {reconnect_delay}s\")\n               await asyncio.sleep(reconnect_delay)\n               reconnect_delay = min(reconnect_delay * 2, 60)\n   \n   asyncio.run(stream())\n   Limits     Limit  Description    Simultaneous connections  Plan-dependent (typically 2–50)   Symbols per connection  Plan-dependent (typically 50–500)   Idle timeout  60 seconds without any message   Recommendation  Send a   ping  every 30 seconds  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":128,"path":129,"dir":130,"title":131,"description":132,"keywords":133,"body":140},"content:3.guides:0.rate-limiting.md","\u002Fguides\u002Frate-limiting","guides","Rate Limiting","How rate limits work — headers, response codes, backoff strategies, and best practices",[134,135,136,137,138,139,42],"Default Limits by Plan","WebSocket Limits","Rate-Limit Headers","429 Response","Exponential Backoff","Proactive Self-Throttling","  Rate Limiting  The API enforces two independent rate-limit windows:   per minute  and   per day . Both\nare checked on every REST request.  WebSocket connections are governed separately — see   WebSocket limits \nbelow.   Default Limits by Plan     Plan  RPM (per minute)  RPD (per day)  WS connections  Symbols per WS connection    Basic  30  10,000  2  200   Standard  120  50,000  10  200   Pro  600  250,000  30  500   Enterprise  Custom  Custom  Custom  Custom   These are   starting defaults, not fixed ceilings.  Every limit above is set per key and\ncan be adjusted without changing plan — if one of them is the only thing blocking you,\ncontact us before re-architecting around it. Always read your own live values from\n   \u002Fv1\u002Fusage  rather than hardcoding the numbers in this table.   WebSocket Limits  WebSocket connections are   not  governed by RPM\u002FRPD. Opening a connection does not\nconsume your request budget, and the   bar \u002F  tick  messages you receive are not counted as\nrequests. Two separate limits apply instead:     Limit  Field on   \u002Fv1\u002Fusage  Enforced    Concurrent connections   limits.ws_connections  At the HTTP upgrade — exceeding it returns   429   TOO_MANY_CONNECTIONS   Subscriptions per connection   limits.ws_symbols  At   subscribe  — symbols over the cap come back in the   rejected  array  There is   no separate limit on connection attempts  — only on concurrent connections. A\nreconnect loop will not get you throttled further or blocked. It will also never succeed\non its own, though: if you are at the cap, retrying cannot help until a slot frees, so\nback off rather than spinning.  Refused upgrades are counted in your   rate_limited  usage metric.    Prefer fewer, fuller connections.    ws_symbols  is per connection and feed-agnostic, so\none socket can carry symbols from any mix of your granted feeds. Sizing as\n  ceil(total_symbols \u002F limits.ws_symbols)  uses far fewer sockets than one-per-feed.   Rate-Limit Headers  Every response includes current consumption data so you can self-throttle:     X-RateLimit-Limit-Minute: 120\n   X-RateLimit-Remaining-Minute: 98\n   X-RateLimit-Reset-Minute: 1749600060\n   X-RateLimit-Limit-Day: 50000\n   X-RateLimit-Remaining-Day: 48766\n   429 Response  When you exceed a limit, the API returns:     HTTP\u002F1.1 429 Too Many Requests\n   Retry-After: 42\n   X-RateLimit-Exceeded: minute\n   Content-Type: application\u002Fjson\n     {\n     \"error\"  : {\n       \"code\"  :   \"RATE_LIMIT_EXCEEDED\"  ,\n       \"message\"  :   \"Rate limit exceeded (minute). Retry after 42 seconds.\"\n     }\n   }\n  Always read   Retry-After  — it gives the exact number of seconds to wait, never guess.   Exponential Backoff     import time\n   import random\n   import requests\n   \n   def api_get(url, headers, params=None, max_retries=5):\n       \"\"\"GET with exponential backoff on 429.\"\"\"\n       for attempt in range(max_retries):\n           resp = requests.get(url, headers=headers, params=params)\n   \n           if resp.status_code == 200:\n               return resp.json()\n   \n           if resp.status_code == 429:\n               retry_after = int(resp.headers.get(\"Retry-After\", 60))\n               jitter = random.uniform(0, 5)\n               wait   = retry_after + jitter\n               print(f\"Rate limited (attempt {attempt + 1}) — waiting {wait:.1f}s\")\n               time.sleep(wait)\n               continue\n   \n           # Don't retry on other 4xx errors\n           resp.raise_for_status()\n   \n       raise RuntimeError(\"Max retries exceeded\")\n   Proactive Self-Throttling  Instead of waiting for a 429, monitor the headers and slow down proactively:     import time\n   import requests\n   \n   class RateLimitedClient:\n       def __init__(self, api_key, rpm=120):\n           self.session      = requests.Session()\n           self.session.headers.update({\"X-API-Key\": api_key})\n           self.min_interval = 60.0 \u002F rpm\n           self.last_call    = 0.0\n           self.remaining    = rpm\n   \n       def get(self, url, params=None):\n           # If close to the limit, wait for the minute to reset\n           if self.remaining \u003C= 5:\n               wait = 60 - (time.time() % 60) + 1\n               print(f\"Approaching limit — pausing {wait:.0f}s\")\n               time.sleep(wait)\n   \n           # Enforce minimum interval between calls\n           elapsed = time.time() - self.last_call\n           if elapsed \u003C self.min_interval:\n               time.sleep(self.min_interval - elapsed)\n   \n           resp = requests.get(url, headers=self.session.headers, params=params)\n   \n           self.remaining = int(resp.headers.get(\"X-RateLimit-Remaining-Minute\", 999))\n           self.last_call = time.time()\n   \n           return resp\n   \n   client = RateLimitedClient(\"fxd_your_key\", rpm=120)\n   Best Practices  1 — Cache closed candles  Closed bars never change. Cache them locally rather than re-fetching:     import functools\n   \n   @functools.lru_cache(maxsize=2000)\n   def get_daily_candles(symbol, date_str):\n       \"\"\"Cache daily OHLCV by symbol + date.\"\"\"\n       return api.history(symbol, \"1D\", from_date=date_str, to_date=date_str)\n  2 — Use WebSocket for live prices  Don't poll   \u002Fv1\u002Fhistory  every second to check for new bars. Subscribe to the WebSocket instead — you get pushed updates the instant each M1 bar closes.  3 — Fetch large batches, not many small ones  Instead of 10 separate requests for 10 symbols, batch using   countback :     # Don't do this — 10 requests\n   for symbol in symbols:\n       data = client.history(symbol, \"60\", countback=10)\n   \n   # Better — one larger request per symbol but fetch more at once\n   data = client.history(\"WMMARKETS:ZERO:EURUSD\", \"60\", countback=500)\n  4 — Stagger parallel requests  If you must make multiple simultaneous requests, add a small delay between them to avoid burst spikes:     import time\n   \n   for symbol in symbols:\n       process(client.history(symbol, \"60\", 100))\n       time.sleep(0.1)  # ~10 requests\u002Fsecond = well under 120 RPM\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":142,"path":143,"dir":130,"title":14,"description":144,"keywords":145,"body":152},"content:3.guides:1.symbol-format.md","\u002Fguides\u002Fsymbol-format","The compound key format BROKER:ACCOUNT:SYMBOL — structure, examples, and normalization rules",[146,54,147,148,149,150,151],"Structure","OANDA share CFDs — dots and mixed case","Symbol Normalization","Case Sensitivity","Fetch the Full Symbol List","Filtering by Type","  Symbol Format  Every instrument in the API is identified by a   compound key  — a colon-separated string with three parts.   Structure   WMMARKETS:ZERO:EURUSD\n─────────┬──────┬──────\n         │      │\n         │      └─ Display symbol (uppercase, no broker suffix)\n         │\n         └─ Account type (uppercase)\n\nWMMARKETS ── Broker name (uppercase)\n  The compound key is used as the   symbol  parameter in every endpoint:    GET \u002Fv1\u002Fsymbols?symbol=WMMARKETS:ZERO:EURUSD   GET \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:EURUSD&resolution=60  WebSocket   subscribe  message   Examples     Compound Key  Broker  Account  Instrument     WMMARKETS:ZERO:EURUSD  WM Markets  Hedge  EUR\u002FUSD    WMMARKETS:ZERO:XAUUSD  WM Markets  Hedge  Gold\u002FUSD    WMMARKETS:ZERO:BTCUSD  WM Markets  Hedge  Bitcoin\u002FUSD    WMMARKETS:ZERO:US30  WM Markets  Hedge  Dow Jones    WMMARKETS:ZERO:AAPL  WM Markets  Hedge  Apple Inc. CFD    OANDA:LIVE:EURUSD  OANDA  Live  EUR\u002FUSD    OANDA:LIVE:XAUUSD  OANDA  Live  Gold\u002FUSD    OANDA:LIVE:AAPL_CFD.US  OANDA  Live  Apple Inc. CFD (NASDAQ)    OANDA:LIVE:ASML_CFD.NL  OANDA  Live  ASML CFD (Euronext Amsterdam)    OANDA:LIVE:ERICb_CFD.SE  OANDA  Live  Ericsson B CFD (Stockholm)   OANDA share CFDs — dots and mixed case  The   OANDA:LIVE  feed carries about 1,620 instruments, and roughly 1,540 of them are   share CFDs across 12 exchanges . Their names differ from the FX-style symbols in two ways that will break naive parsing:   1. They contain a dot.  The instrument part is   \u003CTICKER>_CFD.\u003CEXCHANGE>  —   AAPL_CFD.US ,   ASML_CFD.NL ,   MC_CFD.FR ,   SAP_CFD.DE . A compound key therefore looks like:   OANDA:LIVE:AAPL_CFD.US\n└──┬──┘ └┬─┘ └────┬────┘\nbroker  acct   instrument (contains a dot)\n  If you split a compound key,   split on   :  and take at most 3 parts  (  split(':', 2)  in Python,   split(':')  then re-join the tail in JS). Splitting on   .  or assuming the instrument is alphanumeric will corrupt these names.   2. Some tickers are mixed case, and the case is meaningful.  Nordic share classes encode the class as a lower-case trailing letter:   ERICb_CFD.SE  (Ericsson   B ),   VOLVb_CFD.SE ,   NOVOb_CFD.DK ,   MAERSKa_CFD.DK ,   SEBa_CFD.SE . Upper-casing these produces a symbol that does not exist.   Symbol Normalization  MT5 broker symbols sometimes include suffixes that differ between brokers. The API strips these and stores only the clean display symbol:     MT5 Raw Symbol  API Compound Key     EURUSD   WMMARKETS:ZERO:EURUSD    EURUSDm   WMMARKETS:ZERO:EURUSD    EURUSD.   WMMARKETS:ZERO:EURUSD    EURUSD@   WMMARKETS:ZERO:EURUSD    EURUSD.sml  (OANDA)   OANDA:LIVE:EURUSD  You never need to handle broker-specific suffixes — the API always exposes the clean name. Note that the exchange suffix on an OANDA share CFD (  .US ,   .NL ,   .SE ) is   not  a broker suffix: it is part of the instrument's identity and is always present.   Case Sensitivity    Symbol lookups are case-SENSITIVE. Use the exact string   \u002Fv1\u002Fsymbols  returns.   \u002Fv1\u002Fhistory ,   \u002Fv1\u002Fcoverage  and the WebSocket   subscribe  all match the compound key exactly.   WMMARKETS:ZERO:eurusd  returns   no_data , not EUR\u002FUSD.  This matters most for OANDA's Nordic share CFDs, where the canonical form is genuinely mixed case —   OANDA:LIVE:ERICb_CFD.SE  resolves,   OANDA:LIVE:ERICB_CFD.SE  does not, because the lower-case   b  is the share class.  Free-text   search  (  \u002Fv1\u002Fsymbols?search= )   is  case-insensitive — it is only the exact-key lookups that are strict. The safe pattern is to take symbol strings from   \u002Fv1\u002Fsymbols  verbatim and never re-case them.   Fetch the Full Symbol List     import requests\n   \n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   \n   resp = requests.get(\n       \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fsymbols\",\n       headers=headers,\n       params={\"limit\": 1000}\n   )\n   \n   for sym in resp.json()[\"symbols\"]:\n       print(f\"{sym['symbol']:\u003C45} | {sym['display_symbol']:\u003C12} | {sym['type']}\")\n  Sample output:   WMMARKETS:ZERO:AUDUSD                        | AUD\u002FUSD     | forex\nWMMARKETS:ZERO:BTCUSD                        | BTC\u002FUSD     | crypto\nWMMARKETS:ZERO:GER40                         | GER40       | index\nWMMARKETS:ZERO:AAPL                          | AAPL        | cfd\nWMMARKETS:ZERO:XAUUSD                        | XAU\u002FUSD     | forex\n   Filtering by Type     # Get only forex symbols\n   resp = requests.get(\n       \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fsymbols\",\n       headers=headers,\n       params={\"limit\": 1000}\n   )\n   symbols = resp.json()[\"symbols\"]\n   forex = [s for s in symbols if s[\"type\"] == \"forex\"]\n   crypto = [s for s in symbols if s[\"type\"] == \"crypto\"]\n   indices = [s for s in symbols if s[\"type\"] == \"index\"]\n   cfds = [s for s in symbols if s[\"type\"] == \"cfd\"]\n   \n   print(f\"Forex: {len(forex)} | Crypto: {len(crypto)} | Indices: {len(indices)} | CFDs: {len(cfds)}\")\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":154,"path":155,"dir":130,"title":156,"description":157,"keywords":158,"body":163},"content:3.guides:2.resolutions.md","\u002Fguides\u002Fresolutions","Resolutions","All supported bar resolutions, history depth, and how to choose the right timeframe",[159,160,161,162],"Complete Resolution Table","Choosing the Right Resolution","Fetch Multiple Timeframes","TradingView Resolution Mapping","  Resolutions  Complete Resolution Table     API Value  Name  Bar Duration  Best For  History Depth     1  M1  1 minute  Scalping, tick-level analysis  ~3 months    5  M5  5 minutes  Short-term intraday  ~12 months    10  M10  10 minutes  Short-term intraday  ~12 months    15  M15  15 minutes  Intraday swing  ~12 months    30  M30  30 minutes  Intraday swing  Derived from M1    60  H1  1 hour  Swing trading  From January 2024    120  H2  2 hours  Swing trading  From January 2024    240  H4  4 hours  Position trading  From January 2024    1D  Daily  1 day  Long-term analysis  From January 2024    1W  Weekly  1 week  Long-term analysis  From January 2024    1M  Monthly  1 month  Macro analysis  From January 2024   Choosing the Right Resolution    M1 for scalping:  Use M1 when you need minute-level precision. Keep in mind the ~3-month depth limit — for any backtesting beyond 3 months, step up to M5.    H1 to H4 for swing strategies:  Best balance of data quality and history depth. All 230 symbols have H1+ data going back to January 2024, making these ideal for most algorithmic strategies.    Daily and above for macro analysis:  Full history from January 2024 across all 230 symbols. Use   1D  for trend filters, regime detection, or long-term signal generation.    Resolution support is per feed — read   supported_resolutions  from   \u002Fv1\u002Fsymbols .  The table above describes the forex feeds. The Iran Stock Exchange feeds (  ISE:TADIL ,   ISE:NOTADIL ) serve    60 ,   240 ,   1D ,   1W ,   1M  only  — there is no   1 ,   5 ,   10 ,   15  or   30  for those symbols, and a request for one returns no data. Hourly depth also differs by account:   ISE:TADIL  reaches back years for most instruments,   ISE:NOTADIL  roughly one to two. Never hardcode this list; take it from the symbol's own   supported_resolutions .   Fetch Multiple Timeframes     import requests\n   \n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   BASE    = \"https:\u002F\u002Fapi.pipdata.net\"\n   SYMBOL  = \"WMMARKETS:ZERO:EURUSD\"\n   \n   timeframes = {\n       \"M1\":  (\"1\",    200),\n       \"M15\": (\"15\",   500),\n       \"H1\":  (\"60\",   500),\n       \"H4\":  (\"240\",  200),\n       \"D1\":  (\"1D\",   365),\n   }\n   \n   data = {}\n   for name, (resolution, countback) in timeframes.items():\n       resp = requests.get(f\"{BASE}\u002Fv1\u002Fhistory\", headers=headers, params={\n           \"symbol\":     SYMBOL,\n           \"resolution\": resolution,\n           \"countback\":  countback\n       })\n       d = resp.json()\n       if d[\"s\"] == \"ok\":\n           data[name] = d\n           print(f\"{name}: {len(d['t'])} bars\")\n       else:\n           print(f\"{name}: {d['s']}\")\n   TradingView Resolution Mapping  If you are building a TradingView DataFeed, the   resolution  values map directly to TradingView's interval format:     TradingView Interval  API   resolution     1   1    5   5    10   10    15   15    30   30    60   60    120   120    240   240    D   1D    W   1W    M   1M  Pass the TradingView   resolution  string directly — no mapping needed.  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":165,"path":166,"dir":130,"title":167,"description":168,"keywords":169,"body":174},"content:3.guides:3.errors.md","\u002Fguides\u002Ferrors","Error Handling","Complete error code reference and error handling patterns in Python and JavaScript",[170,171,172,173],"Error Response Format","Complete Error Code Reference","Python — Structured Error Handling","JavaScript — Structured Error Handling","  Error Handling  Error Response Format  All API errors use a consistent JSON structure:     {\n     \"error\"  : {\n       \"code\"  :   \"RATE_LIMIT_EXCEEDED\"  ,\n       \"message\"  :   \"Rate limit exceeded (minute). Retry after 42 seconds.\"\n     }\n   }\n  The   code  field is machine-readable and stable across API versions. The   message  is human-readable and may change.   Complete Error Code Reference     HTTP Status  Code  Description  Action    400   INVALID_ID  Malformed ID in request path  Check the resource ID   401   UNAUTHORIZED  No API key provided  Add   X-API-Key  header   401   KEY_INVALID  Key not found  Verify the key value   401   KEY_INACTIVE  Key has been revoked  Contact support   401   KEY_EXPIRED  Key expiry date passed  Request a new key   403   IP_NOT_ALLOWED  IP not on allowlist  Update the IP allowlist   403   SYMBOL_NOT_PERMITTED  Feed not in key's permissions  Upgrade plan or add feed access   404   SYMBOL_NOT_FOUND  Symbol does not exist  Check the symbol name   404   NOT_FOUND  Resource does not exist  Check the URL path   405   METHOD_NOT_ALLOWED  Wrong HTTP method  Use GET   422   INVALID_PARAMS  Missing or invalid query parameters  Check the endpoint documentation   429   RATE_LIMIT_EXCEEDED  Request rate too high  Wait for   Retry-After  seconds   429   TOO_MANY_CONNECTIONS  WebSocket connection cap reached  Returned on the HTTP   upgrade , so your client sees   Unexpected server response: 429 , not a JSON frame. The response carries   Retry-After ,   X-Ws-Connections-Limit  and   X-Ws-Connections-In-Use  (match header names case-insensitively). Close unused connections, or pack more symbols per connection — see   WebSocket .   500   SERVER_ERROR  Internal server error  Retry with backoff   503   SERVICE_UNAVAILABLE  Service temporarily down  Retry with backoff   Python — Structured Error Handling     import time\n   import requests\n   \n   class ForexAPIError(Exception):\n       def __init__(self, code: str, message: str, http_status: int):\n           self.code   = code\n           self.message = message\n           self.status = http_status\n           super().__init__(f\"[{http_status}] {code}: {message}\")\n   \n   \n   def api_get(url: str, headers: dict, params: dict = None) -> dict:\n       \"\"\"\n       Make a GET request with full error handling and 429 retry.\n       Returns the parsed JSON body on success.\n       Raises ForexAPIError on API errors.\n       \"\"\"\n       try:\n           resp = requests.get(url, headers=headers, params=params, timeout=15)\n       except requests.Timeout:\n           raise ForexAPIError(\"TIMEOUT\", \"Request timed out\", 0)\n       except requests.ConnectionError:\n           raise ForexAPIError(\"CONNECTION_ERROR\", \"Could not reach the server\", 0)\n   \n       if resp.status_code == 200:\n           return resp.json()\n   \n       # Parse structured error body\n       try:\n           body  = resp.json()\n           error = body.get(\"error\", {})\n           code  = error.get(\"code\", \"UNKNOWN\")\n           msg   = error.get(\"message\", \"Unknown error\")\n       except Exception:\n           code = \"PARSE_ERROR\"\n           msg  = resp.text\n   \n       raise ForexAPIError(code, msg, resp.status_code)\n   \n   \n   def api_get_with_retry(url, headers, params=None, max_retries=3):\n       \"\"\"Same as api_get but retries on 429 and 5xx.\"\"\"\n       for attempt in range(max_retries):\n           try:\n               return api_get(url, headers, params)\n           except ForexAPIError as e:\n               if e.status == 429:\n                   # Read exact wait time from the response\n                   resp = requests.get(url, headers=headers, params=params, timeout=15)\n                   wait = int(resp.headers.get(\"Retry-After\", 60))\n                   print(f\"Rate limited — waiting {wait}s (attempt {attempt + 1}\u002F{max_retries})\")\n                   time.sleep(wait)\n               elif e.status >= 500:\n                   wait = 2 ** attempt\n                   print(f\"Server error — retrying in {wait}s\")\n                   time.sleep(wait)\n               else:\n                   raise  # Don't retry on 4xx (except 429)\n       raise RuntimeError(\"Max retries exceeded\")\n   \n   \n   # Usage\n   headers = {\"X-API-Key\": \"fxd_your_key\"}\n   \n   try:\n       data = api_get_with_retry(\n           \"https:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fhistory\",\n           headers,\n           {\"symbol\": \"WMMARKETS:ZERO:EURUSD\", \"resolution\": \"60\", \"countback\": 100}\n       )\n   except ForexAPIError as e:\n       if e.code == \"RATE_LIMIT_EXCEEDED\":\n           print(\"Hit rate limit — slow down requests\")\n       elif e.code == \"KEY_EXPIRED\":\n           print(\"API key expired — request a replacement\")\n       elif e.code == \"SYMBOL_NOT_PERMITTED\":\n           print(\"Feed access denied — check key permissions\")\n       else:\n           print(f\"API error: {e}\")\n   JavaScript — Structured Error Handling     class   ForexAPIError   extends   Error   {\n     constructor  (  code  ,   message  ,   status  ) {\n       super  (  `[${  status  }] ${  code  }: ${  message  }`  );\n       this  .code     =   code;\n       this  .status   =   status;\n     }\n   }\n   \n   async   function   apiGet  (  endpoint  ,   params   =   {},   apiKey  ) {\n     const   url   =   new   URL  (  `https:\u002F\u002Fapi.pipdata.net${  endpoint  }`  );\n     Object.  entries  (params).  forEach  (([  k  ,   v  ])   =>   url.searchParams.  set  (k, v));\n   \n     let   resp;\n     try   {\n       resp   =   await   fetch  (url, {\n         headers: {   \"X-API-Key\"  : apiKey },\n         signal: AbortSignal.  timeout  (  15_000  )\n       });\n     }   catch   (err) {\n       throw   new   ForexAPIError  (  \"CONNECTION_ERROR\"  , err.message,   0  );\n     }\n   \n     if   (resp.ok)   return   resp.  json  ();\n   \n     let   code   =   \"UNKNOWN\"  , message   =   \"Unknown error\"  ;\n     try   {\n       const   body   =   await   resp.  json  ();\n       code      =   body?.error?.code      ??   code;\n       message   =   body?.error?.message   ??   message;\n     }   catch   {}\n   \n     throw   new   ForexAPIError  (code, message, resp.status);\n   }\n   \n   \u002F\u002F With 429 retry\n   async   function   apiGetWithRetry  (  endpoint  ,   params  ,   apiKey  ,   maxRetries   =   3  ) {\n     for   (  let   attempt   =   0  ; attempt   \u003C   maxRetries; attempt  ++  ) {\n       try   {\n         return   await   apiGet  (endpoint, params, apiKey);\n       }   catch   (err) {\n         if   (err.status   ===   429  ) {\n           const   wait   =   (  parseInt  (err.message.  match  (  \u002F  \\d  +  \u002F  )?.[  0  ])   ||   60  )   *   1000  ;\n           console.  log  (  `Rate limited — waiting ${  wait   \u002F   1000  }s`  );\n           await   new   Promise  (  r   =>   setTimeout  (r, wait));\n         }   else   if   (err.status   >=   500  ) {\n           const   wait   =   2   **   attempt   *   1000  ;\n           console.  log  (  `Server error — retrying in ${  wait   \u002F   1000  }s`  );\n           await   new   Promise  (  r   =>   setTimeout  (r, wait));\n         }   else   {\n           throw   err;    \u002F\u002F Don't retry 4xx\n         }\n       }\n     }\n     throw   new   Error  (  \"Max retries exceeded\"  );\n   }\n   \n   \u002F\u002F Usage\n   try   {\n     const   data   =   await   apiGetWithRetry  (\n       \"\u002Fv1\u002Fhistory\"  ,\n       { symbol:   \"WMMARKETS:ZERO:EURUSD\"  , resolution:   \"60\"  , countback:   100   },\n       \"fxd_your_key\"\n     );\n     console.  log  (  `${  data  .  t  .  length  } bars received`  );\n   }   catch   (err) {\n     if   (err.code   ===   \"RATE_LIMIT_EXCEEDED\"  ) {\n       console.  error  (  \"Slow down — rate limit hit\"  );\n     }   else   if   (err.code   ===   \"SYMBOL_NOT_PERMITTED\"  ) {\n       console.  error  (  \"Feed access denied\"  );\n     }   else   {\n       console.  error  (  \"API error:\"  , err.message);\n     }\n   }\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":176,"path":177,"dir":178,"title":179,"description":180,"keywords":181,"body":188},"content:4.examples:0.python.md","\u002Fexamples\u002Fpython","examples","Python Examples","Complete Python examples — historical data, DataFrames, multi-symbol download, and live WebSocket streaming",[182,183,184,185,186,187],"Install Dependencies","1 — Base Client","2 — Convert to DataFrame + Technical Indicators","3 — Download All Symbols to CSV","4 — Live WebSocket Stream","5 — Latest Price Snapshot","  Python Examples  Install Dependencies     pip   install   requests   websockets   pandas\n   1 — Base Client  A reusable session-based client with automatic error handling:     # forex_client.py\n   import requests\n   import time\n   from typing import Optional\n   \n   \n   class ForexClient:\n       BASE_URL = \"https:\u002F\u002Fapi.pipdata.net\"\n   \n       def __init__(self, api_key: str):\n           self.session = requests.Session()\n           self.session.headers.update({\"X-API-Key\": api_key})\n   \n       def _get(self, path: str, params: dict = None) -> dict:\n           resp = self.session.get(f\"{self.BASE_URL}{path}\", params=params, timeout=15)\n           resp.raise_for_status()\n           return resp.json()\n   \n       def symbols(self, search: Optional[str] = None, limit: int = 100) -> list:\n           \"\"\"List available symbols, optionally filtered by name.\"\"\"\n           params = {\"limit\": limit}\n           if search:\n               params[\"search\"] = search\n           return self._get(\"\u002Fv1\u002Fsymbols\", params)[\"symbols\"]\n   \n       def history(self, symbol: str, resolution: str, countback: int = 500) -> dict:\n           \"\"\"Fetch the latest N OHLCV bars for a symbol.\"\"\"\n           return self._get(\"\u002Fv1\u002Fhistory\", {\n               \"symbol\":     symbol,\n               \"resolution\": resolution,\n               \"countback\":  countback\n           })\n   \n       def history_range(self, symbol: str, resolution: str, from_ts: int, to_ts: int) -> dict:\n           \"\"\"Fetch OHLCV bars between two Unix timestamps.\"\"\"\n           return self._get(\"\u002Fv1\u002Fhistory\", {\n               \"symbol\":     symbol,\n               \"resolution\": resolution,\n               \"from\":       from_ts,\n               \"to\":         to_ts\n           })\n   \n       def coverage(self, symbol: Optional[str] = None) -> dict:\n           \"\"\"Get available data date ranges.\"\"\"\n           params = {\"symbol\": symbol} if symbol else {}\n           return self._get(\"\u002Fv1\u002Fcoverage\", params)\n   \n       def status(self) -> dict:\n           \"\"\"Check feed health status.\"\"\"\n           return self._get(\"\u002Fv1\u002Fstatus\")\n   \n       def usage(self, days: int = 7) -> dict:\n           \"\"\"Get API key usage statistics.\"\"\"\n           return self._get(\"\u002Fv1\u002Fusage\", {\"days\": days})\n   \n   \n   # Quick test\n   if __name__ == \"__main__\":\n       client = ForexClient(\"fxd_your_key_here\")\n   \n       st = client.status()\n       print(f\"Service: {st['status']}\")\n   \n       data = client.history(\"WMMARKETS:ZERO:EURUSD\", \"60\", countback=5)\n       if data[\"s\"] == \"ok\":\n           print(f\"EUR\u002FUSD last close: {data['c'][-1]:.5f}\")\n   2 — Convert to DataFrame + Technical Indicators     import pandas as pd\n   import numpy as np\n   \n   \n   def to_dataframe(history: dict) -> pd.DataFrame:\n       \"\"\"Convert API response to a timezone-aware pandas DataFrame.\"\"\"\n       if history.get(\"s\") != \"ok\":\n           return pd.DataFrame()\n   \n       df = pd.DataFrame({\n           \"time\":   pd.to_datetime(history[\"t\"], unit=\"s\", utc=True),\n           \"open\":   history[\"o\"],\n           \"high\":   history[\"h\"],\n           \"low\":    history[\"l\"],\n           \"close\":  history[\"c\"],\n           \"volume\": history[\"v\"],\n       })\n       df.set_index(\"time\", inplace=True)\n       return df\n   \n   \n   def add_ema(df: pd.DataFrame, period: int) -> pd.DataFrame:\n       df[f\"ema{period}\"] = df[\"close\"].ewm(span=period, adjust=False).mean()\n       return df\n   \n   \n   def add_rsi(df: pd.DataFrame, period: int = 14) -> pd.DataFrame:\n       delta = df[\"close\"].diff()\n       gain  = delta.clip(lower=0).rolling(period).mean()\n       loss  = (-delta.clip(upper=0)).rolling(period).mean()\n       rs    = gain \u002F loss\n       df[\"rsi\"] = 100 - (100 \u002F (1 + rs))\n       return df\n   \n   \n   def add_atr(df: pd.DataFrame, period: int = 14) -> pd.DataFrame:\n       hl  = df[\"high\"] - df[\"low\"]\n       hpc = (df[\"high\"] - df[\"close\"].shift()).abs()\n       lpc = (df[\"low\"]  - df[\"close\"].shift()).abs()\n       tr  = pd.concat([hl, hpc, lpc], axis=1).max(axis=1)\n       df[\"atr\"] = tr.ewm(span=period, adjust=False).mean()\n       return df\n   \n   \n   # Example\n   client = ForexClient(\"fxd_your_key_here\")\n   raw = client.history(\"WMMARKETS:ZERO:EURUSD\", \"60\", 500)\n   df  = to_dataframe(raw)\n   df  = add_ema(df, 20)\n   df  = add_ema(df, 50)\n   df  = add_rsi(df, 14)\n   df  = add_atr(df, 14)\n   \n   print(df[[\"close\", \"ema20\", \"ema50\", \"rsi\", \"atr\"]].tail(5))\n   3 — Download All Symbols to CSV     import csv\n   import os\n   import time\n   \n   \n   def download_all(client: ForexClient, resolution: str = \"60\", countback: int = 500):\n       \"\"\"\n       Download OHLCV history for every accessible symbol and save to individual CSV files.\n       \"\"\"\n       os.makedirs(\"forex_data\", exist_ok=True)\n   \n       symbols = client.symbols(limit=1000)\n       print(f\"Downloading {len(symbols)} symbols at {resolution} resolution...\")\n   \n       for i, sym in enumerate(symbols, 1):\n           ticker = sym[\"symbol\"]\n           print(f\"[{i:3}\u002F{len(symbols)}] {ticker}...\", end=\" \", flush=True)\n   \n           data = client.history(ticker, resolution, countback)\n   \n           if data.get(\"s\") != \"ok\":\n               print(\"no data\")\n               continue\n   \n           fname = f\"forex_data\u002F{ticker.replace(':', '_')}_{resolution}.csv\"\n           with open(fname, \"w\", newline=\"\") as f:\n               writer = csv.writer(f)\n               writer.writerow([\"time\", \"open\", \"high\", \"low\", \"close\", \"volume\"])\n               for row in zip(data[\"t\"], data[\"o\"], data[\"h\"], data[\"l\"], data[\"c\"], data[\"v\"]):\n                   writer.writerow(row)\n   \n           print(f\"{len(data['t'])} bars saved\")\n           time.sleep(0.05)  # ~20 req\u002Fs, well within rate limits\n   \n       print(\"\\nDone.\")\n   \n   \n   client = ForexClient(\"fxd_your_key_here\")\n   download_all(client, resolution=\"60\", countback=500)\n   4 — Live WebSocket Stream     # live_stream.py\n   import asyncio\n   import json\n   import websockets\n   from datetime import datetime, timezone\n   \n   API_KEY = \"fxd_your_key_here\"\n   URI     = f\"wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key={API_KEY}\"\n   \n   SYMBOLS = [\n       \"WMMARKETS:ZERO:EURUSD\",\n       \"WMMARKETS:ZERO:GBPUSD\",\n       \"WMMARKETS:ZERO:XAUUSD\",\n       \"WMMARKETS:ZERO:BTCUSD\",\n   ]\n   \n   \n   async def on_bar(symbol: str, bar: dict):\n       \"\"\"Process a newly closed M1 bar.\"\"\"\n       t   = datetime.fromtimestamp(bar[\"time\"], tz=timezone.utc)\n       sym = symbol.split(\":\")[-1]\n       print(\n           f\"[{t.strftime('%H:%M')}] {sym:\u003C8} \"\n           f\"O={bar['open']:.5f} H={bar['high']:.5f} \"\n           f\"L={bar['low']:.5f} C={bar['close']:.5f}\"\n       )\n   \n   \n   async def stream():\n       reconnect_delay = 5\n   \n       while True:\n           try:\n               async with websockets.connect(URI, ping_interval=30) as ws:\n                   print(\"Connected ✓\")\n                   reconnect_delay = 5\n   \n                   await ws.send(json.dumps({\n                       \"action\":  \"subscribe\",\n                       \"symbols\": SYMBOLS,\n                       \"type\":    \"bar\"\n                   }))\n   \n                   async for raw in ws:\n                       msg = json.loads(raw)\n   \n                       if msg[\"type\"] == \"connected\":\n                           print(f\"Session: {msg['session_id']} | Max symbols: {msg['limits']['max_symbols']}\")\n   \n                       elif msg[\"type\"] == \"subscribed\":\n                           print(f\"Subscribed: {len(msg['symbols'])} symbols\")\n                           if msg.get(\"rejected\"):\n                               print(f\"Rejected: {msg['rejected']}\")\n   \n                       elif msg[\"type\"] == \"bar\":\n                           await on_bar(msg[\"symbol\"], msg[\"bar\"])\n   \n                       elif msg[\"type\"] == \"error\":\n                           print(f\"Error: {msg['code']}\")\n                           if msg[\"code\"] in (\"KEY_INVALID\", \"KEY_INACTIVE\", \"KEY_EXPIRED\"):\n                               return  # fatal auth error — stop\n   \n           except websockets.exceptions.ConnectionClosed as e:\n               print(f\"Disconnected ({e}) — reconnecting in {reconnect_delay}s\")\n               await asyncio.sleep(reconnect_delay)\n               reconnect_delay = min(reconnect_delay * 2, 60)\n   \n           except Exception as e:\n               print(f\"Unexpected error: {e}\")\n               await asyncio.sleep(reconnect_delay)\n   \n   \n   if __name__ == \"__main__\":\n       asyncio.run(stream())\n   5 — Latest Price Snapshot  Fetch the most recent close price for every symbol:     def latest_prices(client: ForexClient) -> dict:\n       \"\"\"Return the latest close price for all accessible symbols.\"\"\"\n       symbols = client.symbols(limit=1000)\n       prices  = {}\n   \n       for sym in symbols:\n           ticker = sym[\"symbol\"]\n           data   = client.history(ticker, \"1\", countback=1)\n           if data.get(\"s\") == \"ok\" and data[\"c\"]:\n               prices[ticker] = {\n                   \"display\": sym[\"display_symbol\"],\n                   \"close\":   data[\"c\"][-1],\n                   \"digits\":  sym[\"digits\"]\n               }\n   \n       return prices\n   \n   \n   client = ForexClient(\"fxd_your_key_here\")\n   prices = latest_prices(client)\n   \n   for ticker, info in list(prices.items())[:10]:\n       fmt = f\"{{:.{info['digits']}f}}\"\n       print(f\"{info['display']:\u003C12} = {fmt.format(info['close'])}\")\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":190,"path":191,"dir":178,"title":192,"description":193,"keywords":194,"body":198},"content:4.examples:1.javascript.md","\u002Fexamples\u002Fjavascript","JavaScript Examples","Complete JavaScript\u002FNode.js examples — base client, historical data, TradingView DataFeed, and WebSocket",[183,195,196,197],"2 — Fetch Historical Data","3 — TradingView Charting Library DataFeed","4 — Node.js WebSocket","  JavaScript Examples  These examples work in both   browser  (native   fetch  and   WebSocket ) and   Node.js 18+  without any polyfills.   1 — Base Client     \u002F\u002F ForexClient.js\n   export   class   ForexClient   {\n     static   BASE_URL   =   \"https:\u002F\u002Fapi.pipdata.net\"  ;\n   \n     constructor  (  apiKey  ) {\n       this  .apiKey   =   apiKey;\n     }\n   \n     get   #  headers  () {\n       return   {   \"X-API-Key\"  :   this  .apiKey };\n     }\n   \n     async   #  get  (  path  ,   params   =   {}) {\n       const   url   =   new   URL  (  `${  ForexClient  .  BASE_URL  }${  path  }`  );\n       Object.  entries  (params).  forEach  (([  k  ,   v  ])   =>   url.searchParams.  set  (k,   String  (v)));\n   \n       const   resp   =   await   fetch  (url, { headers:   this  .#headers });\n       if   (  !  resp.ok) {\n         const   err   =   await   resp.  json  ().  catch  (()   =>   ({}));\n         throw   new   Error  (  `${  err  ?.  error  ?.  code   ??   resp  .  status  }: ${  err  ?.  error  ?.  message   ??   resp  .  statusText  }`  );\n       }\n       return   resp.  json  ();\n     }\n   \n     \u002F** List accessible symbols. *\u002F\n     symbols  (  params   =   {}) {\n       return   this  .  #get  (  \"\u002Fv1\u002Fsymbols\"  , params);\n     }\n   \n     \u002F** Fetch historical OHLCV bars. *\u002F\n     history  (  symbol  ,   resolution  ,   countback   =   500  ) {\n       return   this  .  #get  (  \"\u002Fv1\u002Fhistory\"  , { symbol, resolution, countback });\n     }\n   \n     \u002F** Fetch OHLCV bars between two Unix timestamps. *\u002F\n     historyRange  (  symbol  ,   resolution  ,   from  ,   to  ) {\n       return   this  .  #get  (  \"\u002Fv1\u002Fhistory\"  , { symbol, resolution, from, to });\n     }\n   \n     \u002F** Get available data date ranges. *\u002F\n     coverage  (  symbol   =   null  ) {\n       return   this  .  #get  (  \"\u002Fv1\u002Fcoverage\"  , symbol   ?   { symbol }   :   {});\n     }\n   \n     \u002F** Check feed health. *\u002F\n     status  () {\n       return   this  .  #get  (  \"\u002Fv1\u002Fstatus\"  );\n     }\n   \n     \u002F** Get API key usage statistics. *\u002F\n     usage  (  days   =   7  ) {\n       return   this  .  #get  (  \"\u002Fv1\u002Fusage\"  , { days });\n     }\n   }\n   2 — Fetch Historical Data     import   { ForexClient }   from   \".\u002FForexClient.js\"  ;\n   \n   const   client   =   new   ForexClient  (  \"fxd_your_key_here\"  );\n   \n   async   function   main  () {\n     \u002F\u002F Service health check\n     const   st   =   await   client.  status  ();\n     console.  log  (  \"Service status:\"  , st.status);\n   \n     \u002F\u002F Fetch last 200 H1 bars for EUR\u002FUSD\n     const   data   =   await   client.  history  (  \"WMMARKETS:ZERO:EURUSD\"  ,   \"60\"  ,   200  );\n   \n     if   (data.s   ===   \"ok\"  ) {\n       \u002F\u002F Zip into bar objects\n       const   bars   =   data.t.  map  ((  t  ,   i  )   =>   ({\n         time:     new   Date  (t   *   1000  ).  toISOString  (),\n         open:   data.o[i],\n         high:   data.h[i],\n         low:    data.l[i],\n         close:  data.c[i],\n         volume: data.v[i],\n       }));\n   \n       const   last   =   bars[bars.  length   -   1  ];\n       console.  log  (  `${  data  .  meta  .  bar_count  } bars received`  );\n       console.  log  (  `Latest bar: ${  last  .  time  }  close=${  last  .  close  .  toFixed  (  5  )  }`  );\n     }\n   }\n   \n   main  ().  catch  (console.error);\n   3 — TradingView Charting Library DataFeed  Drop-in DataFeed implementation for the TradingView Charting Library:     \u002F\u002F datafeed.js\n   const   API_KEY    =   \"fxd_your_key_here\"  ;\n   const   BASE_URL   =   \"https:\u002F\u002Fapi.pipdata.net\"  ;\n   const   HEADERS    =   {   \"X-API-Key\"  :   API_KEY   };\n   \n   const   ForexDatafeed   =   {\n     \u002F\u002F ── onReady ─────────────────────────────────────────────────────────────\n     onReady  (  callback  ) {\n       fetch  (  `${  BASE_URL  }\u002Fv1\u002Fconfig`  , { headers:   HEADERS   })\n         .  then  (  r   =>   r.  json  ())\n         .  then  (  cfg   =>   setTimeout  (()   =>   callback  (cfg),   0  ));\n     },\n   \n     \u002F\u002F ── searchSymbols ────────────────────────────────────────────────────────\n     searchSymbols  (  query  ,   exchange  ,   type  ,   callback  ) {\n       fetch  (  `${  BASE_URL  }\u002Fv1\u002Fsymbols?search=${  encodeURIComponent  (  query  )  }&limit=30`  , { headers:   HEADERS   })\n         .  then  (  r   =>   r.  json  ())\n         .  then  (  data   =>   callback  (\n           (data.symbols   ??   []).  map  (  s   =>   ({\n             symbol:      s.symbol,\n             full_name:   s.symbol,\n             description: s.description,\n             exchange:    s.exchange,\n             type:        s.type,\n           }))\n         ));\n     },\n   \n     \u002F\u002F ── resolveSymbol ────────────────────────────────────────────────────────\n     resolveSymbol  (  symbolName  ,   onResolve  ,   onError  ) {\n       fetch  (  `${  BASE_URL  }\u002Fv1\u002Fsymbols?symbol=${  encodeURIComponent  (  symbolName  )  }`  , { headers:   HEADERS   })\n         .  then  (  r   =>   r.  json  ())\n         .  then  (  s   =>   onResolve  (s))\n         .  catch  (  err   =>   onError  (err.message));\n     },\n   \n     \u002F\u002F ── getBars ──────────────────────────────────────────────────────────────\n     getBars  (  symbolInfo  ,   resolution  ,   periodParams  ,   onResult  ,   onError  ) {\n       const   {   from  ,   to  ,   countBack   }   =   periodParams;\n       const   params   =   new   URLSearchParams  ({ symbol: symbolInfo.name, resolution });\n   \n       if   (countBack) {\n         params.  set  (  \"countback\"  , countBack);\n         if   (to) params.  set  (  \"to\"  , to);\n       }   else   {\n         params.  set  (  \"from\"  , from);\n         params.  set  (  \"to\"  ,   to);\n       }\n   \n       fetch  (  `${  BASE_URL  }\u002Fv1\u002Fhistory?${  params  }`  , { headers:   HEADERS   })\n         .  then  (  r   =>   r.  json  ())\n         .  then  (  data   =>   {\n           if   (data.s   ===   \"no_data\"  ) {   onResult  ([], { noData:   true   });   return  ; }\n           if   (data.s   !==   \"ok\"  )      {   onError  (data.errmsg   ||   \"Unknown error\"  );   return  ; }\n   \n           const   bars   =   data.t.  map  ((  t  ,   i  )   =>   ({\n             time:   t   *   1000  ,    \u002F\u002F TradingView expects milliseconds\n             open:   data.o[i],\n             high:   data.h[i],\n             low:    data.l[i],\n             close:  data.c[i],\n             volume: data.v[i],\n           }));\n           onResult  (bars, { noData: bars.  length   ===   0   });\n         })\n         .  catch  (  err   =>   onError  (err.message));\n     },\n   \n     \u002F\u002F ── subscribeBars \u002F unsubscribeBars ──────────────────────────────────────\n     _ws:            null  ,\n     _subscribers: {},\n   \n     subscribeBars  (  symbolInfo  ,   resolution  ,   onTick  ,   listenerGuid  ) {\n       this  ._subscribers[listenerGuid]   =   { symbol: symbolInfo.name, onTick };\n       this  .  _ensureWS  ();\n   \n       if   (  this  ._ws.readyState   ===   WebSocket.  OPEN  ) {\n         this  .  _sendSubscribe  ([symbolInfo.name]);\n       }\n     },\n   \n     unsubscribeBars  (  listenerGuid  ) {\n       delete   this  ._subscribers[listenerGuid];\n     },\n   \n     _ensureWS  () {\n       if   (  this  ._ws   &&   this  ._ws.readyState   !==   WebSocket.  CLOSED  )   return  ;\n   \n       this  ._ws   =   new   WebSocket  (  `wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key=${  API_KEY  }`  );\n   \n       this  ._ws.  onopen   =   ()   =>   {\n         const   symbols   =   [  ...new   Set  (Object.  values  (  this  ._subscribers).  map  (  s   =>   s.symbol))];\n         if   (symbols.  length  )   this  .  _sendSubscribe  (symbols);\n         setInterval  (()   =>   {\n           if   (  this  ._ws.readyState   ===   WebSocket.  OPEN  )\n             this  ._ws.  send  (  JSON  .  stringify  ({ action:   \"ping\"   }));\n         },   30_000  );\n       };\n   \n       this  ._ws.  onmessage   =   (  ev  )   =>   {\n         const   msg   =   JSON  .  parse  (ev.data);\n         if   (msg.type   !==   \"bar\"  )   return  ;\n         for   (  const   sub   of   Object.  values  (  this  ._subscribers)) {\n           if   (sub.symbol   ===   msg.symbol) {\n             sub.  onTick  ({\n               time:   msg.bar.time   *   1000  ,\n               open:   msg.bar.open,\n               high:   msg.bar.high,\n               low:    msg.bar.low,\n               close:  msg.bar.close,\n               volume: msg.bar.volume,\n             });\n           }\n         }\n       };\n     },\n   \n     _sendSubscribe  (  symbols  ) {\n       this  ._ws.  send  (  JSON  .  stringify  ({ action:   \"subscribe\"  , symbols, type:   \"bar\"   }));\n     },\n   \n     \u002F\u002F ── getServerTime ────────────────────────────────────────────────────────\n     \u002F\u002F Bar timestamps are broker UTC+3. TradingView must be told the same \"now\"\n     \u002F\u002F or it treats bars after real-UTC-now as future data and hides them.\n     getServerTime  (  callback  ) {\n       callback  (Math.  floor  (Date.  now  ()   \u002F   1000  )   +   10800  );\n     },\n   };\n   \n   \u002F\u002F Initialize TradingView widget\n   new   TradingView.  widget  ({\n     container_id:   \"tv_chart_container\"  ,\n     datafeed:     ForexDatafeed,\n     library_path:   \"\u002Fcharting_library\u002F\"  ,\n     symbol:         \"WMMARKETS:ZERO:EURUSD\"  ,\n     interval:       \"60\"  ,\n     locale:         \"en\"  ,\n     theme:          \"dark\"  ,\n   });\n   4 — Node.js WebSocket     \u002F\u002F For Node.js — install: npm install ws\n   const   WebSocket   =   require  (  \"ws\"  );\n   \n   const   API_KEY   =   \"fxd_your_key_here\"  ;\n   const   ws   =   new   WebSocket  (  `wss:\u002F\u002Fapi.pipdata.net\u002Fv1\u002Fstream?api_key=${  API_KEY  }`  );\n   \n   let   pingTimer;\n   \n   ws.  on  (  \"open\"  , ()   =>   {\n     console.  log  (  \"Connected\"  );\n   \n     ws.  send  (  JSON  .  stringify  ({\n       action:    \"subscribe\"  ,\n       symbols: [  \"WMMARKETS:ZERO:EURUSD\"  ,   \"WMMARKETS:ZERO:XAUUSD\"  ,   \"WMMARKETS:ZERO:BTCUSD\"  ],\n       type:      \"bar\"\n     }));\n   \n     pingTimer   =   setInterval  (()   =>   {\n       if   (ws.readyState   ===   WebSocket.  OPEN  )\n         ws.  send  (  JSON  .  stringify  ({ action:   \"ping\"   }));\n     },   30_000  );\n   });\n   \n   ws.  on  (  \"message\"  , (  data  )   =>   {\n     const   msg   =   JSON  .  parse  (data);\n   \n     if   (msg.type   ===   \"subscribed\"  ) {\n       console.  log  (  `Subscribed to ${  msg  .  symbols  .  length  } symbols`  );\n     }\n   \n     if   (msg.type   ===   \"bar\"  ) {\n       const   sym    =   msg.symbol.  split  (  \":\"  )[  2  ];\n       const   time   =   new   Date  (msg.bar.time   *   1000  ).  toISOString  ().  slice  (  11  ,   16  );\n       console.  log  (  `[${  time  }] ${  sym  .  padEnd  (  8  )  } close=${  msg  .  bar  .  close  }`  );\n     }\n   });\n   \n   ws.  on  (  \"close\"  , ()   =>   {\n     clearInterval  (pingTimer);\n     console.  log  (  \"Disconnected — restarting in 5s\"  );\n     setTimeout  (()   =>   process.  exit  (  1  ),   5000  );\n   });\n   \n   ws.  on  (  \"error\"  , (  err  )   =>   {\n     console.  error  (  \"WebSocket error:\"  , err.message);\n   });\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":200,"path":201,"dir":178,"title":202,"description":203,"keywords":204,"body":215},"content:4.examples:2.curl.md","\u002Fexamples\u002Fcurl","cURL Examples","Ready-to-run curl commands for every API endpoint — copy, paste, and test",[205,206,207,208,209,210,211,212,213,214],"Set Variables","Config","Symbols","Historical Data","Data Coverage","Service Status","Usage Statistics","Alternative Auth Methods","Inspect Rate-Limit Headers","Shell Script — Latest Prices","  cURL Examples  Ready-to-run commands for every endpoint. Replace   fxd_your_key_here  with your actual API key.  Set Variables     export   API_KEY  =  \"fxd_your_key_here\"\n   export   BASE  =  \"https:\u002F\u002Fapi.pipdata.net\"\n   Config     # No API key required\n   curl   -s   \"  $BASE  \u002Fv1\u002Fconfig\"   |   python3   -m   json.tool\n   Symbols     # List first 10 symbols\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fsymbols?limit=10\"   |   python3   -m   json.tool\n   \n   # Search for symbols containing \"USD\"\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fsymbols?search=USD\"   |   python3   -m   json.tool\n   \n   # Search for crypto symbols\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fsymbols?search=BTC\"   |   python3   -m   json.tool\n   \n   # Get full metadata for a specific symbol\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fsymbols?symbol=WMMARKETS:ZERO:EURUSD\"   |   python3   -m   json.tool\n   Historical Data     # Last 100 H1 bars for EUR\u002FUSD\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:EURUSD&resolution=60&countback=100\"   \\\n     |   python3   -m   json.tool\n   \n   # Last 50 M1 bars\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:EURUSD&resolution=1&countback=50\"   \\\n     |   python3   -m   json.tool\n   \n   # Last 365 Daily bars for Gold\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:XAUUSD&resolution=1D&countback=365\"   \\\n     |   python3   -m   json.tool\n   \n   # H4 bars for Bitcoin\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:BTCUSD&resolution=240&countback=200\"   \\\n     |   python3   -m   json.tool\n   \n   # Specific time range (Unix timestamps)\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:EURUSD&resolution=60&from=1740960000&to=1749600000\"   \\\n     |   python3   -m   json.tool\n   \n   # Latest single M1 bar (current price)\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:BTCUSD&resolution=1&countback=1\"   \\\n     |   python3   -c   \"import sys,json; d=json.load(sys.stdin); print(f  \\\"  BTC\u002FUSD: {d['c'][-1]}  \\\"   if d['s']=='ok' else 'no data')\"\n   Data Coverage     # Check coverage for a single symbol\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fcoverage?symbol=WMMARKETS:ZERO:EURUSD\"   |   python3   -m   json.tool\n   \n   # Coverage for all accessible symbols\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fcoverage\"   |   python3   -m   json.tool\n   Service Status     # Full status response\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \"  $BASE  \u002Fv1\u002Fstatus\"   |   python3   -m   json.tool\n   \n   # Just the overall status\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \"  $BASE  \u002Fv1\u002Fstatus\"   \\\n     |   python3   -c   \"import sys,json; d=json.load(sys.stdin); print(d['status'])\"\n   Usage Statistics     # Last 7 days (default)\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fusage\"   |   python3   -m   json.tool\n   \n   # Last 30 days\n   curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fusage?days=30\"   |   python3   -m   json.tool\n   Alternative Auth Methods     # Authorization Bearer header\n   curl   -s   -H   \"Authorization: Bearer   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fsymbols?limit=5\"\n   \n   # Query parameter\n   curl   -s   \"  $BASE  \u002Fv1\u002Fsymbols?limit=5&api_key=  $API_KEY  \"\n   Inspect Rate-Limit Headers     curl   -sv   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fsymbols?limit=1\"   2>&1   \\\n     |   grep   -i   \"x-ratelimit\"\n  Example output:   \u003C X-RateLimit-Limit-Minute: 120\n\u003C X-RateLimit-Remaining-Minute: 119\n\u003C X-RateLimit-Reset-Minute: 1749600060\n\u003C X-RateLimit-Limit-Day: 50000\n\u003C X-RateLimit-Remaining-Day: 49999\n   Shell Script — Latest Prices     #!\u002Fbin\u002Fbash\n   # latest_prices.sh — print the most recent close price for a list of symbols\n   \n   API_KEY  =  \"fxd_your_key_here\"\n   BASE  =  \"https:\u002F\u002Fapi.pipdata.net\"\n   \n   SYMBOLS  =  (  \"EURUSD\"   \"GBPUSD\"   \"USDJPY\"   \"XAUUSD\"   \"BTCUSD\"   \"US30\"  )\n   \n   for   sym   in   \"${  SYMBOLS  [  @  ]}\"  ;   do\n       FULL  =  \"WMMARKETS:ZERO:  $sym  \"\n       CLOSE  =  $(  curl   -s   -H   \"X-API-Key:   $API_KEY  \"   \\\n           \"  $BASE  \u002Fv1\u002Fhistory?symbol=  $FULL  &resolution=1&countback=1\"   \\\n           |   python3   -c   \"import sys,json; d=json.load(sys.stdin); print(d['c'][-1] if d['s']=='ok' else 'N\u002FA')\"  )\n       printf   \"%-10s = %s\\n\"   \"  $sym  \"   \"  $CLOSE  \"\n   done\n  Example output:   EURUSD     = 1.08228\nGBPUSD     = 1.27341\nUSDJPY     = 154.823\nXAUUSD     = 2315.42\nBTCUSD     = 67450.30\nUS30       = 38921.00\n  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":217,"path":218,"dir":219,"title":220,"description":219,"keywords":221,"body":222},"content:index.md","\u002F","","Forex Data API",[21],"    Forex Market Data API   Programmatic access to real-time prices and complete OHLCV history — 230+ symbols across Forex, Crypto, Indices, and CFD Stocks, sourced directly from live MetaTrader 5 broker feeds.     ✅   230+ Symbols  — Forex majors\u002Fminors\u002Fexotics, Crypto, Global indices, CFD stocks  ✅   Full History M1 → Monthly  — Up to 2 years of OHLCV candle data  ✅   Real-time WebSocket  — Live M1 bar push with sub-100ms latency  ✅   TradingView Compatible  — Drop-in DataFeed, no data transformation needed  ✅   Secure API Keys  — Per-feed access control, rate limits, IP allowlists    Why this API?   OHLCV candles from M1 through Monthly, sourced directly from real broker terminals. No third-party data aggregators.   Persistent WebSocket connection with live M1 bar updates. True push — no polling required.   Forex majors, minors, and exotics; Bitcoin, Ethereum, and more crypto; Dow Jones, S&P 500, DAX; plus 100+ CFD stocks.   API keys in   fxd_*  format with per-key feed permissions, RPM\u002FRPD limits, and optional IP allowlists.   Full usage tracking — daily request counts, per-endpoint breakdowns, rate-limit hits, and WebSocket session stats.   Response format is fully compatible with the TradingView Charting Library UDF protocol — connect directly without adapters.   Quick Start     # Set your key once\n   export   API_KEY  =  \"fxd_your_key_here\"\n   export   BASE  =  \"https:\u002F\u002Fapi.pipdata.net\"\n   \n   # List available symbols\n   curl   -H   \"X-API-Key:   $API_KEY  \"   \"  $BASE  \u002Fv1\u002Fsymbols?limit=5\"\n   \n   # Fetch the last 1000 H1 candles for EUR\u002FUSD\n   curl   -H   \"X-API-Key:   $API_KEY  \"   \\\n     \"  $BASE  \u002Fv1\u002Fhistory?symbol=WMMARKETS:ZERO:EURUSD&resolution=60&countback=1000\"\n    Don't have an API key yet?  Contact our team to get one. Keys are shown only once at creation — store it securely.  html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",1791227735867]