Free Crypto Paper Trading API - Test Your Trading Bot | MarginPad
MARGINPAD
Live prices · server-side fills · no deposit

The exchange your bot can afford to break.

A paper-trading futures API on real live prices. Your bot opens leveraged positions, rests limit and stop orders, trails its stops and gets liquidated - settled on our servers, in simulated dollars. Same code you will run for real, none of the consequences.

No card, no KYC, no deposit. Free forever at 120 requests a minute - Pro is $29 when one bot becomes four. Market data stays keyless on every plan.

marginpad.io/api/bot/v1live now
# no key needed - run this right now
curl "https://marginpad.io/api/bot/v1/price?symbol=BTC"

{ "symbol": "BTC", "price": , "ts":  }
POST /openexample
curl -X POST ".../v1/open" -H "X-API-Key: mpb_…" \
  -d '{"symbol":"BTC","side":"long","margin_usd":100,
       "leverage":10,"trail_pct":1.5}'

{ "ok": true, "position": {
    "id": "bot_9f21…",   "entry": ,
    "liq_price": ,  "trail_pct": 1.5,
    "fee_round_trip_usd": 1.10 } }
24
endpoints, plus a WebSocket, webhooks and an MCP server
9
real venue fee schedules your fills can be charged at
1,000×
maximum leverage, on any USDT pair with a live price
1.1M
calls served for the bots already running on it
STATUS trading store liquidation collector scheduled jobs reading /api/status… Full status and history →
Quickstart

Three calls from nothing to a live position

The first one needs no key at all - paste it into a terminal before you decide anything. Every path below exists in a frozen v1 and an enveloped v2; pick one and it will not move under you.

STEP 01

Read the market

GET /v1/price and /v1/klines are keyless and CORS-enabled. Up to 1,000 candles a call, seven intervals, paged backwards with &end= for as much history as a backtest needs.

STEP 02

Open a position

POST /v1/open fills at the live price with isolated margin and gives you back the liquidation level. Add type:"limit" or "stop" to rest an order server-side, or dry_run to price a trade without writing anything.

STEP 03

Let it push, do not poll

Stops, targets, trailing stops and liquidations settle on our side, on 1-minute candles, whether or not your bot is running. Open the WebSocket and the outcome arrives in about two seconds - it is free on every plan, and it is the difference between a bot that reacts and one that waits for its next loop.

# price (no key)
curl "https://marginpad.io/api/bot/v1/price?symbol=SOL"

# open a 20x long with $100 margin
curl -X POST "https://marginpad.io/api/bot/v1/open" \
  -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"symbol":"SOL","side":"long","margin_usd":100,"leverage":20}'

# check live P&L
curl "https://marginpad.io/api/bot/v1/positions" -H "X-API-Key: YOUR_KEY"

# close half, let the rest run
curl -X POST "https://marginpad.io/api/bot/v1/close" \
  -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"id":"POSITION_ID","pct":50}'

Authenticate with X-API-Key on every call except /price, /klines and /time. Official clients, zero dependencies - npm install marginpad or pip install marginpad, or one file each: marginpad.py · marginpad.js - REST, WebSocket, ETag polling, 429 back-off and webhook signature verification built in.

Get your key

Sign in with an email - no password, no KYC. Your bot's positions persist on your account, and one account can hold several keys so a backtest bot and a live bot never share a rate budget.

Your keys, their usage and the book each one trades appear here once you are signed in. Keys are secrets - anyone holding one can trade your paper account.

Why not an exchange testnet

A testnet gives you fake prices. This gives you real ones and fake money.

Testnet books are thin, their prices drift from the real market, and they reset without warning. Everything here is priced from the live multi-exchange feed our own charts run on - only the dollars are simulated.

Execution

The server holds your orders

Limit entries, stop entries, stop-losses, targets and trailing stops are all evaluated on our side against 1-minute candles. Your bot can be offline, restarting or redeployed and the fill still lands at your level, stamped with the minute the market actually reached it.

Costs

Fees that match your real bill

Both legs pay a taker fee, charged as a round trip at close, with funding accrued across marks. Name a venue and the paper account is charged at that exchange's published schedule less our referral discount - so a scalping strategy fails here for the same reason it would fail there.

Failure

Liquidation that behaves

Isolated margin, 0.5% maintenance, losses capped at your margin. A trailing stop is moved by each candle's extreme after that candle was checked against the stop in force - so a bar can never stop you out at a level it created itself.

Idempotency

A timeout is not a mystery

Send client_order_id and a retried open returns the position the first call created, flagged idempotent: true. Closing takes one too. This single field is the difference between a bot you trust unattended and one you babysit.

Observability

Push, not poll

A free WebSocket pushes position events and marks every ~2 s; webhooks POST the same shape to your own URL, HMAC-signed and retried, even while nothing of yours is connected. ETags make the polls you keep nearly free.

Measurement

It grades your strategy

Every book gets its own journal, equity curve and drawdown. /v1/report returns win rate and return by coin, leverage band, side and hour, with a skill score - and it says nothing under eight trades rather than inventing a pattern.

Plans

