API

Get Started

WelcomeQuickstartGuidesChangelogSDKs and ToolsAdditional Info

Authorization

POSTCreate personal access token

List Accounts

GETGet accounts

Account Details

GETGet account portfolio v2GETGet history

Tax Lot Selling

GETGet unrealized tax lotsGETGet unrealized tax lots for symbolGETGet unrealized tax lots csv

Instrument Details

GETGet all instrumentsGETGet instrumentGETSearch bonds

Market Data

POSTGet quotesGETGet bond detailsPOSTGet option expirationsPOSTGet option chainGETGet bars v2GETGet bars v2 with aggregation

Order Placement

POSTPreflight single legPOSTPreflight multi legPOSTPlace orderPUTReplace orderPOSTPlace multileg orderGETGet orderDELETECancel order

Option Details

GETGet option greeksPOSTGet strategy quote
HelpFeedback

Get Started

Placing your first bracket order

Learn how to attach take-profit and stop-loss exits to an entry order, so the position closes automatically without a second API call.

How a bracket order works

A bracket order is a single request that carries its own exits. You submit one entry order with an orderClass and up to two exit legs. When the entry fills, the exit legs are placed for you automatically:

  • takeProfit takes a limitPrice and is placed as a LIMIT order on the opposite side of the entry.
  • stopLoss takes a stopPrice, and is placed as a STOP order. Add an optional limitPrice to place it as a STOP_LIMIT order instead.

Every order in the bracket shares a bracketId, which is the order id of the entry order.

Get your account ID

First, retrieve your account ID using the accounts endpoint:

curl --request GET \
  --url https://api.public.com/userapigateway/trading/account \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

This returns your available accounts with their IDs:

{
  "accounts": [
    {
      "accountId": "YOUR_ACCOUNT_ID",
      "accountType": "BROKERAGE",
      "optionsLevel": "LEVEL_2",
      "brokerageAccountType": "MARGIN"
    }
  ]
}

Get current market price

Pull a quote so you can set the entry, target, and stop levels around the current price:

curl --request POST \
  --url https://api.public.com/userapigateway/marketdata/{YOUR_ACCOUNT_ID}/quotes \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "instruments": [
      {
        "symbol": "AAPL",
        "type": "EQUITY"
      }
    ]
  }'

This returns current bid/ask prices and last trade information:

{
  "quotes": [
    {
      "instrument": {
        "symbol": "AAPL",
        "type": "EQUITY"
      },
      "outcome": "SUCCESS",
      "last": "150.25",
      "lastTimestamp": "2024-01-15T15:59:00Z",
      "bid": "150.20",
      "bidSize": 100,
      "ask": "150.30",
      "askSize": 200
    }
  ]
}

Run preflight on the entry

Preflight estimates cost and buying power for the entry order. Send the entry exactly as you plan to submit it, but without the bracket fields — the preflight request has no orderClass, takeProfit, or stopLoss fields. The exit legs are not priced ahead of time.

curl --request POST \
  --url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/preflight/single-leg \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "instrument": {
      "symbol": "AAPL",
      "type": "EQUITY"
    },
    "orderSide": "BUY",
    "orderType": "LIMIT",
    "limitPrice": "150.00",
    "expiration": {
      "timeInForce": "DAY"
    },
    "quantity": "10"
  }'

The response provides estimated costs and order details:

{
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "estimatedCommission": "0.00",
  "orderValue": "1500.00",
  "estimatedQuantity": "10",
  "estimatedCost": "1500.00",
  "buyingPowerRequirement": "1500.00",
  "regulatoryFees": {
    "secFee": "0.00",
    "tafFee": "0.00"
  }
}

Place the bracket order

Submit the entry with orderClass set to BRACKET and both exit legs attached. Generate a unique UUID for the orderId:

curl --request POST \
  --url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/order \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "orderId": "550e8400-e29b-41d4-a716-446655440000",
    "instrument": {
      "symbol": "AAPL",
      "type": "EQUITY"
    },
    "orderSide": "BUY",
    "orderType": "LIMIT",
    "limitPrice": "150.00",
    "expiration": {
      "timeInForce": "DAY"
    },
    "quantity": "10",
    "orderClass": "BRACKET",
    "takeProfit": {
      "limitPrice": "165.00"
    },
    "stopLoss": {
      "stopPrice": "142.00"
    }
  }'

The response returns the id of the entry order only. The exit legs are created later, when the entry fills:

{
  "orderId": "550e8400-e29b-41d4-a716-446655440000"
}

To use a stop-limit exit instead of a plain stop, add a limitPrice to the stop-loss leg:

"stopLoss": {
  "stopPrice": "142.00",
  "limitPrice": "141.50"
}

Check the entry order

Poll the entry order to see whether it has filled. A bracketed order carries a bracketId; on the entry order it matches its own orderId:

curl --request GET \
  --url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/order/550e8400-e29b-41d4-a716-446655440000 \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Once the entry fills, the exit legs become live orders:

{
  "orderId": "550e8400-e29b-41d4-a716-446655440000",
  "bracketId": "550e8400-e29b-41d4-a716-446655440000",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "createdAt": "2024-01-15T16:00:00Z",
  "type": "LIMIT",
  "side": "BUY",
  "status": "FILLED",
  "quantity": "10",
  "filledQuantity": "10",
  "averagePrice": "149.98",
  "limitPrice": "150.00",
  "expiration": {
    "timeInForce": "DAY"
  }
}

Choosing an order class

Four values are accepted. Omitting orderClass behaves the same as SIMPLE:

  • SIMPLE — a standalone order with no exit legs. This is the default.
  • BRACKET — an entry with takeProfit and stopLoss. The entry may be LIMIT or MARKET.
  • OCO — the entry order must be LIMIT. MARKET entries are rejected.
  • OTO — the entry may be LIMIT or MARKET.

Adjusting an exit leg

The entry order of a bracket cannot be replaced. The closing legs can be repriced with a cancel-replace, but only their limitPrice and stopPrice may change. Resubmit quantity, orderType, and expiration unchanged. Here orderId is the leg being replaced and requestId is a fresh UUID for the replacement. Read the leg's current quantity, orderType, and expiration from its order record and send those values back as they are — the values below are an example, not defaults:

curl --request PUT \
  --url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/order \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "orderId": "TAKE_PROFIT_LEG_ORDER_ID",
    "requestId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "orderType": "LIMIT",
    "expiration": {
      "timeInForce": "DAY"
    },
    "quantity": "10",
    "limitPrice": "168.00"
  }'

Requirements and limits

  • Bracket orders are supported for equities and options only. Crypto and bond orders cannot be bracketed.
  • The entry requires a whole-share quantity. Notional orders using amount cannot be bracketed.
  • Brackets must trade in the CORE market session. Leave equityMarketSession unset, or set it to CORE.
  • The entry orderType must be LIMIT or MARKET — STOP and STOP_LIMIT entries are not accepted, and OCO requires LIMIT.

📝 Important Notes

  • Order placement is asynchronous – poll the entry order after submission to confirm execution.
  • The exit legs do not exist until the entry order fills, so they cannot be inspected or repriced before that point.
  • Preflight covers the entry order only. Budget for the exits yourself when sizing the position.