API

Get Started

WelcomeQuickstartGuidesChangelogSDKs and ToolsAdditional InfoFAQ

Streaming

WSSOrder UpdatesWSSTrades

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

Orders

POSTPreflight single legPOSTPreflight multi legPOSTPlace orderPUTReplace orderPOSTSearch ordersPOSTPlace multileg orderGETGet orderDELETECancel order

Option Details

GETGet option greeksPOSTGet strategy quote

Event Contracts

POSTGet event summaryGETGet event categoriesGETGet event details
HelpFeedback

Additional Info

Advanced Orders

Stop, stop-limit, and on-close orders, good-till-date expiration, extended-hours and 24/5 sessions, bracket orders, trailing stops, and cancel-replace, with a request body for each.

Sending the examples

Every example on this page is a request body for Place order, except the cancel-replace examples, which use Replace order. Save a body to order.json and send it:

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 @order.json

The Python examples assume this setup. Bracket orders need a recent version of the SDK, so upgrade with pip install --upgrade publicdotcom-py first.

import uuid
from decimal import Decimal

from public_api_sdk import (
    ApiKeyAuthConfig,
    InstrumentType,
    OrderExpirationRequest,
    OrderInstrument,
    OrderRequest,
    OrderSide,
    OrderType,
    PublicApiClient,
    PublicApiClientConfiguration,
    TimeInForce,
)

client = PublicApiClient(
    ApiKeyAuthConfig(api_secret_key="YOUR_API_SECRET_KEY"),
    config=PublicApiClientConfiguration(
        default_account_number="YOUR_ACCOUNT_ID"
    ),
)

aapl = OrderInstrument(symbol="AAPL", type=InstrumentType.EQUITY)

Order types

orderTypeBehavior and required prices
MARKETExecutes at the current market price. No price fields.
LIMITExecutes at limitPrice or better.
STOPBecomes a market order once the price reaches stopPrice.
STOP_LIMITBecomes a limit order at limitPrice once the price reaches stopPrice. Needs both prices.
MARKET_ON_CLOSEExecutes at the closing price. No price fields.
LIMIT_ON_CLOSEExecutes at the closing price, only if it's at limitPrice or better.

Size an order with quantity (shares or contracts) or amount (a dollar value), never both. See Order Limits for minimums, maximums, and price increments.

Stop

Sell 10 shares at market if the price falls to $210.00 or below.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "SELL",
  "orderType": "STOP",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "stopPrice": "210.00"
}

Stop limit

When the price falls to $210.00, place a limit order to sell 10 shares at $208.00 or better.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "SELL",
  "orderType": "STOP_LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "stopPrice": "210.00",
  "limitPrice": "208.00"
}

With the Python SDK:

order = client.place_order(
    OrderRequest(
        order_id=str(uuid.uuid4()),
        instrument=aapl,
        order_side=OrderSide.SELL,
        order_type=OrderType.STOP_LIMIT,
        expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
        quantity=Decimal("10"),
        stop_price=Decimal("210.00"),
        limit_price=Decimal("208.00"),
    )
)
print(f"Submitted: {order.order_id}")

Market on close

Sell 10 shares at the closing price.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "SELL",
  "orderType": "MARKET_ON_CLOSE",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10"
}

Limit on close

Sell 10 shares at the closing price, only if it is $225.00 or better.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "SELL",
  "orderType": "LIMIT_ON_CLOSE",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00"
}

Time in force

timeInForceBehavior
DAYExpires at the end of the trading day if it has not filled.
GTDStays open until expirationTime, an exact ISO 8601 timestamp no more than 90 days in the future.

Good till date

A limit order that stays open until an exact expiration time, up to 90 days out.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "GTD",
    "expirationTime": "2026-12-11T20:00:00.000Z"
  },
  "quantity": "10",
  "limitPrice": "220.00"
}

With the Python SDK:

from datetime import datetime, timedelta, timezone

expires_at = datetime.now(timezone.utc) + timedelta(days=30)

order = client.place_order(
    OrderRequest(
        order_id=str(uuid.uuid4()),
        instrument=aapl,
        order_side=OrderSide.BUY,
        order_type=OrderType.LIMIT,
        expiration=OrderExpirationRequest(
            time_in_force=TimeInForce.GTD,
            expiration_time=expires_at,
        ),
        quantity=Decimal("10"),
        limit_price=Decimal("220.00"),
    )
)

Market sessions

equityMarketSession controls when an equity order can execute. If you omit it, the order trades in CORE.

equityMarketSessionWhen the order can execute
CORERegular market hours. The default.
EXTENDED4:00 a.m. to 8:00 p.m. ET. DAY orders only, in whole shares, up to 10,000 shares per order.
TWENTY_FOUR_HOURSAround the clock, five days a week.

The order record from Get order reports the session with different values: REGULAR, REST_OF_DAY, or TWENTY_FOUR_HOURS.

Extended hours

A DAY limit order that can execute from 4:00 a.m. to 8:00 p.m. ET.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00",
  "equityMarketSession": "EXTENDED"
}

With the Python SDK:

from public_api_sdk import EquityMarketSession

order = client.place_order(
    OrderRequest(
        order_id=str(uuid.uuid4()),
        instrument=aapl,
        order_side=OrderSide.BUY,
        order_type=OrderType.LIMIT,
        expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
        quantity=Decimal("10"),
        limit_price=Decimal("225.00"),
        equity_market_session=EquityMarketSession.EXTENDED,
    )
)

24/5

A limit order that can trade around the clock, five days a week.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00",
  "equityMarketSession": "TWENTY_FOUR_HOURS"
}

Bracket orders

A bracket attaches exit legs to an entry order, and the exit legs are placed automatically when the entry fills. takeProfit is placed as a LIMIT order on the opposite side of the entry. stopLoss is placed as a STOP order, or as a STOP_LIMIT order if you include its limitPrice. To make it trail the price instead, see Trailing stops.

orderClassEntry orderType
SIMPLEA standalone order with no exit legs. The same as omitting orderClass.
BRACKETLIMIT or MARKET
OCOLIMIT only
OTOLIMIT or MARKET

Requirements

  • Equities and options only. Crypto and bonds can't be bracketed.
  • A whole-share quantity. Orders sized with amountcan't be bracketed.
  • The CORE session. Omit equityMarketSession or set it to CORE.
  • At least one of takeProfit or stopLoss.

The response returns the entry's orderId. Every order in the bracket carries a bracketId equal to that entry orderId, so you can find the exit legs later. Preflight covers the entry only; send it without the bracket fields.

Bracket

Buy 10 shares at $225.00 with a take-profit at $240.00 and a stop-loss at $215.00.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00",
  "orderClass": "BRACKET",
  "takeProfit": {
    "limitPrice": "240.00"
  },
  "stopLoss": {
    "stopPrice": "215.00"
  }
}

With the Python SDK:

from public_api_sdk import OrderClass, StopLoss, TakeProfit

order = client.place_order(
    OrderRequest(
        order_id=str(uuid.uuid4()),
        instrument=aapl,
        order_side=OrderSide.BUY,
        order_type=OrderType.LIMIT,
        expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
        quantity=Decimal("10"),
        limit_price=Decimal("225.00"),
        order_class=OrderClass.BRACKET,
        take_profit=TakeProfit(limit_price=Decimal("240.00")),
        # add limit_price to place the stop-loss as a STOP_LIMIT order
        stop_loss=StopLoss(stop_price=Decimal("215.00")),
    )
)
print(f"Entry order: {order.order_id}")

Bracket with a stop-limit exit

The same bracket, with the stop-loss placed as a STOP_LIMIT order that sells at $213.00 or better.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00",
  "orderClass": "BRACKET",
  "takeProfit": {
    "limitPrice": "240.00"
  },
  "stopLoss": {
    "stopPrice": "215.00",
    "limitPrice": "213.00"
  }
}

For a full walkthrough, see Placing your first bracket order. For how the legs behave once placed, see Bracket order legs.

Trailing stops

A trailing stop is a stop-loss whose stop price follows the market. As the price moves in your favor, the stop price moves with it, staying a fixed distance away. It never moves back. Add a trailingStop to stopLoss in place of stopPrice, on a BRACKET or an OTO order.

trailingStop fieldValue
typeAMOUNT to trail by a dollar amount, or PERCENTAGE to trail by a percentage of the price.
valueThe trail distance. For AMOUNT, dollars: 5.00 is $5.00. For PERCENTAGE, a decimal fraction: 0.05 is 5%, not 0.05%.
limitOffsetOptional. Makes the exit a trailing stop-limit. Uses the same unit as type: with AMOUNT, 0.01 is $0.01; with PERCENTAGE, 0.01 is 1%. For a sell exit, the limit price sits that far below the trailing stop price.

