API

Get Started

WelcomeQuickstartGuidesChangelogSDKs and ToolsAdditional InfoFAQ

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 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.

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.

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}")

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-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"),
    )
)

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.

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.

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.

Recommended

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