Free is the whole engine. You pay to run more than one thing at once.

Every account starts on Free and gets the complete product: the trading engine, real venue fees, limit and stop entries, trailing stops, replay, books, the WebSocket stream and all keyless market data. The paid plans raise ceilings and add two things - webhooks and AI market reads. Nothing else is held back.

Free

$0

Build and run a real bot without paying anything.

One strategyone book, one journal, one balance
  • 3 keys · 50 open positions · 120 req/min
  • Full engine: market, limit & stop orders, trailing stops, dry run, replay
  • Fee schedule of 9 real venues
  • WebSocket stream · MCP · keyless market data
  • Trading report: totals + skill score

Pro

MOST BOTS
$29/month

For a bot that runs unattended, and for comparing strategies instead of guessing between them.

5 strategies, and the server calls youfive separate books · webhooks · 90-day history
  • 10 keys · 200 open positions · 600 req/min
  • Webhooks - 3 signed, retried endpoints, delivered while you are offline
  • AI market reads - 50 a day through /v1/ai
  • Trading report with breakdowns and written findings
  • 90 days of trade history, report and equity curve - Free keeps 30

Max

$79/month

For a fleet - a family of strategies, each with its own book, running at the same time.

20 strategiestwenty books · 15 webhooks
  • 30 keys · 500 open positions · 2000 req/min
  • 15 webhooks
  • 200 AI market reads a day
  • Everything in Pro

Business

$159/month

For an app or a desk trading on behalf of its own users - a key each, a book each.

100 keys, 50 booksone key per end user, kept apart
  • 1000 open positions · 5000 req/min
  • 50 webhooks
  • 500 AI market reads a day
  • Everything in Max
Rate limits are rarely the reason to upgrade, and we would rather tell you than sell you.

Measured across every key on this API over the last fortnight: the busiest bot in production sits at 46 requests a minute - 38% of what the Free plan already allows - and no key has ever been rate-limited, on any plan. If you are choosing between these, choose on the books, the webhooks and the history, which is what actually runs out. The throughput is there when you need it. Most bots never touch it.

