BOTS & TRADINGVIEW

Python Crypto Trading Bot API: Build a Prop Firm Bot on the QATS Trader API

A step-by-step Python walk-through of the QATS Trader API, from a free sandbox key to a first order, with the risk checks that still apply.

By the QATS team6 min read9 sections

A Python crypto trading bot API integration on a prop account differs from one on an exchange in a way that matters: every order your script sends is checked against the account’s rules before it fills. This tutorial builds a small bot on the QATS Trader API with the requests library, using the endpoint paths and headers as the API reference documents them.

You will create a sandbox key, read the account, check an instrument, place an order safely, handle refusals and timeouts, then move to a paid evaluation, which on QATS is simulated.

01/ 09

Python crypto trading bot API basics: what the QATS Trader API is

The Trader API is a REST interface, version 1, for a trader’s own accounts: market data, account state, orders, positions, fills and a durable event stream. It uses the terminal’s execution engine, the accepted plan rules and the same risk controls, and it has no access to payouts, billing or personal data.

It is not a Binance or Bybit clone: exchange SDKs built around signed API-key headers are not drop-in compatible, because QATS uses a Bearer token. prop.qats.us/developers also publishes an OpenAPI description and a Postman collection.

Prop firm API policies vary widely. In our comparison, checked 2026-10-29, HyroTrader lists “Allowed via API; HFT and copy trading banned”, Topstep “API bots allowed via TopstepX (not on Live Funded)” and BitFunded “Banned”.

Related guideProp firms that allow bots and API trading

02/ 09

Step 1: Create a sandbox account and key

  • Sign up and verify your email; the sandbox needs no challenge purchase.
  • Open Sandbox account → Keys & connections → Trading API, pick Crypto or TradFi and a Binance or Bybit price venue, and create it.
  • Choose Create sandbox key, name it after the bot and pick Read and trade; you can restrict it to your server’s IP address.
  • Save the key once in a secret store. It cannot be shown again and never belongs in code, a URL or a log.
EnvironmentBase URLKey prefix
Sandbox (virtual funds)https://prop.qats.us/api/sandbox/v1qats_sandbox_
Purchased accountshttps://prop.qats.us/api/v1qats_trade_
Trader API environments; sandbox and purchased-account keys are not interchangeable

Help CenterCreate a free API sandbox

03/ 09

Step 2: Authenticate and read the account first

Every request carries the key as a Bearer token. Start with a read, and check that the response names the sandbox before anything trades:

import os, uuid, requests

BASE = "https://prop.qats.us/api/sandbox/v1"

H = {"Authorization": "Bearer " + os.environ["QATS_TOKEN"]} # qats_sandbox_… key

acct = requests.get(BASE + "/trading/account", headers=H, timeout=20).json()

print(acct["environment"], acct["risk"]["riskSummary"]["lossBuffer"])

The response carries balance, equity, the accepted rules, the risk session and the key’s permissions; risk.riskSummary holds lossBuffer (equity minus the loss floor) and dailyLossRemaining. Read limits and request budgets (capabilities.rateLimits) from here instead of hard-coding them.

04/ 09

Step 3: Check the instrument and size the trade yourself

spec = requests.get(BASE + "/trading/instruments", params={"symbol": "BTCUSDT"}, headers=H, timeout=20).json()["instruments"][0]

With a symbol, /trading/instruments returns the tick size, quantity step, minimum quantity and lot size, plus orderCapacity, the largest order each side could take now. Round sizes to quantityStep; invalid increments are refused.

There is no per-trade loss limit, and an entry is admitted without a stop. Size each trade from your risk per trade and stop distance; orderCapacity is a margin ceiling, not a target.

Related guidePosition sizing on a funded account

05/ 09

Step 4: Place an order with an Idempotency-Key

Every write needs an Idempotency-Key header, your own unique ID for that command, saved before you send it. A write without one is refused with IDEMPOTENCY_KEY_REQUIRED.

cid = str(uuid.uuid4()) # store it before sending

order = {"symbol": "BTCUSDT", "side": "BUY", "type": "MARKET", "quantity": 0.001, "bracket": {"enabled": True, "stopTicks": 100, "targetTicks": 200}}

r = requests.post(BASE + "/trading/orders", json=order, headers={**H, "Idempotency-Key": cid}, timeout=20)

A 201 means filled at once, 202 resting. The bracket attaches a stop and target in whole ticks; the values here are illustrative, so work out your own from the stop distance and tickSize. POST /trading/positions/{id}/protection sets or moves them later with stopPrice and targetPrice; /trading/positions/{id}/close and /trading/flatten exit.

