# Tradier API: pricing, sandbox limits and order status

URL: https://fincodelabs.com/en/blog/tradier-api · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Stocks, forex and broker APIs · Published: 2026-11-06 · Updated: 2026-11-06

Tradier API, read 6 November 2026: API access on every plan, a sandbox with 15-minute delayed data, HTTP 400 rate limits, and why a 200 needs a status check.

**Short answer:** The **Tradier API** is Tradier Brokerage's REST and streaming interface for US stocks, ETFs and options, and its pricing page, read 6 November 2026, includes API access on every plan, from Lite at $0 a month. The sandbox uses data delayed 15 minutes. The documented trap: an order call can return HTTP 200 even when a downstream system rejects the order, so read its status afterwards.

No Tradier account was available, so nothing here called Tradier with a token. Official pages were read on 6 November 2026; where they disagree, both versions are given. This is not a recommendation of Tradier, and automated trading can lose money. Other brokers are in the [stock and broker API guides](https://fincodelabs.com/en/blog/stock-and-broker-apis).

## Tradier API pricing: what each plan includes

API access costs nothing extra: Tradier's pricing page, read 6 November 2026, includes it on all three plans.

**Tradier plans on tradier.com/pricing, read 6 November 2026**

| Item | Lite | Pro | Pro Plus |
| --- | --- | --- | --- |
| Monthly fee | $0 | $10 | $35 |
| Stocks, per trade | $0.35 | $0.00 | $0.00 |
| Equity and ETF options, per contract | $0.35 | $0.00 | $0.00 |
| Index options, per contract | $0.35 | $0.35 | $0.10 |
| Option assignment or exercise | $9.00 | $9.00 | $5.00 |
| API access | Included | Included | Included |

The fee schedule adds pass-through costs, such as clearing at $0.0775 a contract, and a $50 yearly inactivity fee for fewer than 2 trades a year; the account minimum is $0. Futures appear there too but come from Tradier Futures, a separate company.

> **Hypothetical example.** At $0.35 each, 29 stock trades a month cost $10.15 on Lite, more than Pro's $10 fee. Pro Plus costs $25 more than Pro and saves $0.25 per index option contract, breaking even at 100 a month on those commissions alone; each assignment or exercise saves $4 more.

## Tradier API documentation: hosts, tokens and personal use

For your own account you need only the token from the API Settings page; without Partner status the API is for personal use only, and partner apps use OAuth 2.0.

- **Hosts.** Production is `https://api.tradier.com/v1/`, the sandbox `https://sandbox.tradier.com/v1/`. A token sent to the other host gets HTTP 401.
- **Tradier API key.** A personal token gives full account access and lasts until you regenerate it, so store it like a password (see [API key security](https://fincodelabs.com/en/blog/api-key-security-trading-bots)).
- **Coverage.** US equities, ETFs and options; Level 1 quotes only.
- **Account types.** The profile endpoint lists individual, joint, entity, custodial and IRA classifications, cash or margin, with options levels 1 to 6.

The API User Agreement, a PDF dated 7 October 2019, says Tradier may reject orders placed through the API, counts an order as received only when it actually reaches Tradier, and may revoke an API's authorization at any time without notice.

## Tradier sandbox: what paper trading does not show

The sandbox has production's endpoints but delayed data, no market data streaming and lower limits, so it tests code paths, not fills.

**Production and sandbox in Tradier's docs, read 6 November 2026**

| Item | Production | Sandbox |
| --- | --- | --- |
| Market data | Real time; some index data delayed 15 minutes | Delayed 15 minutes |
| Orders | Live equities, ETFs and options | Paper trades filled against delayed data |
| Market data streaming | Available | Not offered |
| Requests a minute: standard, market data, trading | 120, 120, 60 | 60, 60, 60 |
| Futures | Not available | Not available |

Tradier's knowledge base calls the sandbox free and says it mostly mirrors production, and the docs FAQ offers paper accounts to brokerage account holders. No page read says whether someone who cannot open an account can get a sandbox token, or how paper fills, partial fills or slippage are modeled (see [paper trading API](https://fincodelabs.com/en/blog/paper-trading-api)).

## Tradier API rate limit: HTTP 400, not 429

Tradier counts requests per access token per minute and answers an overrun with HTTP 400 and a plain-text body, so code waiting for a 429 never sees one.

- **Scope.** Each 1-minute interval starts with the first request. Limits are per token, so programs sharing one personal token share one budget. Placing orders counts as trading.
- **Headers.** `X-Ratelimit-Allowed`, `X-Ratelimit-Used`, `X-Ratelimit-Available` and `X-Ratelimit-Expiry`, the last in epoch milliseconds.
- **Over the limit.** The rate limit page, citing Tradier's live testing, gives HTTP 400 with `Quota Violation: Expires <epoch-ms>` and a non-standard `plain/text` content type. Match that substring before parsing JSON.

The official FAQs disagree on raising limits: the documentation FAQ says most likely not, the knowledge base FAQ offers custom arrangements for specialized needs.

## Why an HTTP 200 does not mean Tradier accepted the order

Tradier's error page says the place-order call returns 200 for errors thrown by downstream systems such as risk management and market centers, so read the order's status afterwards.

- **Status names disagree.** The single-order schema lists `pending`, `open`, `partially_filled`, `filled`, `expired`, `canceled`, `rejected` and `pending_cancel`; the order list's filter spells `cancelled` and omits `pending_cancel`. Accept both spellings (see [order state machine](https://fincodelabs.com/en/blog/order-state-machine)).
- **Undocumented errors.** The error page says such an order carries an `errors` property that the documented order fields leave out, so log it whole.
- **No client order ID.** The only label you can attach is an optional `tag`, and no page read says tags are unique or block duplicates. Order lists show tags only with `includeTags=true`, cover the current day's session and return 25 orders unless you raise `limit` (see [client order ID](https://fincodelabs.com/en/blog/client-order-id)).
- **One item is not a list.** One order comes back as an object, several as an array.

A `preview=true` request runs all validation, including buying power, and returns cost and commission, but it cannot see a later downstream rejection. The error table still describes `TooSmallEquityForDayTrading` as a pattern day trader violation below $25,000 of equity. FINRA Regulatory Notice 26-10 replaced those rules, including the $25,000 minimum, with intraday margin standards effective 4 June 2026 and lets firms phase them in until 20 October 2027; the pages read do not say when Tradier's checks change.

## Tradier API Python: order code tested offline

This sample refuses a tag already used today, reads the status before trusting a 200, treats a lost or unreadable reply as unknown instead of resending, and detects the 400 rate limit reply. It ran on Python 3.11.3 on 6 November 2026. Tradier's overview links community SDKs it does not maintain; review one as you would your own code.

```python
import json, re, time
from decimal import Decimal, InvalidOperation

class Lost(Exception): pass            # reply lost: the order may exist
class Unknown(Exception): pass         # reconcile; never resend blindly
class RateLimited(Exception): pass

def price_text(p, cap=Decimal("100000")):
    try:
        d = Decimal(str(p))
    except InvalidOperation:
        d = Decimal("NaN")
    if d.is_finite() and 0 < d < cap and d == d.quantize(Decimal("0.0001")):
        return format(d.normalize(), "f")  # never "inf" or "1E-5" on the wire
    raise ValueError(f"bad price {repr(p)}")

class Tradier:
    def __init__(self, token, account, send, host="sandbox.tradier.com"):
        if not (token or "").strip():      # fail before any network call
            raise ValueError("missing token")
        self.base, self.send = f"https://{host}/v1", send
        self.orders = f"/accounts/{account}/orders"
        self.headers = {"Authorization": f"Bearer {token.strip()}", "Accept": "application/json"}

    def call(self, method, path, data=None):
        try:                               # send(): GET data as query, POST data as form
            status, body = self.send(method, self.base + path, data, self.headers)
        except OSError as err:
            raise Lost(type(err).__name__) from None
        if status >= 500:
            raise Lost(f"HTTP {status}")
        if status == 401:                  # bad token or wrong host
            raise PermissionError("HTTP 401: token or host")
        if status == 429 or status == 400 and "Quota Violation" in body:  # 400 seen, 429 listed
            m = re.search(r"Expires (\d+)", body)
            wait = int(m.group(1)) / 1000 - time.time() if m else 60
            raise RateLimited(f"wait {max(wait, 0):.0f} s")
        try:
            payload = json.loads(body)
        except ValueError:
            payload = None
        if status == 200 and isinstance(payload, dict):
            return payload
        if status == 200:                  # received, reply unreadable
            raise Lost("HTTP 200, unreadable reply")
        raise RuntimeError(f"HTTP {status}: {body[:60]}")

    def orders_tagged(self, tag):          # covers today's session only
        reply = self.call("GET", self.orders, {"includeTags": "true", "limit": "1000"})
        box = reply.get("orders")
        rows = box.get("order") if isinstance(box, dict) else None
        rows = rows if isinstance(rows, list) else [rows] if isinstance(rows, dict) else []
        return [r for r in rows if isinstance(r, dict) and r.get("id") and r.get("tag") == tag]

    def place(self, symbol, side, qty, tag, price=None):
        if not re.fullmatch(r"[A-Za-z0-9-]{1,40}", tag or ""):
            raise ValueError(f"bad tag {repr(tag)}")
        if isinstance(qty, bool) or not isinstance(qty, int) or not 1 <= qty <= 10_000_000:
            raise ValueError(f"bad quantity {repr(qty)}")
        data = {"class": "equity", "symbol": symbol, "side": side, "quantity": str(qty),
                "type": "market" if price is None else "limit", "duration": "day", "tag": tag}
        if price is not None:
            data["price"] = price_text(price)
        try:                               # refuse a reused tag
            used = self.orders_tagged(tag)
        except Lost as err:
            raise RuntimeError(f"nothing sent: {err}") from None
        if used:
            raise ValueError(f"tag {tag} already used today")
        try:
            order = self.call("POST", self.orders, data).get("order")
            order_id = order.get("id") if isinstance(order, dict) else None
        except Lost as err:
            try:
                hits = self.orders_tagged(tag)
            except Exception as err2:
                raise Unknown(f"{err}, lookup failed: {type(err2).__name__}") from None
            if len(hits) == 1:
                order_id = hits[0]["id"]
            else:
                raise Unknown(f"{err}, {len(hits)} orders tagged {tag}") from None
        if not order_id:
            raise Unknown("HTTP 200 without an order id")
        try:                               # a 200 is not acceptance
            order = self.call("GET", f"{self.orders}/{order_id}").get("order")
        except Exception as err:
            raise Unknown(f"order {order_id} sent, status not read: {err}") from None
        status = order.get("status") if isinstance(order, dict) else None
        return f"order {order_id}: {status or 'unknown'}"

# offline tests: synthetic replies only
def fake(script):
    def send(*request):
        send.calls += 1
        step = script.pop(0)
        if isinstance(step, Exception):
            raise step
        return step
    send.calls = 0
    return send

clean = (200, '{"orders": {"order": {"id": 100}}}')
ok = (200, '{"order": {"id": 101, "status": "ok"}}')
cases = {
    "200, then rejected": [clean, ok, (200, '{"order": {"id": 101, "status": "rejected"}}')],
    "rate limited": [clean, (400, f"Quota Violation: Expires {int(time.time() * 1000) + 12000}")],
    "timeout, one match": [clean, TimeoutError(), (200, '{"orders": {"order": {"id": 102, "tag": "t-1"}}}'),
                           (200, '{"order": {"id": 102, "status": "open"}}')],
    "HTTP 502, two matches": [clean, (502, "x"), (200, '{"orders": {"order": [{"id": 105}, '
                              '{"id": "", "tag": "t-1"}, {"id": 103, "tag": "t-1"}, {"id": 104, "tag": "t-1"}]}}')],
}
for name, script in cases.items():
    try:
        result = Tradier("TOKEN", "VA000000", fake(script)).place("AAPL", "buy", 1, "t-1")
    except (Unknown, RateLimited, PermissionError, RuntimeError) as err:
        result = f"{type(err).__name__}: {err}"
    print(f"{name:23} {result}")

send = fake([])
for price, tag in [(float("1e400"), "t-2"), ("1e-400", "t-2"), (None, "")]:
    try:
        Tradier("TOKEN", "VA000000", send).place("AAPL", "buy", 1, tag, price)
    except ValueError as err:
        print(f"{'bad input':23} {err}")
try:
    Tradier("  ", "VA000000", send)
except ValueError as err:
    print(f"{'empty token':23} {err}; requests sent: {send.calls}")
```

Output:

```
200, then rejected      order 101: rejected
rate limited            RateLimited: wait 12 s
timeout, one match      order 102: open
HTTP 502, two matches   Unknown: HTTP 502, 2 orders tagged t-1
bad input               bad price inf
bad input               bad price '1e-400'
bad input               bad tag ''
empty token             missing token; requests sent: 0
```

Not run: no call reached Tradier, so error bodies, a real rejected order and sandbox fills are untested. In `requests` 2.31.0, timeout and connection errors subclass `OSError`; check any other HTTP library. The tag format and check (one more request per order), price cap and four decimals are this sample's choices; the quantity range is from Tradier's error table. With no match or several, the code stops for a person, because a fresh order may not be listed yet (see [position reconciliation](https://fincodelabs.com/en/blog/position-reconciliation)).

## Who can open a Tradier account, and rules for Turkey

API access follows account access, which Tradier's country list governs.

Tradier's knowledge base list of permitted and blocked countries, read 6 November 2026, says Tradier Brokerage cannot open accounts for residents of Turkey, the United Kingdom, Canada or Australia, among others; citizens of the last three living in a permitted country can apply. Rules depend on your country, and account types and API access vary; no broker is recommended or ranked here.

For readers in Turkey, the Capital Markets Board (SPK) said on 23 February 2023 that residents may trade leveraged products such as forex only through institutions it has authorized, that offering them otherwise is a crime, and that it acts to block websites offering them (Law No. 6362 Articles 99(4) and 109(2)). Options, futures and leverage carry high risk. For integration work in general, see Fincode's [broker API integration](https://fincodelabs.com/en/services/broker-api-integration) page; naming Tradier here does not mean Fincode integrates it or has built on it.

## Frequently asked questions

### Is the Tradier API free?

Tradier's pricing page, read 6 November 2026, includes API access on Lite ($0 a month), Pro ($10) and Pro Plus ($35). Commissions vary by plan; pass-through fees can apply.

### Does Tradier have a sandbox for paper trading?

Yes, at sandbox.tradier.com/v1 with its own token, using data delayed 15 minutes and without market data streaming.

### Do Tradier API tokens expire?

Personal tokens last until you regenerate them; partner OAuth access tokens last 24 hours.

### Can I use the Tradier API outside the United States?

Only where Tradier can open your account, because API access follows account access. Its country list, read 6 November 2026, excludes residents of Turkey, the United Kingdom, Canada and Australia, among others.

## Sources

- [Tradier Docs: Overview (environments, tokens, order status note, scope), read 6 November 2026](https://docs.tradier.com/overview)
- [Tradier Docs: Rate Limits (categories, headers, HTTP 400 Quota Violation)](https://docs.tradier.com/overview/rate-limits)
- [Tradier Docs: Error Responses (HTTP 200 on downstream rejection, error codes)](https://docs.tradier.com/overview/error-responses)
- [Tradier Docs: FAQ (sandbox delay, streaming, personal use, token expiry, API cost)](https://docs.tradier.com/overview/faq)
- [Tradier Docs: Preview Order (validation including buying power, cost and commission)](https://docs.tradier.com/trading/preview-order)
- [Tradier Docs: Get Single Order (status values)](https://docs.tradier.com/account-details/get-single-order)
- [Tradier Docs: Get Account Orders (includeTags, status filter, session scope)](https://docs.tradier.com/account-details/get-account-orders)
- [Tradier Docs: Get User Profile (account classifications, option level)](https://docs.tradier.com/account-details/get-user-profile)
- [Tradier: Pricing and Plans with Fee Schedule, read 6 November 2026](https://tradier.com/pricing)
- [Tradier Brokerage: Application Program Interface User Agreement (PDF dated 7 October 2019)](https://api.tradier.com/v2/applications/agreements?key=api_agreement)
- [Tradier Knowledge Base: Permitted and Blocked Countries](https://support.tradier.com/kb/guide/en/permitted-and-blocked-countries-CLzSa0D1rf/Steps/4997916)
- [Tradier Knowledge Base: Tradier API FAQ (countries, cost, limits, sandbox)](https://support.tradier.com/kb/guide/en/tradier-api-faq-ESmdeBW4Ab/Steps/4973445)
- [FINRA Regulatory Notice 26-10: intraday margin standards replace the day trading rules (effective 4 June 2026, phase-in to 20 October 2027)](https://www.finra.org/rules-guidance/notices/26-10)
- [SPK (Capital Markets Board): Notice on unauthorized leveraged transactions, 23 February 2023 (Turkish)](https://spk.gov.tr/duyurular/basin-duyurulari/2023/izinsiz-kaldiracli-islemlere-iliskin-basin-duyurusu)
- [Capital Markets Law No. 6362, consolidated text, Articles 99 and 109 (Turkish)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the [editorial policy](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/tradier-api

Cite as: Fincode, "Tradier API: pricing, sandbox limits and order status", updated November 6, 2026, https://fincodelabs.com/en/blog/tradier-api