Without limitOffset, the stop-loss leg is placed as a TRAILING_STOP order. With it, the leg is a TRAILING_STOP_LIMIT order. Once the leg is live, Get order on it returns the current trailing stop price, and Replace order can change the trail.

Bracket with a trailing stop

Buy 10 shares at $225.00 with a take-profit at $240.00. Once the entry fills, the stop-loss is placed as a TRAILING_STOP order that trails $5.00 below the price.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00",
  "orderClass": "BRACKET",
  "takeProfit": {
    "limitPrice": "240.00"
  },
  "stopLoss": {
    "trailingStop": {
      "type": "AMOUNT",
      "value": "5.00"
    }
  }
}

Bracket with a trailing stop-limit

The same bracket, with the stop-loss placed as a TRAILING_STOP_LIMIT order that trails 5% below the price (0.05 is 5%) and sells at 1% below the trailing stop price or better.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00",
  "orderClass": "BRACKET",
  "takeProfit": {
    "limitPrice": "240.00"
  },
  "stopLoss": {
    "trailingStop": {
      "type": "PERCENTAGE",
      "value": "0.05",
      "limitOffset": "0.01"
    }
  }
}

Trailing stop without a take-profit

Buy 10 shares at $225.00 with no take-profit. Once the entry fills, the stop-loss trails $10.00 below the price, with its limit $0.01 below the trailing stop price.

{
  "orderId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "instrument": {
    "symbol": "AAPL",
    "type": "EQUITY"
  },
  "orderSide": "BUY",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "225.00",
  "orderClass": "OTO",
  "stopLoss": {
    "trailingStop": {
      "type": "AMOUNT",
      "value": "10.00",
      "limitOffset": "0.01"
    }
  }
}

Cancel and replace

Replace order changes an open order's price or quantity in one request, for equity, option, and crypto quantity orders.

FieldValue
orderIdThe open order you are replacing.
requestIdA new UUID you generate for the replacement order.
orderTypeRequired. The replacement order type.
expirationRequired. The replacement time in force.
quantity or amountThe replacement size.
limitPrice, stopPriceThe prices the replacement order type needs.

Rules

  • Don't send replaces for the same order in parallel. Ordering isn't guaranteed across HTTP calls.
  • The entry of a bracket can't be replaced. Its exit legs, including both legs of an OCO, can change limitPrice and stopPrice only. Resubmit quantity, orderType, and expirationunchanged, using the values from the leg's order record.
  • Replacement is asynchronous. Poll Get order to confirm it.
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 @replace.json

Reprice a limit order

Replace an open limit order with a new limit price. Set orderId to the order being replaced.

{
  "orderId": "ORDER_ID_TO_REPLACE",
  "requestId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "orderType": "LIMIT",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "limitPrice": "226.00"
}

With the Python SDK:

from public_api_sdk import CancelAndReplaceRequest

replacement = client.cancel_and_replace_order(
    CancelAndReplaceRequest(
        order_id="ORDER_ID_TO_REPLACE",
        request_id=str(uuid.uuid4()),
        order_type=OrderType.LIMIT,
        expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
        quantity=Decimal("10"),
        limit_price=Decimal("226.00"),
    )
)
print(f"Replacement order: {replacement.order_id}")

Move a bracket stop-loss

Raise a bracket's stop-loss leg. Resubmit its quantity, orderType, and expiration unchanged.

{
  "orderId": "STOP_LOSS_LEG_ORDER_ID",
  "requestId": "0d2abd8d-3625-4c83-a806-98abf35567cc",
  "orderType": "STOP",
  "expiration": {
    "timeInForce": "DAY"
  },
  "quantity": "10",
  "stopPrice": "220.00"
}

Retries and order status

orderId is the deduplication key for a new order. If a request times out, resend the identical body with the same orderId. If the first request succeeded, the retry has no effect, and changing other fields won't alter the order.

Placement is asynchronous: a successful response confirms submission, not execution. Poll Get order for the status, which is one of NEW, PARTIALLY_FILLED, FILLED, PENDING_REPLACE, REPLACED, PENDING_CANCEL, CANCELLED, QUEUED_CANCELLED, REJECTED, or EXPIRED, or stream status changes with the Order Updates WebSocket.

Recommended

Generate the orderId before the first attempt and reuse it on every retry. See Streaming and polling for updates for rate limits.