Add referencePrice, the price your signal saw, to a market order and the fill is refused with PRICE_REFERENCE_MISMATCH beyond maxDeviationPct, 0.1% by default.

06/ 09

Step 5: Handle refusals, rate limits and timeouts

Errors share one shape: error.code, error.message and optional error.details. Branch on error.code, never on the message text, and never resend a refused order in a loop.

After a timeout, call GET /trading/commands/{id} with the original ID and the same key: confirmed returns the recorded result, and unconfirmed is not proof of failure. Never retry an uncertain trade under a fresh ID; an exact retry replays the recorded result instead of trading twice.

Rate limits count every entry that reaches the quota check, even refused ones, while exits have a separate budget. Honour Retry-After and back off.

ResponseMeaningWhat the bot should do
422 DAILY_LOSS_LIMIT, PERSONAL_DAILY_STOPA loss rule blocks new entriesStop opening trades and read access.tradingAvailability
409 PRICE_REFERENCE_MISMATCHThe market moved past your toleranceRe-read the quote and decide again with a fresh price
409 WORKING_ORDER_LIMIT50 orders already rest on the accountCancel orders before placing more
422 INSUFFICIENT_MARGINNot enough free margin for the orderReduce size or close exposure
429 TRADING_RATE_LIMITEDThe request budget is used upWait the Retry-After seconds, then back off
503 STALE_MARKET_DATAThe quote was too old to fill againstRetry the same command after a moment
Timeout or lost responseThe outcome is unknownGET /trading/commands/{id} with the same key and ID
Responses a prop firm bot should handle on the QATS Trader API

Help CenterRecover after an API timeout

07/ 09

Step 6: Follow fills with the event stream

GET /trading/events/stream is a server-sent events stream of order, position and execution events. Save the last event ID only after handling the event, reconnect with Last-Event-ID, and keep handlers idempotent, since a crash can redeliver an event. A 2xx response proves acceptance, not a full fill.

08/ 09

Prop firm API rules: the risk checks still apply

API orders are not a side door. Admission checks the maximum loss, the daily loss limit, your personal daily stop, margin, the price range and quote age exactly as for a manual order. On a QATS Crypto evaluation the rules are a 10% profit target, a 4% daily loss limit, a 6% end-of-day trailing maximum loss that stops trailing at the starting balance, a 40% consistency rule and 2 minimum trading days, with up to 20× on BTC and ETH, 10× on large caps and 5× on other coins.

Opposite positions on the same market across your own accounts are refused with CROSS_ACCOUNT_HEDGE, and each key belongs to one account. Your own limits (a daily stop, a maximum margin, a maximum number of trades per day) are extra restrictions on top of the plan rules, never a replacement, and the server enforces them.

On QATSRead the QATS rules

09/ 09

Moving from the sandbox to an evaluation

Evaluations and initial funded accounts are simulated; payouts are real payments, subject to the plan’s eligibility rules and identity verification.

  • Create a purchased-account key (qats_trade_…) for the exact evaluation and switch BASE to https://prop.qats.us/api/v1.
  • Read the account again and check its ID, rules and execution mode.
  • Start small and compare each fill with the trade journal.
  • Keep the sandbox for testing changes; its trades never qualify a challenge or earn payouts.
FAQ4 questions

Frequently asked questions

01Does QATS have a Python SDK?

The API reference shows Python examples with the requests library and publishes an OpenAPI description and a Postman collection. The downloadable helper client is JavaScript; in Python, plain HTTP requests are enough.

02Can I use a Binance or Bybit library with the QATS API?

Not directly. QATS REST v1 uses Bearer tokens and its own paths, not exchange API-key headers or signed requests, so exchange SDKs are not drop-in compatible.

03Do API orders follow the same rules as manual trades?

Yes. API orders go through the terminal’s execution engine and the same server-side checks, including daily loss, maximum loss, margin and price checks.

04Can I test a bot without buying a challenge?

Yes. Any registered trader with a verified email can create a free sandbox with virtual funds and sandbox keys. Sandbox results do not qualify a challenge or earn payouts.

Written by the QATS team

The team behind QATS Prop. Every QATS figure comes from the published plan lineup; spotted something outdated? Email support@qats.us.

Published Updated

Educational content, not investment advice. QATS evaluations and initial funded accounts are simulated; payouts depend on eligibility, verification and the applicable agreement.