← Back to the general documentation
API documentation
Trade, read prices and query your account from your own program, with the same rules as the chart. It is simulated money for educational purposes.
Start in 5 minutes
The API is JSON over HTTPS. Amounts and prices travel as text (for example "78000.5") so no precision is lost; dates are Unix seconds. With it you can read prices, open and close positions and query your account from your own program, with the same rules, costs and limits as the chart.
- Create a practice account and save the key (it is shown only once):
curl -X POST https://tapearena.com/v1/accounts
- Look at the price (no key needed):
curl https://tapearena.com/v1/prices
- Buy 0.05 BTC at market:
curl -X POST https://tapearena.com/v1/orders -H "X-API-Key: TU_CLAVE" -H "content-type: application/json" \ -d '{"symbol":"BTC","side":"buy","qty":"0.05"}' - Look at your account:
curl https://tapearena.com/v1/account -H "X-API-Key: TU_CLAVE"
A simulated account of 5,000 USDC, maximum leverage 3×, fee 0.035 % and dynamic slippage. Everything is simulated money for educational purposes.
Authentication and keys
- Every account has one API key. It goes in the
X-API-Keyheader of every call that needs it. - The key is created with
POST /v1/accountsand is only shown in that response: save it. The server stores only its fingerprint, so it cannot be recovered; if you lose it, create another account. - Treat it like a password: do not push it to public repositories or put it in code that runs in a browser.
- With no key, or a wrong one, the answer is 401
invalid API key.
curl https://tapearena.com/v1/account -H "X-API-Key: your_key_here"
Two ways to get a key
- Your competition account: open the user menu → My account → API section and press “Generate key”. It is shown only once. You can generate another any time (the old one stops working at once) or revoke it. Orders go into the same account you use on the chart and compete on the leaderboard.
- A practice account: the ones
POST /v1/accountscreates. They are independent of the competition.
A program never needs your password or your web session: only the key.
Concepts worth knowing
- Symbols:
BTC,ETHandSOL(perpetual futures, real market prices). - Side and quantity:
sideisbuyorsell;qtyis units of the asset (0.05 = 0.05 BTC), not dollars. - One net position per symbol. With the API each order adds to or subtracts from the same position. Buying while you are short reduces it or flips it. This differs from the chart, where each order can be an independent trade with its own SL/TP.
- Chart and API on the same account: orders sent through the API make up a net position separate from your chart trades, but both count towards the leverage limit (one cap for everything). To avoid surprises, use one route or the other for each asset.
- Costs: a 0.035 % fee on the amount of every fill, plus slippage on market orders: 2 basis points plus 1 per 100,000 USDC of amount.
- Leverage: the total amount of your positions cannot exceed 3× your capital (
422 leverage limit exceeded). If your equity falls below 0.5 % of the amount you hold in positions, the system liquidates everything at once. - Equity = balance + unrealised profit or loss of the open positions. The balance (
balance) already deducts fees. - Risk limits: if at the day's close (00:00 UTC) you have lost more than allowed, your positions are closed and the account is blocked (
423) until rehabilitated. See the risk limits section.
Reference: account and orders
POST /v1/accounts public, no key
Creates a practice account and returns its API key. The key is only shown here.
| Parameter | Type | Required | Description |
|---|---|---|---|
country | string (ISO 3166) | no | Two-letter country, only to show the flag. |
season | integer | no | Id of a season open for enrolment (before it starts). |
Example
curl -X POST https://tapearena.com/v1/accounts
import requests
r = requests.post("https://tapearena.com/v1/accounts").json()
api_key = r["api_key"] # save itResponse
{
"account": "acc_8bc1ab396b5f",
"api_key": "cAEFMlBbxnsECOQbifLiaY6BHfXoYFrOYzmYmWqfKp0",
"balance": "5000",
"note": "store the key now; it cannot be shown again"
}Errors
- 422 ·
enrolment closed (opens until the season starts)— The season already started: an account can only be enrolled before the start. - 422 ·
name already taken— That name is already taken. - 422 ·
country must be a 2-letter ISO code— The country must be a 2-letter code. - 429 ·
too many accounts created from your address: at most N per M minutes— Too many accounts created from your address (only when the service applies this limit).
GET /v1/prices public, no key
Latest price of every asset. Updated every second.
Example
curl https://tapearena.com/v1/prices
Response
{
"BTC": "78000",
"ETH": "2300",
"SOL": "150"
}GET /v1/account key required
Your account: balance, equity, whether it is liquidated and your open positions (one per symbol, with a signed quantity: positive = long, negative = short).
Example
curl https://tapearena.com/v1/account -H "X-API-Key: TU_CLAVE"
import requests
H = {"X-API-Key": "TU_CLAVE"}
print(requests.get("https://tapearena.com/v1/account", headers=H).json()["equity"])Response
{
"account": "acc_8bc1ab396b5f",
"balance": "4998.63",
"equity": "4997.84",
"liquidated": false,
"positions": {
"BTC": {
"qty": "0.05",
"entry": "78015.90",
"sl": null,
"tp": null
}
}
}Errors
- 401 ·
invalid API key— The key is missing or wrong. - 429 ·
rate limit exceeded— Too many requests in a row: wait a moment.
POST /v1/orders key required
Sends a market order (fills at once) or a limit order (fills when the price reaches yours). You can add a Stop Loss and Take Profit, which attach to the position. See the life of an order.
| Parameter | Type | Required | Description |
|---|---|---|---|
symbol | string | yes | BTC, ETH or SOL. |
side | string | yes | buy or sell. |
qty | string (decimal) | yes | Units of the asset; greater than 0. |
type | string | no | market (default) or limit. |
price | string (decimal) | no | Limit price. Required when type is limit. |
sl | string (decimal) | no | Stop Loss: for a long, below the price; for a short, above it. |
tp | string (decimal) | no | Take Profit: for a long, above the price; for a short, below it. |
Example
curl -X POST https://tapearena.com/v1/orders -H "X-API-Key: TU_CLAVE" -H "content-type: application/json" \
-d '{"symbol":"BTC","side":"buy","qty":"0.05","sl":"74000","tp":"84000"}'import requests
H = {"X-API-Key": "TU_CLAVE"}
r = requests.post("https://tapearena.com/v1/orders", headers=H, json={"symbol": "BTC", "side": "buy", "qty": "0.05"})
print(r.status_code, r.json())Response
// market order (or an already-executable limit): 201
{
"status": "filled",
"symbol": "BTC",
"side": "buy",
"qty": "0.05",
"price": "78015.90",
"fee": "1.365",
"realized_pnl": "0"
}
// limit order that waits: 201
{
"status": "pending",
"order_id": 1,
"symbol": "BTC",
"side": "buy",
"qty": "0.02",
"price": "70000"
}Errors
- 422 ·
leverage limit exceeded— The order would take the total amount past the maximum leverage. - 422 ·
no price for symbol— Unknown symbol (use BTC, ETH or SOL). - 422 ·
a limit order needs a price— A limit order needs the price field. - 422 ·
at most 20 pending orders— You already have 20 pending limit orders. - 422 ·
account liquidated— Your account was liquidated and cannot trade. - 422 ·
season not active— Your account's season is not running. - 422 ·
stop loss must be below the current price for a long position— The Stop Loss or Take Profit is on the wrong side of the price (there are equivalent messages for each case). - 423 ·
account blocked: a risk limit was exceeded; rehabilitate it— Your account is blocked for exceeding a risk limit. - 429 ·
rate limit exceeded— Too many requests in a row. - 429 ·
order limit: at most 20 per hour and 200 per day per account (HFT is prohibited)— You reached the maximum orders per hour (20) or per day (200); high-frequency trading is prohibited.
POST /v1/stops key required
Changes or removes the Stop Loss and Take Profit of your position in a symbol. Send null to remove one.
| Parameter | Type | Required | Description |
|---|---|---|---|
symbol | string | yes | Symbol with an open position. |
sl | string | null | no | New Stop Loss, or null to remove it. |
tp | string | null | no | New Take Profit, or null to remove it. |
Example
curl -X POST https://tapearena.com/v1/stops -H "X-API-Key: TU_CLAVE" -H "content-type: application/json" \
-d '{"symbol":"BTC","sl":"75000","tp":null}'Response
{
"symbol": "BTC",
"sl": "75000",
"tp": null
}Errors
- 422 ·
no open position in that symbol— You have no open position in that symbol. - 422 ·
stop loss must be above the current price for a short position— Stop Loss or Take Profit on the wrong side of the price (depending on long or short). - 423 ·
account blocked: a risk limit was exceeded; rehabilitate it— Account blocked by risk.
GET /v1/orders/open key required
Your pending limit orders (at most 20 at a time).
Example
curl https://tapearena.com/v1/orders/open -H "X-API-Key: TU_CLAVE"
Response
[
{
"id": 1,
"symbol": "BTC",
"side": "buy",
"qty": "0.02",
"price": "70000",
"sl": "68000",
"tp": "76000",
"created": 1794425798.33,
"origin": "manual",
"ref": ""
}
]DELETE /v1/orders/{order_id} key required
Cancels a pending limit order.
| Parameter | Type | Required | Description |
|---|---|---|---|
order_id | integer (path) | yes | The order_id the order returned. |
Example
curl -X DELETE https://tapearena.com/v1/orders/1 -H "X-API-Key: TU_CLAVE"
Response
{
"cancelled": 1
}Errors
- 404 ·
unknown order— The order does not exist, already filled or is not yours.
GET /v1/fills key required
Every fill of your account, oldest first. origin says where it came from: api (this API), manual (the chart), conditional (conditional order), strategy (Laboratory strategy) or system (liquidation). realized_pnl is the profit closed in that fill, before deducting the fee.
Example
curl https://tapearena.com/v1/fills -H "X-API-Key: TU_CLAVE"
Response
[
{
"symbol": "BTC",
"side": "buy",
"qty": "0.05",
"price": "78015.90",
"fee": "1.365",
"realized_pnl": "0",
"liquidation": false,
"origin": "api",
"ref": ""
}
]How an order is executed
- Market: it fills right away at the last price plus slippage. You get
status: "filled"with the real price, the fee and, if you closed something, therealized_pnl. - Limit: if the market is already at your price or better, it fills at once at the current price and without slippage. Otherwise it stays
"pending"and fills, in full, when the price touches it. Market depth and priority queues are not simulated. - Stop Loss and Take Profit: they attach to the position. If the price reaches the Stop Loss, the position is closed at market with slippage; at the Take Profit, it is closed at the set price. With a pending limit order they apply when it fills.
- Leverage: checked when you send and again when a limit order fills; if it does not fit at that moment, the order is dropped.
Reference: public data
These endpoints need no key. They are cached for a few seconds, so there is no point in querying them more often.
GET /v1/seasons public, no key
The seasons, with their rules (minimums, maximum allowed drawdown) and status: upcoming, live or ended.
Example
curl https://tapearena.com/v1/seasons
Response
[
{
"id": 1,
"name": "November 2026",
"kind": "monthly",
"start": 1793491200.0,
"end": 1796083200.0,
"min_trades": 3,
"max_drawdown": "0.5",
"min_active_days": 2,
"min_trade_frac": "0.01",
"status": "live"
}
]GET /v1/leaderboard public, no key
A season's leaderboard: position, name, return, maximum drawdown, trades, active days and score. With include_unranked=true it adds the accounts that do not qualify yet, with the reason.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | How many rows (1 to 100). Default 10. |
season | integer | no | Season; the current one by default. |
include_unranked | boolean | no | Include the unranked. |
Example
curl 'https://tapearena.com/v1/leaderboard?limit=5'
GET /v1/participants/{account} public, no key
The public history of an account: statistics, the equity curve (what the leaderboard measures) and the balance curve (the cash with closed trades), positions and trades. Trades of user accounts are published with a 1-hour delay so nobody can copy a live strategy.
| Parameter | Type | Required | Description |
|---|---|---|---|
rows | integer | no | How many trades (the most recent), up to 50,000. Default 600. |
max_points | integer | no | Curve points. Default 600. |
Example
curl 'https://tapearena.com/v1/participants/acc_8bc1ab396b5f?rows=50'
GET /v1/curves public, no key
The equity curves of all participants of a season (return against the start), to draw the comparison.
| Parameter | Type | Required | Description |
|---|---|---|---|
season | integer | no | Season. |
max_points | integer | no | Points per curve (default 120). |
limit | integer | no | How many accounts (default 150). |
Example
curl 'https://tapearena.com/v1/curves?max_points=60'
GET /v1/benchmarks public, no key
What simply buying and holding each asset would have earned since the season started: the reference line to measure your strategy.
| Parameter | Type | Required | Description |
|---|---|---|---|
season | integer | no | Season. |
Example
curl https://tapearena.com/v1/benchmarks
Errors and status codes
Errors return a JSON {"detail": "…"}. Body validation errors (for example, side other than buy or sell) return in detail a list with the field and the reason.
| Code | Meaning | What to do |
|---|---|---|
| 200 / 201 | Success (201: something was created or filled) | — |
| 401 | Missing or wrong key | Check the X-API-Key header |
| 403 | Operation reserved for administrators | Not available to your account |
| 404 | Unknown order, symbol or resource | Check the identifier |
| 422 | Order rejected or invalid body (leverage, SL on the wrong side, season not active…) | Read detail and fix it; do not retry as is |
| 423 | Account blocked for exceeding a risk limit | Rehabilitate it from the website |
| 429 | Too many requests, or order limit reached | Wait and retry with exponential back-off |
| 502 | Price data not available right now | Retry in a few seconds |
Usage limits
| What | Limit |
|---|---|
| Requests with a key | 10 per second per key, with a burst of 20 (429 rate limit exceeded) |
| Orders (high frequency) | 20 per hour and 200 per day per account (429 order limit…). High-frequency trading is prohibited |
| Account creation | 5 per 60 minutes per address (429) |
| Pending limit orders | 20 at a time |
| Leverage | 3× equity; liquidation below 0.5 % of the amount |
| Trades of a participant | up to 50,000 per call |
| Accounts per person | 1 |
Good practice: do not query more than once per second; reuse a single connection; on a 429 wait before retrying; do not repeat a rejected order with the same data; keep your key out of your code. Abusive use (automating account creation, overloading the service, exploiting errors or stale prices) may lead to the user being cancelled.
Complete examples
Python with requests: buy, set stops and exit
import requests, time
BASE = "https://tapearena.com"
H = {"X-API-Key": "TU_CLAVE"}
def call(method, path, **kw):
"""Call the API and retry on 429 (exponential back-off)."""
for wait in (0.2, 0.5, 1, 2, 4):
r = requests.request(method, BASE + path, headers=H, timeout=10, **kw)
if r.status_code != 429:
return r
time.sleep(wait)
return r
price = float(requests.get(BASE + "/v1/prices").json()["BTC"])
r = call("POST", "/v1/orders", json={"symbol": "BTC", "side": "buy", "qty": "0.05",
"sl": str(round(price * 0.97)), "tp": str(round(price * 1.06))})
print(r.status_code, r.json()) # 201 {"status": "filled", ...}
print(call("GET", "/v1/account").json()["positions"])
# exit: sell what you hold
pos = call("GET", "/v1/account").json()["positions"]["BTC"]["qty"]
print(call("POST", "/v1/orders", json={"symbol": "BTC", "side": "sell", "qty": pos}).json())
A limit order and cancelling it
o = call("POST", "/v1/orders", json={"symbol": "ETH", "side": "buy", "qty": "1", "type": "limit", "price": "2000"}).json()
if o["status"] == "pending":
print("waiting, id", o["order_id"])
call("DELETE", f"/v1/orders/{o['order_id']}")
With the single-file client
from client import Client
c, info = Client.register("https://tapearena.com") # save info["api_key"]
c.buy("BTC", "0.05")
c.target("BTC", 0.5, leverage=1) # move the position to 50 % of equity
c.close("BTC")
Watching the price and computing an average
import requests, time
prices = []
for _ in range(30): # one price per second
prices.append(float(requests.get("https://tapearena.com/v1/prices").json()["BTC"]))
time.sleep(1)
avg = sum(prices) / len(prices)
print("BTC above its 30 s average:", prices[-1] > avg)
JavaScript (fetch)
const H = {"X-API-Key": "TU_CLAVE", "content-type": "application/json"};
const r = await fetch("https://tapearena.com/v1/orders", {method: "POST", headers: H,
body: JSON.stringify({symbol: "BTC", side: "buy", qty: "0.05"})});
console.log(r.status, await r.json());
These examples use a practice account. Run your programs on your own server or computer, not on a public web page, so you do not expose the key.
Single-file Python client
A single file, no dependencies: copy it next to your program. Download it with curl -O https://tapearena.com/sdk/client.py.
from client import Client, run
c, info = Client.register("https://tapearena.com") # save info["api_key"]
c.prices(); c.buy("BTC", "0.1"); c.target("BTC", 0.5, leverage=2); c.close("BTC")
run(c, "BTC", lambda hist: 1.0 if hist[-1] > hist[0] else -1.0, every=60)
Client.register(url)creates a practice account;Client(url, api_key=…)reuses an existing one.prices(),account(),position(sym),fills(),leaderboard(),seasons().buy(),sell(),order(),target(sym, fraction, leverage=…)(moves the position to a fraction of equity) andclose(sym).run(client, symbol, strategy, every=seconds)loops a function that receives the price history and returns a target between −1 and 1.
Available assets
You trade crypto perpetual futures, quoted in USDC with simulated money. Today there are 3 assets; any other symbol is rejected.
| API symbol | Contract | History (download) | Price now |
|---|---|---|---|
| BTC | Perpetual future BTC/USDC | BTCUSDT | … |
| ETH | Perpetual future ETH/USDC | ETHUSDT | … |
| SOL | Perpetual future SOL/USDC | SOLUSDT | … |
Click a symbol to see its chart. You can be long (buy) or short (sell) on each, with a net position per symbol and up to 10x leverage on your total equity. Several assets can be combined in one account.
Historical data
TapeArena does not allow downloading price history: the API only gives current prices (and your program can keep the ones it receives). If you want to test a strategy on history, download it from a public source and work with it on your own machine:
- Binance Public Data (data.binance.vision): free. Candles (OHLCV) of USDT-M perpetual futures, such as BTCUSDT, ETHUSDT and SOLUSDT, in daily and monthly files since 2020.
- Tardis.dev: trade-by-trade data, order book and funding of many derivatives markets. Paid, with the first day of each month free to try.
Prices on other markets differ slightly from TapeArena's, so use them for research and do not expect identical results. Check each source's terms of use.
What is not in the API yet
The API covers the basic operation with one net position per symbol. These features exist in the graphical environment and do not yet have an endpoint:
- Independent trades (closing or reducing a specific trade, per-trade SL/TP).
- Conditional orders, OCO and expiration.
- Laboratory strategies (save, enable, portfolio).
- Rehabilitating a blocked account (done from “My account”).
This list will be updated here when the API grows.
Frequently asked questions
How do I get my API key?
For your competition account, from My account → API → “Generate key”. For a practice account, with POST /v1/accounts. Either way it is shown once: save it.
Why do I get a 429 error?
You exceeded the limit of 10 requests per second per key (burst of 20) or the orders per hour and per day. Wait and retry with exponential back-off.
What is the difference between a market and a limit order?
A market order fills instantly at the last price plus slippage; a limit order fills when the price reaches yours, in full and without slippage.
Can I hold several positions in the same asset through the API?
No: with the API there is one net position per symbol. Buying and selling add to and subtract from that same position.