Python REST API client for the Trading 212 Public API
Project description
t212 — Trading 212 Python Client
A typed Python client for the Trading 212 Public API. Supports both synchronous and asynchronous usage, full pagination, and all 18 endpoints across the Account, Instruments, Orders, Positions, History, and Pies domains.
pip install t212
Requirements: Python 3.11+
Table of Contents
- Authentication
- Environments
- Quick Start
- Async Usage
- Resources
- Pagination
- Rate Limiting
- Error Handling
- Advanced Configuration
- Development
- License
Authentication
Trading 212's API uses HTTP Basic Authentication. Your API key acts as the username and your API secret as the password. Generate these in the Trading 212 app under Settings → API.
Important: Keep your credentials out of source code. Use environment variables or a secrets manager.
import os
from t212 import Trading212Client, Environment
client = Trading212Client(
api_key=os.environ["T212_API_KEY"],
api_secret=os.environ["T212_API_SECRET"],
env=Environment.LIVE,
)
Environments
| Constant | Description |
|---|---|
Environment.DEMO |
Paper trading account (default) |
Environment.LIVE |
Real money account |
Always test against DEMO first. The demo and live environments have separate credentials.
Quick Start
from t212 import Trading212Client, Environment
with Trading212Client("my_key", "my_secret", env=Environment.LIVE) as client:
# Account summary
summary = client.account.get_summary()
print(f"Balance: {summary.data.total_value} {summary.data.currency}")
print(f"Available to trade: {summary.data.cash.available_to_trade}")
# Open positions
positions = client.positions.get()
for pos in positions.data:
print(f"{pos.instrument.ticker}: qty={pos.quantity}, P&L={pos.wallet_impact.unrealized_profit_loss}")
# Place a market order
from t212.models.orders import MarketOrderRequest
req = MarketOrderRequest(ticker="AAPL_US_EQ", quantity=1.0)
order = client.orders.place_market(req)
print(f"Order {order.data.id} status: {order.data.status}")
# Iterate all dividend history (auto-paginated)
for div in client.history.iter_dividends():
print(f"{div.ticker}: {div.amount} {div.currency} on {div.paid_on}")
The client implements __enter__/__exit__ so it can be used as a context manager (recommended) or constructed and closed manually with client.close().
Async Usage
Use AsyncTrading212Client for async/await environments (FastAPI, asyncio scripts, etc.).
import asyncio
from t212 import AsyncTrading212Client, Environment
async def main():
async with AsyncTrading212Client("my_key", "my_secret", env=Environment.LIVE) as client:
summary = await client.account.get_summary()
print(summary.data.total_value)
# Async iteration over paginated history
async for order in client.history.iter_orders():
print(order.order.id, order.fill.price if order.fill else "pending")
asyncio.run(main())
Every method on AsyncTrading212Client is async. Async iterators (iter_orders, iter_dividends, iter_transactions) use async for.
Resources
All resource methods return an APIResponse[T] object:
response = client.account.get_summary()
response.data # The parsed model (AccountSummary, list[Order], etc.)
response.status_code # HTTP status code (int)
response.rate_limit # RateLimitInfo — see Rate Limiting section
Account
summary = client.account.get_summary()
# summary.data → AccountSummary
print(summary.data.currency) # "GBP"
print(summary.data.total_value) # 6000.0
print(summary.data.cash.available_to_trade) # 1000.0
print(summary.data.cash.reserved_for_orders) # 100.0
print(summary.data.investments.current_value) # 5000.0
print(summary.data.investments.unrealized_profit_loss) # 200.0
Instruments
# List all tradable instruments
instruments = client.instruments.list()
# instruments.data → list[TradableInstrument]
for inst in instruments.data:
print(inst.ticker, inst.name, inst.type, inst.currency_code)
# Get exchange schedules
exchanges = client.instruments.get_exchanges()
# exchanges.data → list[Exchange]
for exchange in exchanges.data:
print(exchange.name)
for schedule in exchange.working_schedules or []:
for event in schedule.time_events or []:
print(f" {event.type}: {event.date}")
TradableInstrument fields: ticker, name, isin, currency_code, type (InstrumentType), extended_hours, working_schedule_id, max_open_quantity, short_name, added_on.
Orders
Listing & fetching
# All open orders
orders = client.orders.list()
# orders.data → list[Order]
# Single order by ID
order = client.orders.get(987654321)
# order.data → Order
print(order.data.status) # OrderStatus.NEW
print(order.data.type) # OrderType.MARKET
print(order.data.side) # OrderSide.BUY
print(order.data.quantity) # 1.0
Placing orders
from t212.models.orders import (
MarketOrderRequest,
LimitOrderRequest,
StopOrderRequest,
StopLimitOrderRequest,
)
from t212.models.enums import TimeValidity
# Market order
req = MarketOrderRequest(ticker="AAPL_US_EQ", quantity=1.0)
result = client.orders.place_market(req)
# Market order (extended hours)
req = MarketOrderRequest(ticker="AAPL_US_EQ", quantity=1.0, extended_hours=True)
result = client.orders.place_market(req)
# Limit order
req = LimitOrderRequest(
ticker="AAPL_US_EQ",
quantity=1.0,
limit_price=150.00,
time_validity=TimeValidity.DAY, # or TimeValidity.GOOD_TILL_CANCEL
)
result = client.orders.place_limit(req)
# Stop order
req = StopOrderRequest(
ticker="AAPL_US_EQ",
quantity=1.0,
stop_price=140.00,
time_validity=TimeValidity.DAY,
)
result = client.orders.place_stop(req)
# Stop-limit order
req = StopLimitOrderRequest(
ticker="AAPL_US_EQ",
quantity=1.0,
stop_price=140.00,
limit_price=138.00,
time_validity=TimeValidity.GOOD_TILL_CANCEL,
)
result = client.orders.place_stop_limit(req)
Cancelling
result = client.orders.cancel(987654321)
# result.data is None on success
Order fields: id, ticker, type, side, status, strategy, quantity, filled_quantity, limit_price, stop_price, time_in_force, currency, extended_hours, initiated_from, created_at, instrument.
Positions
positions = client.positions.get()
# positions.data → list[Position]
for pos in positions.data:
print(pos.instrument.ticker)
print(pos.quantity)
print(pos.average_price_paid)
print(pos.current_price)
print(pos.quantity_available_for_trading)
print(pos.wallet_impact.unrealized_profit_loss)
print(pos.wallet_impact.currency)
Position fields: instrument, quantity, average_price_paid, current_price, quantity_available_for_trading, quantity_in_pies, created_at, wallet_impact.
History
Orders history
# Single page
page = client.history.get_orders(cursor=None, ticker="AAPL_US_EQ", limit=50)
# page.data → PaginatedResponse[HistoricalOrder]
print(page.data.items) # list[HistoricalOrder]
print(page.data.next_page_path) # str | None
# Auto-paginate (yields every item across all pages)
for hist_order in client.history.iter_orders(ticker="AAPL_US_EQ"):
print(hist_order.order.id, hist_order.fill.price if hist_order.fill else None)
Dividends history
page = client.history.get_dividends(limit=100)
for div in client.history.iter_dividends():
print(div.ticker, div.amount, div.type, div.paid_on)
HistoryDividendItem fields: ticker, amount, currency, gross_amount_per_share, quantity, paid_on, type (DividendType), reference, instrument.
Transactions history
page = client.history.get_transactions(limit=100)
for tx in client.history.iter_transactions():
print(tx.type, tx.amount, tx.currency, tx.date_time)
TransactionType values: DEPOSIT, WITHDRAW, FEE, TRANSFER.
CSV reports
from t212.models.history import PublicReportRequest, ReportDataIncluded
# Request a new report
req = PublicReportRequest(
data_included=ReportDataIncluded(include_dividends=True, include_orders=True),
time_from="2024-01-01T00:00:00Z",
time_to="2024-12-31T23:59:59Z",
)
enqueued = client.history.request_report(req)
print(enqueued.data.report_id)
# Check existing reports
reports = client.history.get_reports()
for r in reports.data:
print(r.report_id, r.status, r.download_link)
Pagination
Endpoints that return lists of historical data are cursor-paginated. You can either manage pages manually or use the iter_* convenience methods:
# Manual pagination
cursor = None
while True:
page = client.history.get_orders(cursor=cursor, limit=50)
for item in page.data.items or []:
process(item)
if page.data.next_page_path is None:
break
# Extract cursor from next_page_path for the next call
import urllib.parse as up
qs = up.parse_qs(up.urlparse(page.data.next_page_path).query)
cursor = int(qs["cursor"][0])
# Automatic (recommended)
for item in client.history.iter_orders(limit=50):
process(item)
The iter_* methods (iter_orders, iter_dividends, iter_transactions) handle all page fetching automatically and yield items one by one.
Rate Limiting
Every APIResponse includes a rate_limit attribute:
result = client.account.get_summary()
rl = result.rate_limit
rl.limit # int | None — requests allowed per period
rl.remaining # int | None — requests left in current window
rl.used # int | None — requests used so far
rl.period # int | None — window length in seconds
rl.reset # int | None — Unix timestamp when the window resets
The client does not auto-retry on 429 RateLimitError. Implement retry logic in your application:
import time
from t212 import RateLimitError
def safe_get_summary(client):
while True:
try:
return client.account.get_summary()
except RateLimitError as e:
reset = e.status_code # use rate_limit.reset from previous response
time.sleep(5)
Error Handling
All exceptions inherit from Trading212Error and carry a .status_code attribute.
| Exception | HTTP Status | Cause |
|---|---|---|
AuthenticationError |
401 | Invalid API key or secret |
ForbiddenError |
403 | Missing scope or permission |
NotFoundError |
404 | Resource does not exist |
ValidationError |
400 | Bad request / invalid parameters |
RateLimitError |
429 | Rate limit exceeded |
TimeoutError |
408 | Request timed out |
ServerError |
5xx | Trading 212 server error |
from t212 import (
Trading212Error,
AuthenticationError,
RateLimitError,
ForbiddenError,
)
try:
result = client.account.get_summary()
except AuthenticationError:
print("Check your API key and secret")
except ForbiddenError:
print("Enable the required API scope in the Trading 212 app")
except RateLimitError as e:
print(f"Rate limited (HTTP {e.status_code}), back off and retry")
except Trading212Error as e:
print(f"API error {e.status_code}: {e}")
Advanced Configuration
The client passes **httpx_kwargs directly to the underlying httpx.Client / httpx.AsyncClient, so you can customise timeouts, proxies, transport, and headers.
import httpx
from t212 import Trading212Client, Environment
# Custom timeout
client = Trading212Client(
"key", "secret",
env=Environment.LIVE,
timeout=httpx.Timeout(30.0, connect=5.0),
)
# HTTP proxy
client = Trading212Client(
"key", "secret",
env=Environment.LIVE,
proxies={"https://": "http://proxy.example.com:8080"},
)
# Custom transport (e.g. for testing)
transport = httpx.MockTransport(...)
client = Trading212Client("key", "secret", transport=transport)
Development
# Clone and install with dev dependencies
git clone https://github.com/joshglasspool/t212.git
cd t212
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Run tests
pytest tests/
# Lint
ruff check t212/
# Type check
mypy t212/
Tests use pytest-httpx to mock all HTTP calls — no real credentials or network access needed.
The OpenAPI spec the client was built from lives at spec/api.yaml. When Trading 212 publishes an updated spec, diff it against this file to identify endpoints or schema changes that need updating in the client.
License
MIT — see LICENSE for details.
This project is an independent open-source library and is not affiliated with, endorsed by, or supported by Trading 212. Use of the Trading 212 API is subject to their Terms of Service.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file t212_api-0.1.0.tar.gz.
File metadata
- Download URL: t212_api-0.1.0.tar.gz
- Upload date:
- Size: 30.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a0a82280944f66e1d5d5295e26f7bab749c4615c426e57a589786b86a7a3058
|
|
| MD5 |
84dc3244c2a9076b8a9060f20d66ab6d
|
|
| BLAKE2b-256 |
7ad701c6753e9185dfc72f5d20299a2a18962d8cac25f96b31bb5f6c71a479cd
|
Provenance
The following attestation bundles were made for t212_api-0.1.0.tar.gz:
Publisher:
publish.yml on joshglasspool/t212
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
t212_api-0.1.0.tar.gz -
Subject digest:
6a0a82280944f66e1d5d5295e26f7bab749c4615c426e57a589786b86a7a3058 - Sigstore transparency entry: 1000630798
- Sigstore integration time:
-
Permalink:
joshglasspool/t212@3c2c54ec537d77636137f4d8803832da85770bca -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/joshglasspool
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3c2c54ec537d77636137f4d8803832da85770bca -
Trigger Event:
release
-
Statement type:
File details
Details for the file t212_api-0.1.0-py3-none-any.whl.
File metadata
- Download URL: t212_api-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c070e3400c8946a44168f741cf66eda2108e83d6936e9f82e38df6f6635870c
|
|
| MD5 |
9d8fc9467178f372cee5b85d47373c79
|
|
| BLAKE2b-256 |
6064c7cae114959af095847257dbac44fe04f58035c775fba7aa7c6cbde9802d
|
Provenance
The following attestation bundles were made for t212_api-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on joshglasspool/t212
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
t212_api-0.1.0-py3-none-any.whl -
Subject digest:
8c070e3400c8946a44168f741cf66eda2108e83d6936e9f82e38df6f6635870c - Sigstore transparency entry: 1000630858
- Sigstore integration time:
-
Permalink:
joshglasspool/t212@3c2c54ec537d77636137f4d8803832da85770bca -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/joshglasspool
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3c2c54ec537d77636137f4d8803832da85770bca -
Trigger Event:
release
-
Statement type: