# Trading 212 API quickstart: practice key to first call

Independent and unofficial. Not affiliated with or endorsed by any broker or platform named here. Educational only: nothing here is advice, and nothing here recommends a broker or a trade.

This is the markdown version of https://openbrokerapi.com/brokers/trading-212/quickstart, written from the same records as that page.

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.

Make a first Trading 212 API call on the paper trading host: a key and secret, the Basic header and GET account summary in Python, JavaScript or curl. Each step is sourced on Trading 212's own pages. The samples were written from the documentation and have not been run against Trading 212.

- Checked: 2026-10-12

Unverified: the code on this page was written from the documentation and has not yet been run on a demo account.

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 you need before you start

Trading 212 calls this API a beta and says it is enabled and usable only for the Invest and Stocks ISA account types. Its documentation names two environments, paper trading (demo) and live trading, and this quickstart uses paper trading only. On your own machine you need one of three things: Python 3 with the requests package, Node 18 or later, or curl. You do not need a wrapper library. Nothing on this page asks for your key: the samples read it from environment variables that you set yourself.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api), checked 2026-10-12.

### 2. Generate a key and a secret

Trading 212's help centre gives the route: open the menu, choose Settings, then API (Beta), then Generate API key. A risk warning has to be accepted first. You then review the permissions on offer (the article gives account data, history, orders and portfolio as examples), name the key and choose between access from any IP address and access restricted to addresses you list. You receive an API key and a secret, and the article says the secret is shown only once, so store it where your code can read it and nobody else can. For this quickstart a key that can read account data is enough: leave the order permissions off until you need them. The article says key versions may differ between real and demo accounts. It does not spell out, in the text opened for this page, the steps for making a key on the practice account, so check which account you are in before you generate one.

Status: verified. Source: [helpcentre.trading212.com](https://helpcentre.trading212.com/hc/en-us/articles/14584770928157-Trading-212-API-key), checked 2026-10-12.

### 3. Point at the paper trading host

Paper trading (demo) is https://demo.trading212.com/api/v0 and live trading is https://live.trading212.com/api/v0. The paths are the same on both, so the host is the only thing that separates a practice call from a real one. Every sample on this page names the demo host in full. Do not change it until you have read the checklist for going live, and never as a quick test.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api), checked 2026-10-12.

### 4. Build the Authorization header

Trading 212 uses HTTP Basic authentication. Join the key and the secret with a colon, encode the result in Base64 and send it in the Authorization header after the word Basic. There is no login call and no session: the same header goes on every request. The samples in the next step read the two values from the T212_API_KEY and T212_API_SECRET environment variables and build the header in memory. The curl sample uses the -u option, which makes curl build the same header for you.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api), checked 2026-10-12.

### 5. First call: account summary

GET /api/v0/equity/account/summary is read only and takes no parameters, which makes it a cheap way to prove that the key, the secret and the host agree. Trading 212 documents it at 1 request every 5 seconds, so run it once and read the reply before running it again.

Python:

```python
# Set T212_API_KEY and T212_API_SECRET as environment variables first. Never paste real values into code or into this site.
# Written from Trading 212's documentation. Not run against Trading 212.
import base64
import os
import requests

BASE_URL = "https://demo.trading212.com"  # paper trading (demo) host
pair = os.environ["T212_API_KEY"] + ":" + os.environ["T212_API_SECRET"]
headers = {
    "Authorization": "Basic " + base64.b64encode(pair.encode()).decode(),
}

resp = requests.get(BASE_URL + "/api/v0/equity/account/summary", headers=headers, timeout=10)
print("HTTP", resp.status_code, "requests left in this period:", resp.headers.get("x-ratelimit-remaining"))
resp.raise_for_status()
summary = resp.json()
print(summary["currency"], summary["totalValue"], summary["cash"]["availableToTrade"])
```

JavaScript:

```javascript
// Set T212_API_KEY and T212_API_SECRET as environment variables first. Never paste real values into code or into this site.
// Written from Trading 212's documentation. Not run against Trading 212.
// Node 18 or later. Save as a .mjs file so top level await works.
const BASE_URL = "https://demo.trading212.com"; // paper trading (demo) host
const pair = `${process.env.T212_API_KEY}:${process.env.T212_API_SECRET}`;

const res = await fetch(`${BASE_URL}/api/v0/equity/account/summary`, {
  method: "GET",
  headers: {
    "Authorization": `Basic ${Buffer.from(pair).toString("base64")}`
  }
});
console.log("HTTP", res.status, "requests left in this period:", res.headers.get("x-ratelimit-remaining"));
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const summary = await res.json();
console.log(summary.currency, summary.totalValue, summary.cash.availableToTrade);
```

curl:

```bash
# Set T212_API_KEY and T212_API_SECRET as environment variables first. Never paste real values into code or into this site.
# Written from Trading 212's documentation. Not run against Trading 212.
# -u makes curl build the Authorization: Basic header from the key and the secret.
curl -s -u "$T212_API_KEY:$T212_API_SECRET" \
  "https://demo.trading212.com/api/v0/equity/account/summary"
```

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api/accounts/getaccountsummary), checked 2026-10-12.

