Broker API HubMenu

IG API quickstart: key to first call

Make your first IG API call: demo host, session login and GET /accounts in Python, JavaScript and curl, using placeholder credentials only.

Independent and unofficialChecked Code not yet run on a demo account

Risk warning: CFDs and spread bets are complex instruments and come with a high risk of losing money rapidly due to leverage. You can lose money on these products, and each provider publishes its own loss figure under FCA rules. You should consider whether you understand how they work and whether you can afford to take the high risk of losing your money. Automated and API trading can amplify losses through bugs, connectivity failures and runaway orders.

Demo accounts only. Credentials are read from environment variables on your own machine. No field on this site asks for a key, a username or a password.

Steps

  1. What IG publishes about keys and demo accounts

    This is what IG publishes on the page linked below, rather than a step we are asking you to take. IG says API keys are issued only to existing live account holders, from the API keys page in your account settings, and that demo API access needs a separate demo account applied for with the same email address as the live account, with its own key. Demo and live credentials cannot be mixed.

    ig.comchecked

  2. Point at demo

    Use https://demo-api.ig.com/gateway/deal with your demo username, password and demo key (the demo host is on IG's own API page). A demo key sent to the live host, or the reverse, fails authentication: the same facts are marked Verified under Demo vs live on the IG page.

    ig.comchecked

  3. Log in

    POST /session with VERSION: 2, sending your login identifier and password. IG-Group's own Java sample makes this call with VERSION 2, and IG's REST trading API guide says the response carries a CST header and an X-SECURITY-TOKEN header, both to be sent on every later request. Every tab reads both headers straight off the login response into variables, so you never copy a token by hand: Python and JavaScript read them from the response object, and curl writes the headers to a file with -D, pulls the two values out with grep and awk, then deletes the file. No sample prints a token, so a live one never lands in a terminal log or a screenshot.

    Python
    # Set IG_API_KEY, IG_USERNAME, IG_PASSWORD as environment variables first. Never paste real values into code or into this site.
    import os
    import requests
    
    BASE_URL = "https://demo-api.ig.com/gateway/deal"
    API_KEY = os.environ["IG_API_KEY"]
    
    resp = requests.post(
        BASE_URL + "/session",
        headers={
            "X-IG-API-KEY": API_KEY,
            "VERSION": "2",
            "Accept": "application/json; charset=UTF-8",
            "Content-Type": "application/json; charset=UTF-8",
        },
        json={"identifier": os.environ["IG_USERNAME"], "password": os.environ["IG_PASSWORD"]},
        timeout=10,
    )
    resp.raise_for_status()
    
    # The two session tokens arrive as response headers. Read them straight into variables:
    # nothing to copy by hand, and neither value is printed.
    CST = resp.headers.get("CST")
    SECURITY_TOKEN = resp.headers.get("X-SECURITY-TOKEN")
    if not (CST and SECURITY_TOKEN):
        raise SystemExit("Login worked but a session header is missing")
    print("Session tokens captured: CST and X-SECURITY-TOKEN")
    
    JavaScript
    // Set IG_API_KEY, IG_USERNAME, IG_PASSWORD as environment variables first. Never paste real values into code or into this site.
    // Node 18 or later. Save as a .mjs file so top level await works.
    const BASE_URL = "https://demo-api.ig.com/gateway/deal";
    const API_KEY = process.env.IG_API_KEY;
    
    const res = await fetch(`${BASE_URL}/session`, {
      method: "POST",
      headers: {
        "X-IG-API-KEY": API_KEY,
        "VERSION": "2",
        "Accept": "application/json; charset=UTF-8",
        "Content-Type": "application/json; charset=UTF-8"
      },
      body: JSON.stringify({ identifier: process.env.IG_USERNAME, password: process.env.IG_PASSWORD })
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    
    // The two session tokens arrive as response headers. Read them straight into variables:
    // nothing to copy by hand, and neither value is printed.
    const cst = res.headers.get("CST");
    const securityToken = res.headers.get("X-SECURITY-TOKEN");
    if (!cst || !securityToken) throw new Error("Login worked but a session header is missing");
    console.log("Session tokens captured: CST and X-SECURITY-TOKEN");
    
    curl
    # Set IG_API_KEY, IG_USERNAME, IG_PASSWORD as environment variables first. Never paste real values into code or into this site.
    # Needs curl and jq: jq builds the login body, so a quote in your password cannot break the JSON.
    BASE_URL="https://demo-api.ig.com/gateway/deal"
    
    # 1. Log in. -D writes the response headers to a file; the body is discarded.
    curl -s -D ig_headers.txt -o /dev/null -w "Login: HTTP %{http_code}\n" \
      -X POST "$BASE_URL/session" \
      -H "X-IG-API-KEY: $IG_API_KEY" \
      -H "VERSION: 2" \
      -H "Accept: application/json; charset=UTF-8" \
      -H "Content-Type: application/json; charset=UTF-8" \
      -d "$(jq -n --arg id "$IG_USERNAME" --arg pw "$IG_PASSWORD" '{identifier: $id, password: $pw}')"
    
    # 2. Pull both tokens out of the headers file into shell variables, then delete the file.
    IG_CST=$(grep -i '^CST:' ig_headers.txt | awk '{print $2}' | tr -d '\r')
    IG_SECURITY_TOKEN=$(grep -i '^X-SECURITY-TOKEN:' ig_headers.txt | awk '{print $2}' | tr -d '\r')
    rm -f ig_headers.txt
    
    if [ -n "$IG_CST" ] && [ -n "$IG_SECURITY_TOKEN" ]; then
      echo "Session tokens captured: CST and X-SECURITY-TOKEN"
    else
      echo "No session tokens in the response headers: check the key, the host and the login details"
    fi
    

    raw.githubusercontent.comchecked

  4. First real call

    GET /accounts with VERSION: 1 and the three auth headers. Each tab logs in first and hands the CST and X-SECURITY-TOKEN values it captured straight to this call, in memory or in shell variables, so there is nothing to copy and no token to set by hand. If you see your balance, you are in.

    Python
    # Logs in and makes the first real call in one run, so the tokens pass from one call to the next in memory.
    # Set IG_API_KEY, IG_USERNAME, IG_PASSWORD as environment variables first. Never paste real values into code or into this site.
    import os
    import requests
    
    BASE_URL = "https://demo-api.ig.com/gateway/deal"
    API_KEY = os.environ["IG_API_KEY"]
    
    resp = requests.post(
        BASE_URL + "/session",
        headers={
            "X-IG-API-KEY": API_KEY,
            "VERSION": "2",
            "Accept": "application/json; charset=UTF-8",
            "Content-Type": "application/json; charset=UTF-8",
        },
        json={"identifier": os.environ["IG_USERNAME"], "password": os.environ["IG_PASSWORD"]},
        timeout=10,
    )
    resp.raise_for_status()
    
    # The two session tokens arrive as response headers. Read them straight into variables:
    # nothing to copy by hand, and neither value is printed.
    CST = resp.headers.get("CST")
    SECURITY_TOKEN = resp.headers.get("X-SECURITY-TOKEN")
    if not (CST and SECURITY_TOKEN):
        raise SystemExit("Login worked but a session header is missing")
    
    # First real call: GET /accounts with VERSION 1 and the three auth headers.
    accounts = requests.get(
        BASE_URL + "/accounts",
        headers={
            "X-IG-API-KEY": API_KEY,
            "CST": CST,
            "X-SECURITY-TOKEN": SECURITY_TOKEN,
            "VERSION": "1",
            "Accept": "application/json; charset=UTF-8",
        },
        timeout=10,
    )
    accounts.raise_for_status()
    print(accounts.json())
    
    JavaScript
    // Logs in and makes the first real call in one run, so the tokens pass from one call to the next in memory.
    // Set IG_API_KEY, IG_USERNAME, IG_PASSWORD as environment variables first. Never paste real values into code or into this site.
    // Node 18 or later. Save as a .mjs file so top level await works.
    const BASE_URL = "https://demo-api.ig.com/gateway/deal";
    const API_KEY = process.env.IG_API_KEY;
    
    const res = await fetch(`${BASE_URL}/session`, {
      method: "POST",
      headers: {
        "X-IG-API-KEY": API_KEY,
        "VERSION": "2",
        "Accept": "application/json; charset=UTF-8",
        "Content-Type": "application/json; charset=UTF-8"
      },
      body: JSON.stringify({ identifier: process.env.IG_USERNAME, password: process.env.IG_PASSWORD })
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    
    // The two session tokens arrive as response headers. Read them straight into variables:
    // nothing to copy by hand, and neither value is printed.
    const cst = res.headers.get("CST");
    const securityToken = res.headers.get("X-SECURITY-TOKEN");
    if (!cst || !securityToken) throw new Error("Login worked but a session header is missing");
    
    // First real call: GET /accounts with VERSION 1 and the three auth headers.
    const accounts = await fetch(`${BASE_URL}/accounts`, {
      method: "GET",
      headers: {
        "X-IG-API-KEY": API_KEY,
        "CST": cst,
        "X-SECURITY-TOKEN": securityToken,
        "VERSION": "1",
        "Accept": "application/json; charset=UTF-8"
      }
    });
    if (!accounts.ok) throw new Error(`HTTP ${accounts.status}`);
    console.log(await accounts.json());
    
    curl
    # Logs in and makes the first real call in one run, so the tokens pass from one call to the next in shell variables.
    # Set IG_API_KEY, IG_USERNAME, IG_PASSWORD as environment variables first. Never paste real values into code or into this site.
    # Needs curl and jq: jq builds the login body, so a quote in your password cannot break the JSON.
    BASE_URL="https://demo-api.ig.com/gateway/deal"
    
    # 1. Log in. -D writes the response headers to a file; the body is discarded.
    curl -s -D ig_headers.txt -o /dev/null -w "Login: HTTP %{http_code}\n" \
      -X POST "$BASE_URL/session" \
      -H "X-IG-API-KEY: $IG_API_KEY" \
      -H "VERSION: 2" \
      -H "Accept: application/json; charset=UTF-8" \
      -H "Content-Type: application/json; charset=UTF-8" \
      -d "$(jq -n --arg id "$IG_USERNAME" --arg pw "$IG_PASSWORD" '{identifier: $id, password: $pw}')"
    
    # 2. Pull both tokens out of the headers file into shell variables, then delete the file.
    IG_CST=$(grep -i '^CST:' ig_headers.txt | awk '{print $2}' | tr -d '\r')
    IG_SECURITY_TOKEN=$(grep -i '^X-SECURITY-TOKEN:' ig_headers.txt | awk '{print $2}' | tr -d '\r')
    rm -f ig_headers.txt
    
    if [ -z "$IG_CST" ] || [ -z "$IG_SECURITY_TOKEN" ]; then
      echo "No session tokens in the response headers: check the key, the host and the login details"
    else
      # 3. First real call: GET /accounts with VERSION 1 and the three auth headers.
      curl -s "$BASE_URL/accounts" \
        -H "X-IG-API-KEY: $IG_API_KEY" \
        -H "CST: $IG_CST" \
        -H "X-SECURITY-TOKEN: $IG_SECURITY_TOKEN" \
        -H "VERSION: 1" \
        -H "Accept: application/json; charset=UTF-8"
      echo
    fi
    

    Unverifiedgithub.com

  5. Or skip the plumbing

    pip install trading-ig, set acc_type to DEMO and call IGService(...).create_session(). Community maintained, not IG's.

    trading-ig.readthedocs.iochecked

  6. Run the complete script

    One Python file that does all of the above on the demo host: reads IG_API_KEY, IG_USERNAME and IG_PASSWORD from environment variables, logs in with POST /session (VERSION 2), keeps CST and X-SECURITY-TOKEN in memory only, reads GET /positions (VERSION 2) and writes your open positions to positions.csv. It spaces requests 2.5 seconds apart, under IG's 60 non-trading a minute per app and 30 a minute per account non-trading limits, and logs in again once if a call returns 401 with an invalid token. It refuses to run against any host but https://demo-api.ig.com/gateway/deal. Save it as ig_demo.py and run python3 ig_demo.py.

    Python
    #!/usr/bin/env python3
    """IG demo API: log in, read open positions, save them to positions.csv.
    
    Unofficial example from Broker API Hub, not an IG product. Demo host only.
    Before running, set these environment variables on your own machine:
      IG_API_KEY    your demo API key (demo needs its own key)
      IG_USERNAME   your IG demo username, not your email
      IG_PASSWORD   your IG demo password
    Never paste real values into this file or into any website.
    Needs Python 3.8 or later and requests (pip install requests).
    """
    import csv
    import os
    import sys
    import time
    
    import requests
    
    DEMO_HOST = "https://demo-api.ig.com/gateway/deal"
    BASE_URL = os.environ.get("IG_BASE_URL", DEMO_HOST).rstrip("/")
    # IG lists 60 non-trading requests a minute per app and 30 a minute per account, read on
    # labs.ig.com on 23 Sep 2026. IG says those limits cannot currently be increased.
    # One request every 2.5 seconds is at most 24 a minute, under both.
    MIN_GAP = 2.5
    TOKEN_ERRORS = ("client-token-invalid", "oauth-token-invalid")
    OUT_FILE = "positions.csv"
    
    if BASE_URL != DEMO_HOST:
        sys.exit("Refusing to run: this script only talks to the demo host " + DEMO_HOST)
    
    
    def env(name):
        value = os.environ.get(name)
        if not value:
            sys.exit("Set the " + name + " environment variable first.")
        return value
    
    
    # CST and X-SECURITY-TOKEN live in this object's memory only: never printed, never saved.
    http = requests.Session()
    http.headers.update({
        "X-IG-API-KEY": env("IG_API_KEY"),
        "Accept": "application/json; charset=UTF-8",
        "Content-Type": "application/json; charset=UTF-8",
    })
    last_call = 0.0
    
    
    def send(method, path, version, **kwargs):
        """One request, spaced at least MIN_GAP seconds after the previous one."""
        global last_call
        wait = MIN_GAP - (time.monotonic() - last_call)
        if wait > 0:
            time.sleep(wait)
        last_call = time.monotonic()
        return http.request(method, BASE_URL + path, headers={"VERSION": version}, timeout=15, **kwargs)
    
    
    def log_in():
        http.headers.pop("CST", None)
        http.headers.pop("X-SECURITY-TOKEN", None)
        resp = send("POST", "/session", "2",
                    json={"identifier": env("IG_USERNAME"), "password": env("IG_PASSWORD")})
        if resp.status_code != 200:
            sys.exit("Login failed with HTTP " + str(resp.status_code) + ": " + resp.text[:200]
                     + "\nCheck that the key, username and password are all for demo.")
        cst, token = resp.headers.get("CST"), resp.headers.get("X-SECURITY-TOKEN")
        if not cst or not token:
            sys.exit("Logged in, but CST or X-SECURITY-TOKEN was missing from the response headers.")
        http.headers.update({"CST": cst, "X-SECURITY-TOKEN": token})
        print("Logged in to demo. Session tokens are held in memory only.")
    
    
    def call(method, path, version):
        """Authenticated call. On HTTP 401 with an invalid token, log in again once and retry."""
        resp = send(method, path, version)
        if resp.status_code == 401 and any(e in resp.text for e in TOKEN_ERRORS):
            print("Session token rejected (it expires, and IG resets tokens at weekends). Logging in again once.")
            log_in()
            resp = send(method, path, version)
        if resp.status_code != 200:
            sys.exit("HTTP " + str(resp.status_code) + " from " + path + ": " + resp.text[:200])
        return resp.json()
    
    
    def main():
        log_in()
        rows = []
        for item in call("GET", "/positions", "2").get("positions", []):
            pos, mkt = item.get("position", {}), item.get("market", {})
            rows.append({
                "dealId": pos.get("dealId"),
                "epic": mkt.get("epic"),
                "instrumentName": mkt.get("instrumentName"),
                "direction": pos.get("direction"),
                "size": pos.get("size"),
                "level": pos.get("level"),
                "currency": pos.get("currency"),
                "stopLevel": pos.get("stopLevel"),
                "limitLevel": pos.get("limitLevel"),
                "createdDateUTC": pos.get("createdDateUTC"),
            })
        with open(OUT_FILE, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=["dealId", "epic", "instrumentName", "direction", "size",
                                                   "level", "currency", "stopLevel", "limitLevel", "createdDateUTC"])
            writer.writeheader()
            writer.writerows(rows)
        print("Open positions written to " + OUT_FILE + ": " + str(len(rows)))
    
    
    if __name__ == "__main__":
        try:
            main()
        except requests.RequestException as err:
            sys.exit("Network error: " + str(err))
    

    labs.ig.comchecked

  7. From first call to first order

    One Python file built from the IG endpoint records on this site, as the last step of this quickstart. POST /session (version 2) logs in with IG_API_KEY, IG_USERNAME and IG_PASSWORD and keeps CST and X-SECURITY-TOKEN in memory. GET /positions (version 2) reads your open positions. POST /positions/otc (version 2) places one small demo order with every field the record marks required (epic, size, expiry, currencyCode, forceOpen and guaranteedStop) and the fields the vendor's page marks neither required nor optional (direction and orderType), each tagged in a comment. GET /confirms/{dealReference} (version 1) reads the confirmation or status. It sends at most one request every 2 seconds, which stays under each request limit in the IG limits table below. It refuses to run against any host but https://demo-api.ig.com/gateway/deal, and it never prints a token. Dry run is the default: save it as ig_first_order.py, run python ig_first_order.py, and it prints each request it would send with every secret shown as the name of its variable, reading no credential at all. Run python ig_first_order.py send to send them to the demo host. The script has not been run against IG.

    Python
    #!/usr/bin/env python3
    """IG demo API: from first call to first order.
    
    Unofficial example from Broker API Hub, not published by IG. Written from the IG endpoint records on
    openbrokerapi.com and not run against IG: the quickstart page says so. Demo host only.
    
    What it does, in order:
      1. POST /session (version 2): logs in and keeps CST and X-SECURITY-TOKEN in memory
      2. GET /positions (version 2): reads your open positions
      3. POST /positions/otc (version 2): places one small demo order with every field the record marks required
      4. GET /confirms/{dealReference} (version 1): reads the confirmation or status of that order
    
    Dry run is the default: nothing is sent, no credential is read, and each request is printed as it
    would go, with every secret shown as the name of the variable that holds it. Run it with the word
    send as the first argument (python ig_first_order.py send) to send the requests to the demo host.
    
    Before a real send, set these environment variables on your own machine:
      IG_API_KEY    your demo API key (demo needs its own key)
      IG_USERNAME   your IG demo username, not your email
      IG_PASSWORD   your IG demo password
    Never paste real values into this file or into any website.
    Needs Python 3.8 or later and requests (pip install requests).
    """
    import json
    import os
    import sys
    import time
    
    import requests
    
    DEMO_HOST = "https://demo-api.ig.com/gateway/deal"
    BASE_URL = os.environ.get("IG_BASE_URL", DEMO_HOST).rstrip("/")
    SEND = len(sys.argv) > 1 and sys.argv[1] == "send"
    # Pacing, from this site's limits register for IG:
    #   60 requests per minute (Per application, that is per API key, non trading calls only; checked 12 Oct 2026)
    #   100 requests per minute, while another IG page says 40 trade requests per minute; paced against 40 (Per account, trading calls only; checked 12 Oct 2026)
    #   30 requests per minute (Per account, non trading calls only; checked 12 Oct 2026)
    # One request every 2 seconds stays under each of them.
    MIN_GAP = 2
    
    if BASE_URL != DEMO_HOST:
        sys.exit("Refusing to run: this script only talks to the demo host " + DEMO_HOST)
    
    # The order. Every field the order record marks required is here, with the record's own words after it,
    # and so is every field the vendor's page marks neither required nor optional (tagged not marked by the vendor).
    # A value of "..." or 0 is a placeholder: set it before a real send, or the script refuses to send.
    ORDER = {
        "epic": "...",            # Instrument epic, for example IX.D.FTSE.DAILY.IP. (required)
        "direction": "BUY",       # BUY or SELL. (not marked by the vendor)
        "size": 0,                # Deal size. (required)
        "orderType": "MARKET",    # MARKET, LIMIT or QUOTE. (not marked by the vendor)
        "expiry": "DFB",          # "DFB" for daily funded spread bets, "-" for CFDs, or a contract month for futures style bets. (required)
        "currencyCode": "GBP",    # Currency, for example GBP. (required)
        "forceOpen": True,        # True to open a new position rather than net against an opposite one. (required)
        "guaranteedStop": False,  # True for a guaranteed stop. (required)
    }
    
    
    def env(name):
        """A credential or setting. In a dry run the value is the variable's own name, so nothing secret is read or printed."""
        if not SEND:
            return "<" + name + ">"
        value = os.environ.get(name)
        if not value:
            sys.exit("Set the " + name + " environment variable first.")
        return value
    
    
    # Session and credential headers live in this object's memory only: never printed, never saved.
    http = requests.Session()
    http.headers.update({
        "X-IG-API-KEY": env("IG_API_KEY"),
        "Accept": "application/json",
    })
    last_call = 0.0
    
    
    def request(method, path, version=None, body=None, params=None):
        """One request: printed in a dry run, paced and sent otherwise. Exits on an error reply, without printing a token."""
        global last_call
        headers = {}
        if version:
            headers["VERSION"] = version
        if not SEND:
            print("DRY RUN " + method + " " + BASE_URL + path)
            shown = dict(http.headers)
            shown.update(headers)
            print("  headers: " + json.dumps(shown, indent=4))
            if params:
                print("  query: " + json.dumps(params))
            if body is not None:
                print("  body: " + json.dumps(body, indent=4))
            return None
        wait = MIN_GAP - (time.monotonic() - last_call)
        if wait > 0:
            time.sleep(wait)
        last_call = time.monotonic()
        resp = http.request(method, BASE_URL + path, headers=headers, json=body, params=params, timeout=15)
        if resp.status_code >= 400:
            sys.exit("HTTP " + str(resp.status_code) + " from " + method + " " + path + ": " + resp.text[:300])
        print(method + " " + path + ": HTTP " + str(resp.status_code))
        return resp
    
    
    def log_in():
        """POST /session (version 2). Log in with username and password. Returns CST and X-SECURITY-TOKEN in the response headers; send both on every later call."""
        resp = request("POST", "/session", version="2",
                       body={"identifier": env("IG_USERNAME"), "password": env("IG_PASSWORD")})
        if resp is None:
            http.headers.update({"CST": "<IG_CST>", "X-SECURITY-TOKEN": "<IG_SECURITY_TOKEN>"})
            return
        ig_cst = resp.headers.get("CST")
        ig_security_token = resp.headers.get("X-SECURITY-TOKEN")
        if not (ig_cst and ig_security_token):
            sys.exit("Logged in, but CST or X-SECURITY-TOKEN was missing from the response headers.")
        http.headers.update({"CST": ig_cst, "X-SECURITY-TOKEN": ig_security_token})
        print("Logged in to demo. CST and X-SECURITY-TOKEN held in memory only.")
    
    
    def main():
        log_in()
        # GET /positions (version 2): All open positions on the active account, each with its market snapshot.
        resp = request("GET", "/positions", version="2")
        if resp is not None:
            print("Open positions: " + json.dumps(resp.json())[:600])
        # POST /positions/otc (version 2): Open an OTC position. Returns a dealReference only: the deal is not confirmed until you call GET /confirms/{dealReference}.
        if SEND and any(v in ("...", 0) for v in walk(ORDER)):
            sys.exit("Set every placeholder in ORDER before a real send.")
        resp = request("POST", "/positions/otc", version="2", body=ORDER)
        ref = resp.json()["dealReference"] if resp is not None else "<dealReference from the order reply>"
        print("dealReference: " + str(ref))
        # GET /confirms/{dealReference} (version 1): Deal confirmation for a dealReference. This is where you learn whether the deal was ACCEPTED or REJECTED, and why.
        resp = request("GET", "/confirms/" + str(ref), version="1")
        if resp is not None:
            print("Confirmation: " + json.dumps(resp.json(), indent=2))
        else:
            print("Dry run finished. Nothing was sent. Run again with the word send as the first argument to send these requests to the demo host.")
    
    
    def walk(value):
        """Every leaf value in a nested dict or list, for the placeholder check."""
        if isinstance(value, dict):
            for v in value.values():
                yield from walk(v)
        elif isinstance(value, list):
            for v in value:
                yield from walk(v)
        else:
            yield value
    
    
    if __name__ == "__main__":
        try:
            main()
        except requests.RequestException as err:
            sys.exit("Network error: " + str(err))
    
    

    labs.ig.comchecked

IG limits to pace against

LimitValueScopeSource
Historical price data points a week10,000Per account, price history endpoints onlylabs.ig.comchecked
Non trading requests a minute30Per account, non trading calls onlylabs.ig.comchecked
Non trading requests a minute60Per application, that is per API key, non trading calls onlylabs.ig.comchecked
Concurrent streaming subscriptions40Per streaming connectionlabs.ig.comchecked corroborated by labs.ig.com/streaming-api-guide.html
Trading requests a minute100Sources conflict ig.com states around 40, checked . IG's own pages disagree. The conflict is labelled, not resolved. Pace against 40.Per account, trading calls onlylabs.ig.comchecked

Sources

Sections