What changes with the planFreePro · $29Max · $79Business · $159
Market-data API (/api/v1/*) with your key
60 / min per IP without a key, on every plan
120 / min600 / min2000 / min5000 / min
API keys per account31030100
Open positions at once502005001000
Books - a separate journal, balance and report per strategy
the limit most people actually reach
152050
Webhooks - signed, retried, delivered while you are offlinenone31550
AI market read /v1/ainone50 a day200 a day500 a day
Trade history kept
what /v1/trades, the report and the equity curve can still see
30 days90 days90 days90 days
Requests per minute, per key
measured: the busiest production bot uses 46, and nothing has ever been limited
12060020005000
Trading report /v1/reporttotals + skill score
up to 30 days back
+ breakdowns & findings
up to 90 days back
+ breakdowns & findings+ breakdowns & findings

On every plan, Free included: the whole trading engine - market, limit and stop orders, trailing stops, partial closes, dry run and replay - the WebSocket stream, the MCP server, the fee schedule of 9 real venues, $1 to $100,000 of margin per trade at up to 1000× leverage, and every keyless market-data endpoint at full speed. The paid plans move ceilings. They do not unlock the product.

Premium is a different product. A MarginPad Premium subscription unlocks features on the website - the exclusive chart indicators, the liquidation heatmap, Ask-AI on charts - and since 15 September 2026 it does not change a single API limit. These plans do not unlock those site features either. Buy whichever one you actually use.

Plans are month by month, paid in crypto or straight from your MarginPad rewards balance, and a month bought early is added to the one you have rather than replacing it. Your live ceilings are always in GET /api/bot/v1/usage - limits, features, plan_until - so a bot can read them instead of guessing.

What you are buying, in writing. Section 9 of the terms covers the API on its own: what a plan includes and what happens the day it ends, that nothing renews automatically and there is nothing to cancel, what we do and do not commit to on availability (there is no SLA - we say so, and we say what we do commit to instead), thirty days’ notice before any breaking change, and that you may build and sell commercial products on this API without asking us.

Your bot is told before the plan lapses, not after. Every keyed response carries X-MP-Plan, and inside the last fourteen days it also carries X-MP-Plan-Expires and X-MP-Plan-Days-Left. Log them and an unattended bot can warn you itself; the same countdown is on this page and in /v1/usage. A plan that runs out drops the key to Free - it never stops answering.

Endpoints

Twenty-four of them. Everything is JSON over HTTPS, CORS on, one header. Swap v1 for v2 in any path for the {ok,data,ts} envelope.

Market data - no key required
GET/v1/price?symbol=BTCLive price for any USDT pair. No key needed - try it right now in your browser.
GET/v1/klines?symbol=BTC&interval=60OHLC candles for your strategy (intervals: 1, 5, 15, 30, 60, 240, 1440 minutes). No key needed. Up to 1000 candles per call, newest last - about 16 hours on 1m, 3 days on 5m, 10 days on 15m, 41 days on 1h, 166 days on 4h and roughly 3 years on 1d. Pass &end=<unix ms> to page further back.
GET/v1/marketsEverything tradable, with asset_class, max_leverage and taker_fee_pct per symbol. Filter with ?class=crypto|stock|forex|metal|index. Call this once at startup instead of discovering limits by trial and error.
GET/v1/time?client_ts=…Server clock. No key needed. Pass your own unix ms and you get drift_ms back. A bot bucketing candles against a drifting local clock builds bars nobody else sees, and that is miserable to debug from the outside.
bars = get(f"/api/bot/v1/klines?symbol=BTC&interval=1")
while len(all_bars) < wanted:
    end_ms = bars[0]["time"] * 1000          # oldest candle in hand
    bars = get(f"/api/bot/v1/klines?symbol=BTC&interval=1&end={end_ms}")
    if not bars: break
    all_bars = bars + all_bars
Trading
POST/v1/openOpen a simulated position at the live price. Body: {"symbol":"BTC","side":"long","margin_usd":100,"leverage":20,"sl":58000,"tp":66000} (sl/tp optional). Returns the position incl. its liq_price. Add "dry_run":true to get the priced trade back - entry, quantity, liquidation price and distance, the open fee and the full round trip - without writing anything. Use the dry run to size a position or to unit-test your bot against the real engine.
POST/v1/open trail_pcttrail_pct sets a stop that follows the best price seen since entry at that percent distance, ratcheted on the server: from 1-minute candle extremes on the minute sweep and from the live price on every /positions read, so it keeps moving while your bot is offline and never loosens. A long opened at 60,000 with trail_pct: 1 starts with its stop at 59,400; when price prints 63,000 the stop is 62,370. Positions carry trail_pct and trail_hwm.
POST/v1/open type:"limit"The order rests on the server instead of filling now: {"symbol":"BTC","side":"long","type":"limit","limit_price":58000,"margin_usd":100,"leverage":20}. It fills at your price, not at the price the engine noticed the cross - the fill is found from the 1m high/low, so a wick that retraced between two checks still counts. Your bot does not have to be running. A limit long must be below the market and a limit short above it; otherwise you get limit_marketable rather than a silent market fill.
POST/v1/open type:"stop"A breakout entry: a long above the market or a short below it, e.g. {"symbol":"BTC","side":"long","type":"stop","limit_price":66000,"margin_usd":100,"leverage":10}. Same engine as a limit order - it fills at the level from 1m candles, with the browser closed - but the wrong side is refused (stop_wrong_side) so a stop and a limit can never be confused. sl, tp, trail_pct, client_order_id and dry_run all apply.
POST/v1/closeClose a position at the live price. Body: {"id":"bp...","pct":50} - pct is optional (default 100); partial closes split the position exactly like on the site.
POST/v1/close_allPanic button - closes every open position at the live price in one call.
POST/v1/sltpMove the stop-loss, take-profit or trailing stop on an open position without closing it. Body: {"id":"bot...","sl":63500,"tp":66000,"trail_pct":1.5} - send null to clear one, omit a field to keep it. Levels are checked against the entry side. Before this existed you had to close and reopen to move a stop, which changed your entry and paid a full round trip.
Resting orders
GET/v1/ordersYour resting orders, each with type: "limit" | "stop", plus the last 20 that filled, expired or were cancelled - a filled one carries the position_id it created. Orders are good until cancelled, expire after 30 days, and 20 may rest per account.
POST/v1/modify_orderChange a resting order in place: {"order_id":"lo...","limit_price":58500,"sl":57000,"tp":62000,"margin_usd":150,"leverage":10,"trail_pct":1} - send only what changes. Before this the only way was cancel and re-place, which lost the order's client_order_id. The direction is re-derived from the market now and the candle watermark restarts, so a moved level can never fill on a bar printed before the change. A filled, cancelled or expired order answers 409.
POST/v1/cancel_orderCancel one resting order. Body: {"order_id":"lo..."}. Already filled or cancelled comes back 409, so a retry can never undo a fill.
Account and state
GET/v1/positions?status=openAll your positions with live mark_price and unrealized_pnl_usd; crossed liquidations and SL/TP are settled automatically. ?status=open|closed trims the body to what your loop actually reads; ?since=<unix ms> and If-None-Match (304 when nothing changed) are supported too.
GET/v1/accountAccount summary: open positions, margin in use, live unrealized P&L, lifetime realized P&L, wins/losses, win rate, plus balance_usd, equity_usd, free_margin_usd and return_pct.
GET/v1/balanceJust the money: starting balance, balance, equity, margin in use, free margin, unrealized P&L. Cheaper than /account when that is all your sizing logic needs.
GET/v1/trades?limit=100&before=…The full closed-trade ledger with paging. /positions is capped at 100 rows; this pages through the whole retention window - follow next_before until it comes back null. Use it to compute strategy statistics on the complete record instead of a truncated tail.
GET/v1/accountsEvery book on the account with its lifetime numbers and the keys bound to it.
GET/v1/equity?days=30&step_min=60The equity curve of this book, bucketed, with max_drawdown_pct and the live unrealized point at the end.
POST/v1/resetThis book back to $10,000 with {"confirm":true}: closes archived, orders cancelled. Refused with 409 open_positions_exist while anything is open.
GET/v1/usageYour plan, your ceilings, what is left in the current window, and usage_30d - calls per day by endpoint plus refused (the 429s), so a bot can see how close it runs to its limit.
Costs, intelligence and delivery
GET/v1/feesWhose fee schedule your fills are charged at: the venues we model with taker, maker, our referral discount and the effective rate, plus your current default. POST {"venue":"bybit"} sets the account default; fee_venue on /open overrides it for one position. See venue fees.
GET/v1/report?days=30The trading report for the account behind the key, measured from its own closed trades: totals and the skill score on every plan; win rate and return by coin, leverage band, side, hour and day plus the written findings on API Pro and Max (locked[] names what is withheld). Every finding carries the n it rests on and says nothing under 8 trades.
POST/v1/aiAPI PROThe chart panel's AI read, from the API: {"symbol":"BTC","interval":"60","question":"Is this leaning long or short?"}. Same model, same prompt and the same 50-a-day quota as Ask-AI on the site. You get the answer, a parsed plan (bias, entry, stop, targets) when the model saw a setup, and the brief it reasoned over - price, move, swing high/low, moving averages, RSI, ATR, Bollinger, recent closes - so you can log exactly what it looked at. Educational, not financial advice.
GET/v1/webhooksAPI PROTrading events pushed to your URL, seconds after they happen, whether or not your bot is connected - see webhooks. POST {"act":"add","url":"https://…"} registers one, {"act":"test","id":…} sends a ping and returns the status your server answered, {"act":"delete","id":…} removes it.
POST/v1/replay2.6Run your bot through one past UTC day on our own 1-minute candles - see replay.

Machine-readable: OpenAPI 3.1 spec with full request and response schemas · an interactive reference you can fire calls from · the changelog (JSON).

How the engine works

Fills and prices

Orders fill instantly at the real live price (multi-exchange feed - the same prices as our Paper Trade). API fills carry no slippage (fills on the website itself do), so treat entry prices as slightly optimistic.

Trading fees - read this before you measure a strategy

Every position pays a taker fee on both sides, settled into pnl_usd at close. It is charged as a round trip: fee = qty × (entry + exit) × rate. Funding is accrued on positions held across funding marks and deducted the same way. Open positions already show P&L net of the round trip in unrealized_pnl_usd, so what you see is what settles.

MarketFee per sideRound trip on $10,000 notional
Crypto perps0.055%$11.00
Stocks & ETFs0.02%$4.00
Metals & indices0.015%$3.00
Forex majors0.008%$1.60

Prefer the exact bill of the exchange you will run on? Charge the paper account at its schedule, referral discount included. Above roughly 180× leverage the rate tapers so the round trip can never exceed 20% of your margin. GET /v1/markets returns the exact rate and leverage cap for every symbol. Short-horizon strategies live or die on this number - a scalp that clears less than the round trip is a loser no matter how often it is right.

Margin, liquidation, SL/TP, trailing

Isolated margin. Losses are capped at your margin. The liquidation price uses a 0.5% maintenance margin rate; when the live price crosses it the position settles at the liq price automatically. Stop-loss and take-profit orders execute automatically too - when the price crosses your sl or tp (checked against 1-minute candle wicks on the minute sweep, so a spike that retraced still counts), the position settles at that level. A trailing stop ratchets from the best price seen: on the sweep it is moved by each candle's extreme after that candle has been checked against the stop in force, so a bar can never stop you out at a level it created itself.

Balance and equity

Every paper account starts at a nominal $10,000. /v1/account and /v1/balance report balance_usd, equity_usd and free_margin_usd so you can express results as a return instead of a bare dollar figure. This balance is a scorecard, not a constraint: margin is not debited from it and an open is never rejected for lack of funds (margin_enforced: false). Size your positions yourself if you want the constraint to be real.

Rate-limit headers

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (epoch seconds); a 429 adds Retry-After. Limits are counted per key, so a second key gets its own budget. X-RateLimit-Scope says whether the key's limit or the per-IP one applied.

Your bot's trades on the site

Positions opened via the API live on your account server-side, so they show in My Trades, count for the season boards and XP, and feed your trading report - the same report GET /v1/report returns to the bot.

The streaming API is free on purpose

Putting it behind a paywall would push everyone else back onto polling, which costs us far more than the stream does - so the incentives line up: use the WebSocket, we both win. Market data stays keyless and free permanently; a key only raises its ceiling.

Fill realism, because a backtest that flatters you is worth nothing

Two things in this engine are deliberately generous, and it is better to name them than to let you find out on a live account. A market order fills at the live price, so a new position reads 0.00 unrealized and the entry line sits exactly where you asked for it. And maintenance margin is a flat 0.5%, whatever the position is worth. For learning to trade, both are the right call. For proving out a strategy they are optimistic, and the bigger the size the more optimistic they get.

So they are switches, not assumptions. Off by default - nothing you have already built moves - and on, the fill price and the liquidation price behave the way a real book and a real risk-limit table make them behave.

GET  /api/bot/v2/realism                     // your setting, the venue table and the whole tier ladder
POST /api/bot/v2/realism {"slippage": true, "margin_tiers": true, "margin_venue": "binance"}

# or per call, so one strategy can be tested both ways without touching the account
POST /api/bot/v1/open {"symbol":"BTC","side":"long","margin_usd":100,"leverage":10,
                       "slippage": true, "margin_tiers": true, "mmr_pct": 0.4}

Slippage

The fill moves against you by what walking a real order book costs: 0.01% on the majors, 0.05% on thinner crypto, 0.005% on forex, 0.02% on stocks, metals and indices. A buy pays up, a sell sells down - never the other way.

Margin tiers

Maintenance margin rises with notional size, the way an exchange risk-limit table does, so a large position is liquidated sooner - a higher maintenance requirement tolerates less adverse move before it closes you, which is exactly what a venue wants on size. This is our published model rather than any one venue's table, and GET /realism returns the whole ladder - you should never have to reverse-engineer it from a liquidation price.

Margin venue

Which venue's published maintenance-margin rate to build on: Binance 0.40%, Bybit 0.50%, MEXC 0.10%, Kraken 0.60%, Hyperliquid 1.25%, Coinbase 1.33%. It moves only where the liquidation price sits - never size, fees or P&L.

A position keeps the maintenance margin it was filled with, so turning this on never rewrites a trade you already hold, and every close records what it ran with. That is where the arena's Fills column comes from - a board that ranks bots ought to say which of them were on easy mode.

Fees as on the exchange you will actually use

By default every paper fill pays the MarginPad rate: 0.055% per side on crypto, both legs, charged as a round trip at close. That is a fair average, but it is nobody's exact bill. If you already know where the bot will run for real, charge the paper account at that venue's schedule - its published taker rate, less the referral discount a MarginPad sign-up gets there - so the forward test settles the way the live account will:

GET  /api/bot/v2/fees                       // the table below + your current default
POST /api/bot/v2/fees  {"venue":"hyperliquid"}   // account default: every open from now on, site and API
POST /api/bot/v2/open  {"symbol":"BTC","side":"long","margin_usd":100,"leverage":10,"fee_venue":"bybit"}   // just this one
POST /api/bot/v2/open  {..., "dry_run":true}      // -> fee_venue, taker_fee_pct, fee_vs_marginpad_default_usd
VenueTakerOur referral discountYou pay per side
Bybit0.055%20%0.044%
Binance0.050%20%0.040%
Bitget0.060%20%0.048%
Gate0.050%20%0.040%
Hyperliquid0.045%4% (code MARGINPAD)0.0432%
OKX · Kraken0.050%-0.050%
MEXC0.020%-0.020%
KuCoin0.060%-0.060%
MarginPad defaultno venue chosen0.055%

Rates are the venues' published base tier and the discounts we state on /exchanges/; the API returns them with source so you know they are advertised numbers, not measurements. Both legs pay the taker rate (the paper engine fills at market; a maker rebate on a resting entry is not modelled). Crypto perps only - stocks, forex, metals and indices keep the MarginPad class rates whatever the venue. Open positions keep the rate they were filled with; the extreme-leverage cap (a round trip never above 20% of margin) still applies. The same choice is the "Fees as on" selector in the Paper Trade form, so a bot and its owner's manual trades pay identical fees.

Writing a bot that does not break

Always send client_order_id on open. This is the single most important line in these docs. Without it, a request that times out on the network leaves you guessing: did the position open or not? Retry and you may have two. With it, the retry returns the position the first call created, flagged idempotent: true:

POST /api/bot/v2/open
{"symbol":"BTC","side":"long","margin_usd":100,"leverage":5,"client_order_id":"sig-2026-08-19-1403"}

// first call            -> {"ok":true,"data":{"position":{"id":"bot...", ...}}}
// same call retried     -> {"ok":true,"data":{"position":{"id":"bot...", ...},"idempotent":true}}

Use a value your strategy can regenerate deterministically - a signal id, or symbol plus candle timestamp. Ids are remembered for 7 days.

Closing takes one too. Send client_order_id on /close and a retry reports what the first call did (idempotent: true) instead of an error you have to interpret.

Send symbol when you close. Every /positions row already carries it, so this is free to add and it measurably shortens the close:

POST /api/bot/v2/close
{"id":"bot...","symbol":"BTC"}    // one price fetch, one round trip
{"id":"bot..."}                   // server must first look up which symbol to price

Our trading store is a single region-pinned instance, so every extra hop costs what the round trip to it costs from where you are. Measured on 2026-08-19: about 20 ms from Europe but 198 ms median and 368 ms at the 95th percentile from Singapore. A cold price feed adds around 400 ms on top, and without the hint a close on a liquid pair could end up waiting on an illiquid one in the same batch. With the hint a close is one hop plus one warm price. A wrong or stale symbol costs nothing - the server notices and falls back.

Do not burn your rate budget on polling. Most bots spend the bulk of their calls asking "has anything changed". Two things make that nearly free:

# 1. state changes: send back the ETag, get 304 and an empty body when nothing moved
curl -H "X-API-Key: $KEY" -H 'If-None-Match: W/"1a2b3c-4"' \
     https://marginpad.io/api/bot/v1/positions

# 2. live prices: /price is KEYLESS, so it costs nothing against your 120/min
curl https://marginpad.io/api/bot/v1/price?symbol=BTC

The ETag covers structural state - position ids, status, quantity, stop, target, exit - and deliberately not the mark price, which moves every tick and would make the ETag never match. So: poll /positions for "did my stop fire", poll the keyless /price for P&L. Watch X-RateLimit-Remaining and back off on Retry-After.

WebSocket - stop polling entirely

Positions and prices are pushed to you, so you find out your stop fired within a couple of seconds instead of on your next poll:

wss://marginpad.io/api/bot/v2/stream?api_key=mpb_…

<- {"type":"welcome","data":{"channels":["positions","prices"],"tick_ms":2000},"ts":…}
<- {"type":"snapshot","data":{"positions":[…]},"ts":…}
<- {"type":"position","data":{"event":"closed","position":{…,"pnl_usd":-1.24}},"ts":…}
<- {"type":"prices","data":{"BTC":64770.1},"ts":…}

-> {"op":"subscribe","channels":["positions"]}     // prices off, events only
-> {"op":"ping"}                                    // <- {"type":"pong"}

Events: position with event: opened | updated | closed fires when something actually changes - a fill, a stop moving, a close, a liquidation. prices carries the mark price of your open symbols each tick. Mark-price movement alone never raises a position event, so the channel stays quiet when nothing has happened.

Stops settle faster while you are connected. The stream drives the same server-side SL/TP/liquidation sweep the REST path uses, on its ~2s cadence, instead of leaving your position to the periodic sweep.

Up to 3 concurrent sockets per account, shared across your keys. Sockets are recycled after 6 hours - reconnect and you get a fresh snapshot. If the connection drops, treat the next snapshot as the truth rather than replaying missed events.

Webhooks - events pushed to your server API PRO

Measured before this existed: 89.5% of every call to this API was polling /positions and /account for events. A webhook turns that around - we POST to you when something actually happens, with the same Position shape a poll would have returned, and it works while your bot is offline, restarting or deployed somewhere that cannot hold a socket.

POST /api/bot/v2/webhooks
{"act":"add","url":"https://bot.example.com/marginpad","events":["position.closed","position.liquidated","order.filled"]}

-> {"ok":true,"data":{"webhook":{"id":"wh…","secret":"whs_…","events":[…],"active":true}}}

# what arrives at your URL, seconds after the event:
POST https://bot.example.com/marginpad
X-MP-Event: position.closed        X-MP-Delivery: 1841
X-MP-Timestamp: 1789124656008      X-MP-Signature: sha256=3763dc5d…
{"event":"position.closed","ts":1789124656008,"hook_id":"wh…","data":{"id":"bot…","symbol":"BTC","status":"closed","pnl_usd":-0.08,"close_reason":"SL hit",…}}

Events: position.opened · position.updated (a stop or target moved) · position.closed · position.liquidated · order.filled (carries the position it created) · order.expired · order.cancelled. Omit events to receive all of them. Site-side trades on the same account fire them too.

Verify every delivery. X-MP-Signature is sha256=HMAC_SHA256(secret, raw body) with the secret returned when you added the hook. Compare it before you parse the body; both SDKs ship verify_webhook / verifyWebhook. Answer 2xx within 6 seconds - anything else is retried 5 times with backoff (30 s, 1, 2, 4 min), and a hook is paused after 25 consecutive failures; GET /webhooks shows active, consecutive_failures and last_error so you can see why. Three hooks per account on Pro, fifteen on Max, fifty on Business; deliveries are best-effort, the WebSocket snapshot and /positions remain the source of truth.

Try it before you write a receiver: register https://marginpad.io/api/whsink/<any-token-you-choose> as the URL, then open GET on that same address - it echoes the last 20 deliveries (headers and body) for 15 minutes. No auth; the token is the secret.

Books - one strategy, one clean result 2.5

A book is a separate journal, balance, report and equity curve inside your account. Mint a key with a book name and every call made with that key trades that book; your main account and your other books never see it. Free accounts get one book besides the main account, API Pro five, Max twenty and Business fifty.

POST /api/bot/key  {"act":"create","name":"rsi bot","book":"rsi"}   // key bound to the book "rsi"
GET  /api/bot/v1/accounts        // every book with lifetime numbers and its keys
GET  /api/bot/v1/account         // "account":"rsi" on a book key, "main" otherwise
POST /api/bot/v1/reset  {"confirm":true}   // this book back to $10,000: closes archived, orders cancelled
GET  /api/bot/v1/equity?days=30&step_min=60   // equity curve + max_drawdown_pct

Reset is refused with 409 open_positions_exist while anything is open: close first, then reset. The archived trades are kept (never deleted) but the report, the ledger, /trades and the equity curve start from the reset, and a browser that still holds the old journal cannot push it back. Webhooks fire on the account's hooks with "account":"rsi" in the payload, so one receiver serves every book.

Replay - run your bot through a past day 2.6

Backtest with the code you will run live. Start a replay of one past UTC day and the same routes your bot already calls act on a separate replay journal, priced from MarginPad's own 1-minute candles at the cursor. Stops, targets and liquidations are checked on every candle between two of your calls, on the high and the low, with the same fee and funding math as live. Nothing from a replay reaches the boards, the arena or your report.

POST /api/bot/v1/replay  {"symbol":"BTC","day":"2026-09-11","speed":120}   // 120 market seconds per real second: a day in 12 minutes
GET  /api/bot/v1/replay?interval=5&bars=120       // cursor, price, progress, candles up to the cursor
POST /api/bot/v1/open    {...}                        // acts on the replay book at the cursor price while the replay runs
POST /api/bot/v1/replay  {"act":"stop"}               // close at the cursor, get the summary, empty the replay journal

Speed goes from 1 (real time) to 600 (a day in 2.4 minutes). One replay per key at a time, crypto only, market orders only for now: read the replay price and candles from GET /v1/replay, since the keyless /v1/price and /v1/klines keep answering live data.

Managing your keys

You can hold several keys on one account - keep a backtest bot and a live bot apart, and revoke one without touching the other. Usage and rate limits are counted per key, so each gets its own budget and its own line in your stats. Signed in on the site, from the browser:

POST /api/bot/key  {"act":"list"}                          // all your keys
POST /api/bot/key  {"act":"create","name":"backtest-bot"}   // new key
POST /api/bot/key  {"act":"rename","key":"mpb_...","name":"live-bot"}
POST /api/bot/key  {"act":"revoke","key":"mpb_..."}         // usage history is kept

A revoked key returns 401 revoked_key. Keys are secrets - anyone holding one can trade your paper account. Create and revoke them at the top of this page.

Bot arena - a public scoreboard for bots

Every account or book with at least five closes opened through the API this 14-day season is ranked on /arena/ by realized P&L net of fees and funding, with win rate, return on the $10,000 scorecard, average ROE and liquidations. No prizes and nothing to sign up for: trade through the API and the board picks you up. JSON: GET /api/arena.

Use it from Claude, ChatGPT or Cursor

MarginPad runs a remote MCP server, so an AI assistant can read markets and trade your paper account directly - no glue code:

https://marginpad.io/mcp

Add it as a remote MCP server in your client and set the header X-API-Key: mpb_… if you want the paper-trading tools. Market-data tools need no key. Twenty-seven tools are exposed: prices, candles, markets, screener, funding, open interest, liquidations, fear and greed, the economic calendar, two calculators, and paper open (with trailing stops and dry run) / limit and stop orders / modify / cancel / close / sltp / positions / orders / balance / trades / report / fees / accounts / reset / equity / replay. GET /mcp in a browser returns the server descriptor.

Response format - v1 and v2

/api/bot/v1/* is frozen. Its response bodies will not change; bots written against it keep working indefinitely, and it still receives correctness fixes.

/api/bot/v2/* is the same API with the envelope our data endpoints already use - one predictable shape for success and failure, so you write the parsing once:

{ "ok": true,  "data": { ... }, "ts": 1787200000000 }
{ "ok": false, "error": { "code": "unknown_symbol", "message": "No price feed for that symbol...", "symbol": "FOO" }, "ts": 1787200000000 }

Same paths, same parameters, same key - swap v1 for v2 in the URL. Error code values are stable identifiers; message is for humans and may be reworded.

Error codes

On /v2, failures are always {"ok":false,"error":{"code","message"},"ts"}. The code is a stable identifier you can branch on; the message is prose and may be reworded. Some errors carry the value you need to correct the call - sl_wrong_side and tp_wrong_side include the live price.

CodeHTTPWhat to do
missing_api_key / invalid_api_key401Send a valid key in X-API-Key.
revoked_key401Create a new key; this one was revoked.
rate_limit429Sleep until Retry-After. Do not hot-retry.
unknown_symbol404Check /v1/markets.
symbol_required, margin_usd_min_1, margin_usd_max_100000, id_required400Fix the request body.
sl_wrong_side / tp_wrong_side400The response carries live - put the level on the correct side of it.
stop_wrong_side400A stop entry waits on the breakout side (long above, short below). Use type:"limit" for a pullback entry.
trail_pct_invalid400trail_pct is a percent between 0.05 and 50; it is refused, never silently clamped.
nothing_to_modify400/modify_order needs at least one field.
plan_required402Webhooks and /v1/ai need a paid plan. Everything else on this page is on Free. The body carries plan_needed.
premium_required402Legacy code for plan_required, still sent so a 2.3 bot recognises it.
too_many_webhooks409Delete one first - three hooks on Pro, fifteen on Max, fifty on Business.
bad_url / bad_event400Webhook URLs are https on a public host; event names are listed in GET /webhooks.
ai_quota429The daily AI quota (shared with the site) is used. Resets 00:00 UTC.
too_many_open409You are at your plan's open-position ceiling. Close something.
already_closed400Harmless on a retry - the position is already settled.
no_price400No live price this instant; the position stays open. Retry.
unavailable503Transient. Retry with backoff.

Free liquidation data API LIVE DATA

Real-time liquidations aggregated from Binance, Bybit, OKX, BitMEX, Hyperliquid and Bitfinex - the data behind our Rekt feed and Liquidation Heatmap, exposed as free JSON. Comparable data is normally paywalled at $300+/month elsewhere; here it needs no API key at all. Edge-cached and rate-limited, so it comfortably handles polling bots.

GET/api/v1/feedThe newest liquidations across ALL tracked symbols - {events:[{ts, exchange, symbol, side, price, qty, notional}]}. Events land here seconds after they happen (3s edge cache). Poll every 3-5s.
GET/api/v1/liquidations/live?symbol=BTC&limit=400Raw recent liquidation events for one symbol (BTC, ETH, SOL, XRP, DOGE, BNB, ADA, LINK, AVAX, LTC). side is long_liquidated or short_liquidated, notional is the USD size.
GET/api/v1/liquidations/recent?symbol=BTC&minutes=1440Time-bucketed histogram (long vs short $ per bucket) for charts - the exact source of our heatmap's stats. minutes: up to 43200 (30 days).
GET/api/v1/clusters?symbol=BTCModeled liquidation clusters: price levels where liquidation liquidity is estimated to sit right now - {clusters:[{price, side, est_notional}]}. Refreshes continuously from live order flow.

Attribution appreciated (link marginpad.io) but not required. Coverage is ~70%+ of the market's liquidation flow - the venues that move price. Educational use; no uptime SLA. Just want market data? The whole keyless surface is documented on the free crypto data API page.

Backtest, paper, live - in that order

Most crypto trading bots that look profitable in a backtest lose money the first week they go live. Backtests replay clean historical candles - they cannot catch a race condition in your order logic, a position-sizing bug that only shows up after a losing streak, or leverage that quietly grows past what your margin survives. Forward testing runs the same bot, the same code path, against real live prices in real time. If the strategy has a flaw, it shows up here, where a bug costs you nothing.

BacktestingPaper trading (this API)Live trading
PricesHistorical candlesReal live prices, real timeReal live prices
RiskNoneNone - simulated dollarsReal money at stake
Catches logic bugsRarely - replayed data hides themYes - same code path as liveYes, but each bug costs money
Overfitting checkNo - you tuned on this dataYes - unseen future dataYes
CostFreeFree to use - fees & funding simulatedFees + losses + funding
Best forFiltering ideas fastProving the bot before funding itA strategy already proven twice

The middle step is the one most people skip - and the one that would have saved them. Backtest an idea here, forward-test it on this API, then fund an account.

Step three: where to run the bot for real

Everything on this page settles in simulated dollars. Once the bot has survived weeks of forward testing, the same code needs an exchange API to place real orders. Two venues we point bot builders at, for different reasons:

HLHyperliquidon-chain · API-first

Perps on their own chain with a public REST and WebSocket API, sub-accounts that each get their own API wallet (the bot never holds your main key), hourly funding and a book anyone can audit on-chain. The closest thing to running against this API with real money.

4%off trading fees
with code MARGINPAD
Join Hyperliquid →
BBybitcentralised · deepest books

REST and WebSocket shapes close to what you just wrote, a separate key per sub-account, and one of the feeds our own prices come from. The default when you want a centralised book with fiat on-ramps.

Open a Bybit account →
Referral links - MarginPad may earn a commission if you open an account, at no cost to you; the Hyperliquid discount is their referral discount. The paper API stays free either way, and nothing here needs an exchange account. Hyperliquid is not available to US persons under its terms.

FAQ

Is there a free liquidation data / heatmap API?

Yes - MarginPad exposes real-time liquidations aggregated from 9 exchanges as free JSON, no key needed: /api/v1/feed, /api/v1/liquidations/live, /api/v1/liquidations/recent and /api/v1/clusters. Comparable feeds are usually $300+/month. See the liquidation data section.

Is the trading bot API really free?

Yes. It is a paper-trading simulator - positions use real live market prices but no real money. Sign in with an email, generate an API key and start testing immediately. Paid plans only raise the ceilings.

Which programming languages can I use?

Any language that can make an HTTPS request - Python, JavaScript/Node.js, Go, Rust, PHP, or plain curl. Standard REST, JSON responses, one X-API-Key header.

Which coins can my bot trade?

Any USDT pair with a live price on major exchanges - BTC, ETH, SOL and hundreds of altcoins, plus US stocks, forex majors, metals and indices as perps. Check a symbol with GET /api/bot/v1/price?symbol=SOL.

Do I need an exchange account or a deposit?

No. No deposit, no wallet, no KYC. You only sign in with an email so your API key and positions persist across sessions.

Does MarginPad Premium include the API?

No. The Bot API has its own plans and a Premium subscription does not change any API limit. Premium unlocks features on the website; the API plans unlock API limits. They are bought separately.

Guides and tutorials