Stop and stop-limit orders, good-till-date expiration, extended-hours and 24/5 sessions, bracket orders, 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. |
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}")| 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-11-21T20: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.
| 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.
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.
Recommended
orderId before the first attempt and reuse it on every retry. See Polling for updates for rate limits.