Skip to content

Learn

Exchange APIs

Alpaca API 403 Forbidden: what it means and how to find the cause

Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs.

What you see is usually one of these:

403 Forbidden
{"code": 40310000, "message": "insufficient buying power", ...}

or, on the order endpoint, a 403 whose meaning Alpaca’s API reference gives as “Forbidden — Buying power or shares is not sufficient.”

In one line: on Alpaca, 403 means the request was understood and refused — but the reason can be anything from your account balance to your credentials, and the only reliable way to know which is the code and message in the response body. This page is for anyone calling the Alpaca Trading API directly or through an SDK or MCP server, who has a 403 and wants the cause rather than a guess.

What this error actually means

Alpaca uses HTTP 403 for more than one kind of refusal. From its own documentation:

WhereWhat 403 means there
POST /v2/orders“Forbidden — Buying power or shares is not sufficient.”
POST /v2/positions/{symbol_or_contract_id}/exercise“Available position quantity is not sufficient.”
POST /v2/positions/{…}/do-not-exercise“Available position quantity is not sufficient or no position found.”
/v1/locates endpoints“User has no access to a resource.”
Any order flagged as a possible wash tradeRejected with HTTP 403 (Alpaca’s user protection docs)

Alpaca’s own error guide, published on its Learn site, frames 403 more broadly: these errors typically occur when credentials, account permissions or subscriptions do not allow the requested action, and many are account-level restrictions rather than request-format bugs. That is the right mental model. A 403 from Alpaca is rarely fixed by changing the shape of the request.

The body is what distinguishes them. The Trading API’s OpenAPI spec defines an error object with two required fields, code (a number) and message (a string). Some 403 bodies carry extra fields — an insufficient buying power rejection, for instance, also includes buying_power and cost_basis.

The usual cause: the account cannot do what you asked

The codes and messages Alpaca’s error guide documents for 403:

codemessage
40310000insufficient buying power
40310000account is not allowed to short
40310000account is not authorized to trade
40310000account is restricted to liquidation only
40310000asset ”…” is not fractionable
40010001subscription does not permit querying recent SIP data

Note that 40310000 covers several unrelated conditions. Branch on the message, not the code alone, and log both. The last row is a market data response rather than a trading one, but it arrives as HTTP 403 all the same.

These are examples Alpaca has published, not an exhaustive table. If you get a code that is not here, the message is still Alpaca’s own description of what happened — read it before looking anywhere else.

For an insufficient buying power rejection, the extra buying_power and cost_basis fields in the body let you compare what the order needed with what the account had, without a second request.

Other causes, in order of likelihood

Paper and live crossed. Alpaca has two hosts, https://paper-api.alpaca.markets and https://api.alpaca.markets, and each account has separate credentials. A paper key does not work against the live host, and a live key does not work against paper. Alpaca’s error guide says invalid or missing credentials may return 403 rather than 401 in some cases, so do not rule this out just because the status is not 401. We could not confirm which status a crossed key returns, so check it either way. The full setup is in Alpaca paper trading API.

The quick check is GET /v2/account with the same headers and host as the failing request. If that fails too, the problem is the key and host pairing, not the order.

Wash-trade protection. If Alpaca detects a possible wash trade — broadly, an order that could end up trading against your own orders — it rejects the order with 403. Its documentation lists bracket, OCO and trailing-stop orders as exceptions. The symptom is an order that looks valid in isolation. What exactly Alpaca treats as a possible wash trade is described on its user protection page; check your open orders in the same symbol against it.

A key without trading access. Alpaca keys carry Access Controls chosen at creation: Read only, Full access, or Custom per scope. A key created read-only, or Custom with trading set to read-only, is not meant to place orders. We did not find which status and code Alpaca returns in that case, so treat it as a candidate when reads succeed and every write fails, and check the key’s settings in the dashboard.

A market data subscription. 40010001 is the documented example: the plan does not permit the data you asked for. The fix is a different data request or a different plan, not different code.

Too many market data streams. Alpaca documents a limit of one concurrent market data stream connection per account, and says a second may receive a 406 or a 403. An MCP server and a script both streaming data under the same account is one easy way to hit this.

The old pattern day trading rejection. Older tutorials describe 40310100, “trade denied due to pattern day trading protection”. Alpaca has since removed the PDT rule and its restrictions — see the Alpaca PDT rule removal. If you see this code in an old log, it is historical.

How to confirm which one you have

  1. Print the whole body, not just the status. Most 403s identify themselves in the message.

    r = requests.post(f"{BASE}/v2/orders", json=order, headers=HEADERS)
    if r.status_code == 403:
        body = r.json()
        log.error(
            "alpaca 403 host=%s code=%s message=%s body=%s",
            BASE, body.get("code"), body.get("message"), body,
        )

    Use .get(): a credentials failure may carry only a message (Alpaca’s 401 example is {"message": "unauthorized"}), and extra fields vary.

  2. Call GET /v2/account with the same host and key. Failure means a credential or host problem. Success means the key works and the account refused this specific request.

  3. Match the message against the tables above. Buying power, shorting, fractional and liquidation-only messages are account state; the data subscription message is a plan question.

  4. Check your open orders in the same symbol. If the rejected order could interact with one of them, suspect wash-trade protection and compare against Alpaca’s user protection page.

  5. Check the key’s Access Controls if every write fails and every read succeeds.

Most of these belong in the “refused, and will stay refused until something changes” class. Retrying the same order in a loop will not help and will cost rate limit; error handling patterns for trading APIs explains how to route them.

FAQ

What does 403 Forbidden mean on the Alpaca API?

It means Alpaca understood the request and refused it. On POST /v2/orders the documented meaning is insufficient buying power or shares, but Alpaca also uses 403 for wash-trade protection, account restrictions, market data subscription limits and, in some cases, invalid credentials. The code and message fields in the response body identify which.

Can a wrong API key cause a 403 on Alpaca instead of a 401?

Yes. Alpaca’s error guide states that invalid or missing credentials may in some cases return 403 rather than 401. Paper and live accounts also have separate credentials that only work on their own host. Call GET /v2/account with the same key and host: if that fails too, the problem is the credentials.

What is Alpaca error code 40310000?

It is a general account-level rejection code that Alpaca’s error guide shows with several different messages, including insufficient buying power, account not allowed to short, not authorized to trade, liquidation only, and asset not fractionable. Because one code covers several causes, read the message field to know which one applies.

Why does Alpaca reject my order as a wash trade?

Alpaca rejects orders it detects as possible wash trades — orders that could end up trading against your own orders — with HTTP 403. Its documentation lists bracket, OCO and trailing-stop orders as exceptions to this protection. Compare your open orders in the same symbol against Alpaca’s user protection page to see which one triggered it.