Learn how to attach take-profit and stop-loss exits to an entry order, so the position closes automatically without a second API call.
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.
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"
}
]
}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
}
]
}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"
}
}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"
}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"
}
}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.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"
}'quantity. Notional orders using amount cannot be bracketed.equityMarketSession unset, or set it to CORE.orderType must be LIMIT or MARKET — STOP and STOP_LIMIT entries are not accepted, and OCO requires LIMIT.📝 Important Notes