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.
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.jsonThe 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)| orderType | Behavior and required prices |
|---|---|
MARKET | Executes at the current market price. No price fields. |
LIMIT | Executes at limitPrice or better. |
STOP | Becomes a market order once the price reaches stopPrice. |
STOP_LIMIT | Becomes a limit order at limitPrice once the price reaches stopPrice. Needs both prices. |
MARKET_ON_CLOSE | Executes at the closing price. No price fields. |
LIMIT_ON_CLOSE | Executes 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.
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"
}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}")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"
}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"
}| timeInForce | Behavior |
|---|---|
DAY | Expires at the end of the trading day if it has not filled. |
GTD | Stays open until expirationTime, an exact ISO 8601 timestamp no more than 90 days in the future. |
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"),
)
)equityMarketSession controls when an equity order can execute. If you omit it, the order trades in CORE.
| equityMarketSession | When the order can execute |
|---|---|
CORE | Regular market hours. The default. |
EXTENDED | 4:00 a.m. to 8:00 p.m. ET. DAY orders only, in whole shares, up to 10,000 shares per order. |
TWENTY_FOUR_HOURS | Around 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.
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,
)
)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"
}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.
| orderClass | Entry orderType |
|---|---|
SIMPLE | A standalone order with no exit legs. The same as omitting orderClass. |
BRACKET | LIMIT or MARKET |
OCO | LIMIT only |
OTO | LIMIT or MARKET |
quantity. Orders sized with amountcan't be bracketed.CORE session. Omit equityMarketSession or set it to CORE.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.
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}")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.
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 field | Value |
|---|---|
type | AMOUNT to trail by a dollar amount, or PERCENTAGE to trail by a percentage of the price. |
value | The trail distance. For AMOUNT, dollars: 5.00 is $5.00. For PERCENTAGE, a decimal fraction: 0.05 is 5%, not 0.05%. |
limitOffset | Optional. 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.
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"
}
}
}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"
}
}
}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"
}
}
}Replace order changes an open order's price or quantity in one request, for equity, option, and crypto quantity orders.
| Field | Value |
|---|---|
orderId | The open order you are replacing. |
requestId | A new UUID you generate for the replacement order. |
orderType | Required. The replacement order type. |
expiration | Required. The replacement time in force. |
quantity or amount | The replacement size. |
limitPrice, stopPrice | The prices the replacement order type needs. |
OCO, can change limitPrice and stopPrice only. Resubmit quantity, orderType, and expirationunchanged, using the values from the leg's order record.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.jsonReplace 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}")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"
}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
orderId before the first attempt and reuse it on every retry. See Streaming and polling for updates for rate limits.