### 6. Read the reply

A 200 reply is one JSON object. id is the account number and currency is the account currency. totalValue is the whole account. cash holds three numbers: availableToTrade, reservedForOrders and inPies. investments holds four: currentValue, totalCost, unrealizedProfitLoss and realizedProfitLoss. The numbers are JSON numbers, not strings. On a new practice account the investments figures can all be zero, which is a correct reply and not an error.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api/accounts/getaccountsummary), checked 2026-10-12.

### 7. A second read: the instrument list

GET /api/v0/equity/metadata/instruments returns every instrument the account can reach, each with its ticker, name, shortName, isin, type, currencyCode and workingScheduleId. The ticker is the name the order calls expect, so this is where you look one up. Trading 212 says the data is refreshed every 10 minutes and documents the call at 1 request every 50 seconds: fetch the list once, save it to a file and read the file after that. The key needs the metadata permission for this call.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api/instruments/instruments), checked 2026-10-12.

### 8. The limits to respect

Trading 212 sets a limit for each endpoint and applies it per account, whichever key or IP address the request comes from, so a second key does not buy a second allowance. Every reply carries five headers: x-ratelimit-limit, x-ratelimit-period, x-ratelimit-remaining, x-ratelimit-used and x-ratelimit-reset, the last being the Unix time at which the allowance resets. Trading 212 says requests may be sent in bursts within a period. The simplest pacing is to read x-ratelimit-remaining after each call and wait until x-ratelimit-reset when it reaches zero. This site's limits register holds one Trading 212 row, the account summary example of 1 request every 5 seconds, with the status verified and a check date of 22 September 2026: it is in the table below this list.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api/section/rate-limiting), checked 2026-10-12.

### 9. The errors you are likely to meet first

Trading 212's reference lists four failures for the account summary call. 401 is described as Bad API key: check that both the key and the secret are in the header, joined by a colon before encoding, and that the key belongs to the environment whose host you are calling. 403 is described as a missing account scope: the key was created without the permission this call needs. 408 is a timeout. 429 means the allowance for this endpoint is used up, so wait for the reset time in the headers and do not retry in a loop. This site keeps a page for each of these under /errors/trading-212, linked in the block at the foot of this page.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api/accounts/getaccountsummary), checked 2026-10-12.

### 10. What to read next, and one warning about orders

Positions, pending orders and history are further read only calls, each on this site's Trading 212 endpoint pages with its own limit. Placing an order is a separate step that this quickstart does not take. Before you try it on the practice account, note what Trading 212's reference says about POST /api/v0/equity/orders/market: in this beta the call is not idempotent, so sending the same request more than once may result in duplicate orders, and the sign of quantity sets the direction. A script that retries a failed order request without first reading the pending orders can therefore place the order twice. Stay on the paper trading host while you learn how that behaves.

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api/orders/placemarketorder), checked 2026-10-12.

### 11. From first call to first order

One Python file built from the Trading 212 endpoint records on this site, as the last step of this quickstart. It reads T212_API_KEY and T212_API_SECRET from the environment. GET /api/v0/equity/positions (version v0) reads your open positions. POST /api/v0/equity/orders/market (version v0) places one small demo order with the fields the vendor's page marks neither required nor optional (ticker, quantity and extendedHours), each tagged in a comment. GET /api/v0/equity/orders/{id} (version v0) reads the confirmation or status. It sends at most one request every 5 seconds, which stays under each request limit in the Trading 212 limits table below. It refuses to run against any host but https://demo.trading212.com, and it never prints a token. Dry run is the default: save it as t212_first_order.py, run python t212_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 t212_first_order.py send to send them to the demo host. The script has not been run against Trading 212.

Python:

