# Stock trading API: what US brokers document, and the quirks

URL: https://fincodelabs.com/en/blog/stock-trading-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

Stock trading API inventory, read 5 November 2026: auth, test environments, rate limits and retry quirks across US brokers, with tested Python for the traps.

**Short answer:** A **stock trading API** lets software read quotes and accounts and send orders to a broker. As read on 5 November 2026, Alpaca, E\*TRADE, Interactive Brokers, Schwab, tastytrade, TradeStation, Tradier and Webull (alphabetical, not a ranking) each document one, while Robinhood's published REST API covers crypto and its stock access for software is an MCP connection for AI agents. They differ where programs break: Tradier answers rate limits with HTTP 400, tastytrade does not deduplicate retried orders, and E\*TRADE requires a preview before an order.

This list compares what an engineer must design around, from each broker's official documents as read on 5 November 2026. No account or key was used. Calling each sandbox without a token returned an authentication error, which proves only that the host answers, and the Python below runs on synthetic replies.

No broker is recommended here. Automated trading can lose money. Architecture is in [broker API integration](https://fincodelabs.com/en/blog/broker-api-integration-fix-rest) and [trading bot development](https://fincodelabs.com/en/blog/trading-bot-development), and data licensing in [stock market data API](https://fincodelabs.com/en/blog/stock-market-data-api).

## Brokers with API trading: docs, login and test environment

Most publish self-service documentation, but access still depends on an account, and each login model implies a different routine.

**US broker APIs, alphabetical, read 5 November 2026; each login links to official documentation**

| Broker | Login | Test environment | Access per its docs |
| --- | --- | --- | --- |
| [Alpaca](https://fincodelabs.com/en/blog/alpaca-api-python) | [Key headers](https://docs.alpaca.markets/docs/authentication) | `paper-api.alpaca.markets`, separate keys | Paper-only account open to anyone globally |
| E\*TRADE | [OAuth request-token flow](https://apisb.etrade.com/docs/api/authorization/get_access_token.html), HMAC-SHA1 signature | `apisb.etrade.com` | Portal `developer.etrade.com` refused access; docs read on the sandbox host |
| [Interactive Brokers](https://fincodelabs.com/en/blog/interactive-brokers-api-python) | [Web API](https://www.interactivebrokers.com/docs/web-api/authentication/introduction): Client Portal Gateway for individual accounts, OAuth for others; more in [IBKR Web API vs TWS API](https://fincodelabs.com/en/blog/ibkr-web-api-vs-tws-api) | [Paper account](https://www.interactivebrokers.com/docs/web-api/authentication/paper), logged in with its own Paper username | [Open, funded account](https://www.interactivebrokers.com/docs/web-api/trading/getting-started/introduction), [IBKR Pro for individuals](https://www.interactivebrokers.com/docs/web-api/trading/getting-started/trading-access-for-individuals), paper included; some login methods need approval |
| [Schwab](https://fincodelabs.com/en/blog/schwab-api-python) | [OAuth 2 authorization code](https://developer.schwab.com/user-guides/get-started/authenticate-with-oauth) | Not confirmed by Schwab; the community wrapper's README says none | Schwab brokerage account, one app, approval |
| tastytrade | [OAuth2](https://developer.tastytrade.com/docs/authentication/oauth2/), `User-Agent` of the form `product/version` required | `api.cert.tastyworks.com`, separate credentials | Registered OAuth client |
| TradeStation | [OAuth2 authorization code](https://api.tradestation.com/docs/fundamentals/authentication/auth-overview) | `sim-api.tradestation.com/v3` | Funded account, [key requested by email](https://api.tradestation.com/docs/faq) |
| Tradier | [Bearer token](https://docs.tradier.com/docs/getting-started) | `sandbox.tradier.com/v1` | Brokerage account; API at no extra charge; personal use only without Partner status |
| Webull | [HMAC-SHA1 signature](https://developer.webull.com/apis/docs/authentication/overview/) with app key | `api.sandbox.webull.com` | [Application](https://developer.webull.com/apis/docs/getting-started/), reviewed in 1 to 2 business days; shared test accounts need none |

TradeStation recommends v3, and warns it is not liable for applications that let users switch between SIM and live.

## Tradier API, TradeStation API, tastytrade: what test environments hide

Each sandbox misleads in its own way, so learn what it fakes before trusting a passing test.

**Test environment behavior, per official docs**

| Broker | Behavior |
| --- | --- |
| Alpaca | Partial fills at random 10% of the time; no market impact, queue position, price improvement or dividends |
| E\*TRADE | Fill behavior not described on the pages read; the authorization pages show a sandbox URL with an empty host |
| tastytrade | Fills at quote mid, some tickers scripted; `/market-data` returns HTTP 502; trades and positions reset every 24 hours |
| TradeStation | Fake accounts and money, with instant simulated fills |
| Tradier | Market data delayed 15 minutes, paper fills against it; a token works only in its own environment |
| Webull | Generally lower limits than production, for example 30 order calls per 60 seconds against 600 |

Instant fills never exercise your partial-fill path, and a 24-hour reset wipes state you may be testing. For a plan that covers these gaps, see [paper trading API](https://fincodelabs.com/en/blog/paper-trading-api).

## Rate limits: the numbers and what going over looks like

Do not assume HTTP 429, because Tradier reports limits with HTTP 400 and a plain-text body.

**Published limits, read 5 November 2026**

| Broker | Limit | Over the limit |
| --- | --- | --- |
| Alpaca | 200 requests a minute per account (support page dated December 2022) | 429 |
| E\*TRADE | Not stated on the pages read | Order API error list: `400`, code 630000, Too many requests sent at the same time |
| Interactive Brokers | [Web API](https://www.interactivebrokers.com/docs/web-api/trading/usage-and-availability/pacing-limitations): 10 requests a second per session; lower on some endpoints, such as 1 per 5 seconds on `/iserver/orders` | 429; an IP can go in a penalty box for 10 minutes |
| Schwab | Order limit setting of 0 to 120 per account per minute; others not readable | Not readable |
| tastytrade | No published per-endpoint quota | 429 with no rate headers; [failed logins](https://developer.tastytrade.com/docs/guides/rate-limits-and-backoff/) can block an IP for about 8 hours |
| TradeStation | 320 per 5 minutes each for accounts, order details, balances and positions; 500 per 5 minutes for quote snapshots; 90 a minute per options endpoint; no quota listed for placing orders | 429, with `X-RateLimit-Reset` in seconds |
| Tradier | Per token, per minute: 120 standard, 120 market data, 60 trading; sandbox 60 each | `400`, body `Quota Violation: Expires <epoch-ms>` |
| Webull | Order calls 600 per 60 seconds; preview 150 per 10 seconds; balances, positions and order detail 2 per 2 seconds | 429; repeats may bring an IP block |

Tradier's limits page and a no-token call both show the non-standard content type `plain/text`, so parse those replies as text.

## Retrying orders safely: ID fields and what a 200 means

A timeout after sending an order leaves its fate unknown, and the brokers' documents differ on what a resend does, so look the order up before sending it again.

**Client IDs and acceptance, per official docs**

| Broker | Your ID field | What the docs add |
| --- | --- | --- |
| Alpaca | `client_order_id`, up to 128 characters | Called unique; duplicate reply not described. alpaca-py resends on 429 and 504 by default |
| E\*TRADE | `clientOrderId`, up to 20 alphanumeric characters | Meant to stop duplicates and unique per account, but the docs say it appears in no API response, so it cannot serve as a lookup key. Place Order needs the `previewId` from Preview Order, within 3 minutes |
| Schwab | None found in public material | Successful order calls usually return no JSON, per the community wrapper |
| tastytrade | `external-identifier` | The API does not deduplicate, so a repeated ID can place a second order. [`/orders/dry-run`](https://developer.tastytrade.com/docs/guides/place-an-equity-order/) validates and routes nothing |
| TradeStation | `OrderConfirmID`, 1 to 22 characters | [Specification](https://api.tradestation.com/docs/specification/): prevents duplicates, unique per API key, per order, per user. Not in the order read fields; repeat reply not described |
| Tradier | `tag`, optional | A 200 can arrive even if a downstream system rejects the order; [`preview=true`](https://docs.tradier.com/trading/place-equity-order) validates only |
| Webull | `client_order_id`, required, up to 32 characters, unique per account | Resend behavior not described; preview is listed as the first step, not a stated requirement |

This stock trading API Python sample handles both traps, tested on synthetic replies on 5 November 2026 with Python 3.11.3:

```python
import re, time

def limit_wait(status, headers, body, now=None):
    """Seconds to wait if this reply means 'rate limited', else None."""
    now = time.time() if now is None else now
    if status == 429:                      # Alpaca, IBKR, TradeStation, tastytrade, Webull
        wait = headers.get("Retry-After", "60")
        return float(wait) if wait.isdigit() else 60.0   # it can also be a date
    if status == 400 and "Quota Violation" in body:   # Tradier
        expires = re.search(r"Expires\s+(\d+)", body)
        return max(0.0, int(expires.group(1)) / 1000 - now) if expires else 60.0
    return None

class Unknown(Exception):
    """The order's fate is not known: look it up, do not resend."""

def place(send, lookup, client_id):
    """Send once. On a timeout or 5xx, ask the broker what happened."""
    try:
        status, order = send(client_id)
    except TimeoutError:                   # have send() map requests/httpx timeouts to this
        status, order = 599, None
    if status < 300:
        return lookup(client_id) or order   # a 200 alone proves little
    if status >= 500:
        found = lookup(client_id)
        if found is None:
            raise Unknown(client_id)
        return found
    raise ValueError(f"rejected: HTTP {status}")

if __name__ == "__main__":
    NOW = 1_793_900_000.0
    cases = [(429, {"Retry-After": "7"}, "", "429 with Retry-After"),
             (429, {}, "", "429, no headers"),
             (429, {"Retry-After": "Thu, 05 Nov 2026 18:30:00 GMT"}, "", "429, date header"),
             (400, {}, f"Quota Violation: Expires {int((NOW + 12) * 1000)}", "Tradier quota"),
             (400, {}, "Invalid Parameter", "ordinary 400")]
    for status, headers, body, label in cases:
        print(f"{label:22} wait={limit_wait(status, headers, body, NOW)}")

    book = {}                              # synthetic broker

    def lost_reply(cid):                   # order reached the book, reply lost
        book[cid] = {"status": "open"}
        raise TimeoutError

    def never_arrived(cid):
        raise TimeoutError

    def downstream_reject(cid):            # 200, then rejected downstream
        book[cid] = {"status": "rejected"}
        return 200, {"status": "ok"}

    for send, cid in ((lost_reply, "a1"), (downstream_reject, "a2"), (never_arrived, "a3")):
        try:
            print(send.__name__, "->", place(send, book.get, cid))
        except Unknown as err:
            print(send.__name__, "-> Unknown, no resend:", err)
```

Output:

```
429 with Retry-After   wait=7.0
429, no headers        wait=60.0
429, date header       wait=60.0
Tradier quota          wait=12.0
ordinary 400           wait=None
lost_reply -> {'status': 'open'}
downstream_reject -> {'status': 'rejected'}
never_arrived -> Unknown, no resend: a3
```

The 60-second fallback is an illustrative setting. TradeStation's `X-RateLimit-Reset` counts seconds; [Alpaca's Broker API](https://docs.alpaca.markets/us/docs/broker-api-rate-limits) uses that name for a Unix timestamp. The lookup is broker-specific, and on a listing endpoint it may lag behind the order; more in [client order ID](https://fincodelabs.com/en/blog/client-order-id).

## Tokens: what expires, and who has to log in again

Token life decides whether a program can run unattended, and the range runs from no expiry to every day.

**Credential lifetimes, per official docs unless marked**

| Broker | What expires |
| --- | --- |
| Alpaca | Key pairs: no expiry stated on the pages read |
| E\*TRADE | Access token ends at midnight US Eastern by default and goes inactive after two hours without requests; a renew call exists |
| Interactive Brokers | Gateway session up to 24 hours, with browser login on the same machine; [times out](https://www.interactivebrokers.com/docs/web-api/authentication/faq) after about 6 minutes without requests or `/tickle` |
| Schwab | About 30 minutes and seven days, community-documented; see the Schwab guide |
| tastytrade | Access token 15 minutes; refresh tokens do not expire |
| TradeStation | Access token 20 minutes. Refresh tokens do not expire by default. Rotation on request: [one page](https://api.tradestation.com/docs/fundamentals/authentication/refresh-tokens) says every 30 minutes, the auth overview every 40, with a 24-hour absolute limit |
| Tradier | Personal tokens last until regenerated |
| Webull | Requests are signed each time; a reusable token verified in the app is needed only with two-factor authentication |

> **Hypothetical design check.** A trading program restarts at 09:00 US Eastern each trading day. On E\*TRADE that restart meets a token that ended at midnight, so the daily runbook needs a login step. On tastytrade the same restart can refresh by itself.

## Does Robinhood have an API? Webull, E\*TRADE and the rest

Robinhood documents a crypto REST API and a separate MCP connection for AI agents, and no REST API for stocks was found.

- **Crypto Trading API.** Its support article describes crypto market data, account data and orders for US Robinhood Crypto customers, and does not mention stocks or options.
- **Trading MCP.** Support pages describe connecting an external AI agent to `agent.robinhood.com/mcp/trading`, with equities, options and crypto tools. Trades go only to a dedicated agentic account, the agent gets read access to all your accounts, and you stay responsible for its trades. This was read, not run.
- **Community libraries.** [`robin_stocks`](https://pypi.org/project/robin-stocks/) (3.4.0, 18 May 2025) lists an individual author and does not claim a link to Robinhood. Read the terms before relying on any unofficial route; this guide does not cover them.

**tastytrade from Python.** Its own [Python SDK repository](https://github.com/tastytrade/tastytrade-sdk-python) was archived on 13 March 2026, and the [`tastytrade`](https://pypi.org/project/tastytrade/) package on PyPI (13.2.3, 7 August 2026) calls itself unofficial.

For readers in Turkey on the crypto point: Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized.

## How to choose a stock trading API without a ranking

Work through these in order, because the first two can rule a broker out before any code is written.

1. **Eligibility.** Ask the broker in writing whether your country and account type can use the API.
2. **Use.** Own account or customers? Serving others is a separate product and a licensing question, as in [Alpaca's Trading and Broker APIs](https://fincodelabs.com/en/blog/alpaca-api-python).
3. **Login routine.** Can it run unattended, or does a person log in daily or weekly?
4. **Test environment.** Which failures can it actually produce?
5. **Retry rules.** Is there a client ID, and can you search by it?
6. **Data rights.** Can you display what you receive to others?
7. **Protocol.** Volume may justify FIX; see [QuickFIX Python](https://fincodelabs.com/en/blog/quickfix-python).

Rules depend on your country, and account types and API access vary. For readers in Turkey, the Capital Markets Board (SPK) said on 23 February 2023 that residents can trade leveraged forex only through institutions it has authorized, and [Law No. 6362](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf) provides access blocks for online offers from abroad (Article 99(4)) and criminal penalties for unauthorized activity (Article 109(2)).

Fincode quotes this work through [exchange API integration](https://fincodelabs.com/en/services/exchange-api-integration) and claims no prior work with any broker named here.

## Frequently asked questions

### Does Robinhood have an API?

Its published REST API covers crypto for US customers, and its support pages describe a Trading MCP connection for AI agents. No REST API for stocks was found.

### Does E\*TRADE have an API?

Yes. Its documents, read on its sandbox host, describe signed OAuth requests, a required preview before placing an order, and access tokens that end at midnight US Eastern by default.

### Is there a Python SDK for stock trading APIs?

Alpaca publishes alpaca-py and Webull publishes webull-openapi-python-sdk. schwab-py is a community wrapper for Schwab. tastytrade's own SDK repository is archived and the tastytrade package on PyPI calls itself unofficial, so otherwise it means plain HTTP.

### Can I test a stock trading API without a funded account?

Alpaca's docs allow a paper-only account for anyone globally, and Webull's offer shared test accounts with no application. TradeStation's key request needs a funded account, and Tradier's docs ask for a brokerage account.

## Sources

- [Tradier Docs: Rate limits](https://docs.tradier.com/docs/rate-limits)
- [Tradier Docs: Getting started (hosts, tokens, order confirmation)](https://docs.tradier.com/docs/getting-started)
- [TradeStation API: SIM vs LIVE](https://api.tradestation.com/docs/fundamentals/sim-vs-live)
- [TradeStation API: Rate limiting overview](https://api.tradestation.com/docs/fundamentals/rate-limiting/rate-limiting-overview)
- [TradeStation API: Authentication overview](https://api.tradestation.com/docs/fundamentals/authentication/auth-overview)
- [tastytrade Developer Docs: Idempotency and retries](https://developer.tastytrade.com/docs/guides/idempotency-and-retries/)
- [tastytrade Developer Docs: Sandbox](https://developer.tastytrade.com/docs/sandbox/)
- [tastytrade Developer Docs: OAuth2](https://developer.tastytrade.com/docs/authentication/oauth2/)
- [E\*TRADE API documents: Get Access Token](https://apisb.etrade.com/docs/api/authorization/get_access_token.html)
- [E\*TRADE API documents: Order API](https://apisb.etrade.com/docs/api/order/api-order-v1.html)
- [Webull OpenAPI: Rate limits](https://developer.webull.com/apis/docs/rate-limits)
- [Webull OpenAPI: Stock orders (client_order_id)](https://developer.webull.com/apis/docs/trade-api/stock)
- [Robinhood Support: Crypto Trading API](https://robinhood.com/us/en/support/articles/crypto-api/)
- [Robinhood Support: Onboarding an external agent (Trading MCP)](https://robinhood.com/us/en/support/articles/onboarding-an-external-agent/)
- [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)

> 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/stock-trading-api

Cite as: Fincode, "Stock trading API: what US brokers document, and the quirks", updated November 6, 2026, https://fincodelabs.com/en/blog/stock-trading-api