```python
#!/usr/bin/env python3
"""Trading 212 demo API: from first call to first order.

Unofficial example from Broker API Hub, not published by Trading 212. Written from the Trading 212 endpoint records on
openbrokerapi.com and not run against Trading 212: the quickstart page says so. Demo host only.

What it does, in order:
  1. proves the token with GET /api/v0/equity/account/summary (version v0)
  2. GET /api/v0/equity/positions (version v0): reads your open positions
  3. POST /api/v0/equity/orders/market (version v0): places one small demo order with every field the record marks required
  4. GET /api/v0/equity/orders/{id} (version v0): 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 t212_first_order.py send) to send the requests to the demo host.

Before a real send, set these environment variables on your own machine:
  T212_API_KEY      the API key for the practice account
  T212_API_SECRET   the secret shown once when the key was made
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 base64

import requests

DEMO_HOST = "https://demo.trading212.com"
BASE_URL = os.environ.get("T212_BASE_URL", DEMO_HOST).rstrip("/")
SEND = len(sys.argv) > 1 and sys.argv[1] == "send"
# Pacing, from this site's limits register for Trading 212:
#   1 req/5s, account summary (example) (account; checked 12 Oct 2026)
# One request every 5 seconds stays under each of them.
MIN_GAP = 5

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 = {
    "ticker": "...",         # Instrument ticker as Trading 212 writes it, for example AAPL_US_EQ. Read it from the instruments call. (not marked by the vendor)
    "quantity": 0,           # Number of shares. Positive to buy, negative to sell. (not marked by the vendor)
    "extendedHours": False,  # True to let the order fill outside the regular session. Default false. (not marked by the vendor)
}


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({
    "Authorization": "Basic " + (base64.b64encode((env("T212_API_KEY") + ":" + env("T212_API_SECRET")).encode()).decode() if SEND else "<from T212_API_KEY and T212_API_SECRET>"),
    "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 main():
    # GET /api/v0/equity/account/summary (version v0): Cash and investment figures for the account: funds free to trade, cash reserved for pending orders, the value and cost of holdings, and the total.
    resp = request("GET", "/api/v0/equity/account/summary")
    account_id = resp.json()["id"] if resp is not None else "<id>"
    print("id: " + str(account_id))
    # GET /api/v0/equity/positions (version v0): Every open position on the account, with quantity, average price paid, current price and the effect on the account in its own currency.
    resp = request("GET", "/api/v0/equity/positions")
    if resp is not None:
        print("Open positions: " + json.dumps(resp.json())[:600])
    # POST /api/v0/equity/orders/market (version v0): Place a market order, to deal at the next available price. The sign of quantity sets the direction. If the market is closed the order waits for it to open.
    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", "/api/v0/equity/orders/market", body=ORDER)
    ref = resp.json()["id"] if resp is not None else "<id from the order reply>"
    print("id: " + str(ref))
    # GET /api/v0/equity/orders/{id} (version v0): One pending order, looked up by its numeric identifier. Use it to check on an order you placed earlier.
    resp = request("GET", "/api/v0/equity/orders/" + str(ref))
    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))
```

Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api), checked 2026-10-12.

## Sources

- Status: verified. Source: [Trading 212 API docs: general information, environments and authentication](https://docs.trading212.com/api), checked 2026-10-12.
- Status: verified. Source: [Trading 212 help centre: Trading 212 API key](https://helpcentre.trading212.com/hc/en-us/articles/14584770928157-Trading-212-API-key), checked 2026-10-12.
- Status: verified. Source: [Trading 212 API docs: get account summary](https://docs.trading212.com/api/accounts/getaccountsummary), checked 2026-10-12.
- Status: verified. Source: [Trading 212 API docs: get all available instruments](https://docs.trading212.com/api/instruments/instruments), checked 2026-10-12.
- Status: verified. Source: [Trading 212 API docs: rate limiting](https://docs.trading212.com/api/section/rate-limiting), checked 2026-10-12.
- Status: verified. Source: [Trading 212 API docs: place a market order](https://docs.trading212.com/api/orders/placemarketorder), checked 2026-10-12.

## Trading 212 limits to pace against

- Headline limit: 1 req/5s, account summary (example)
  - Scope: account
  - Status: verified. Source: [docs.trading212.com](https://docs.trading212.com/api/section/rate-limiting), checked 2026-10-12.
  - Note: Limits are set per endpoint and applied per account; this is the account summary example from the docs, not a single global figure. A different endpoint on the same docs page carries a much higher limit, order placement (POST market order) at 50 requests a minute, about one call every 1.2 seconds, shown elsewhere on this site's re-verified note dated 23 Sep 2026. The two figures are not a contradiction: they are two different endpoints on one rate-limiting page, read account summary 22 Sep 2026 and order placement 23 Sep 2026. Build a rate limiter against the endpoint you actually call, not against either figure alone.

## Next on the Trading 212 API

- [Trading 212 API error codes](https://openbrokerapi.com/errors/trading-212): 8 decoded, each with what it means, and the fix where the source gives one.
- [Trading 212 API endpoints](https://openbrokerapi.com/brokers/trading-212/endpoints): 16 documented, each with its method, path, parameters and source.
- [Trading 212 API page](https://openbrokerapi.com/brokers/trading-212): The full record: access, authentication, limits and the source for each.
- [Export Trading 212 trade history to Excel or a spreadsheet](https://openbrokerapi.com/guides/trading-212-trade-history): Guide

## Licence and how to cite

The records behind this page are all rights reserved. This file is the page itself, at its own address, in another format. It is not part of the CC BY 4.0 downloads listed at https://openbrokerapi.com/data. Quote from it with a link to the page.

Cite as: Trading 212 API quickstart: practice key to first call. Broker API Hub, https://openbrokerapi.com/brokers/trading-212/quickstart, newest check 2026-10-12.

Where you rely on a single figure, cite the source and the checked date printed beside it as well. Where this page and a broker's own documentation differ, the documentation wins.
