API

Get Started

WelcomeQuickstartGuidesChangelogSDKs and ToolsAdditional Info

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 instrument

Market Data

POSTGet quotesPOSTGet option expirationsPOSTGet option chainGETGet bars v2GETGet bars v2 with aggregation

Order Placement

POSTPreflight single legPOSTPreflight multi legPOSTPlace orderPUTReplace orderPOSTPlace multileg orderGETGet orderDELETECancel order

Option Details

GETGet option greeksPOSTGet strategy quote
HelpFeedback

Get Started

Selling specific tax lots

When you close an equity position, you can control exactly which tax lots are sold. This guide walks through viewing your unrealized tax lots, inspecting the lots behind a symbol, and placing a sell order that targets specific lots — with raw HTTP requests and the Python SDK side by side.

Before you start

For the HTTP examples you need an access token with the portfolio scope, which the tax lot endpoints require. For the Python examples, install version 0.1.19 or later of the Public Python SDK — the release that added tax lot support:

pip install "publicdotcom-py>=0.1.19"

The SDK handles token generation for you — construct the client with the API secret key from your Public account settings:

from public_api_sdk import (
    ApiKeyAuthConfig,
    PublicApiClient,
    PublicApiClientConfiguration,
)

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

View your unrealized tax lots

Start with an account-wide overview of your unrealized tax lots. Each entry summarizes a position along with its cost basis and how its gain or loss splits between short and long term:

curl --request GET \
  --url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/taxlots/unrealized \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

In this example the account holds 15 shares of AAPL, bought at two different times:

{
  "asOf": "2026-07-21",
  "lots": [
    {
      "accountNumber": "YOUR_ACCOUNT_NUMBER",
      "symbol": "AAPL",
      "cusip": "037833100",
      "companyName": "Apple Inc.",
      "quantity": "15",
      "costBasis": "2400.00",
      "unitCost": "160.00",
      "currentPrice": "212.34",
      "currentValue": "3185.10",
      "gainLoss": "785.10",
      "shortTermGainLoss": "161.70",
      "longTermGainLoss": "623.40"
    }
  ],
  "shortTerm": "161.70",
  "longTerm": "623.40",
  "sixtyFortyTerm": "0.00",
  "totalProfitLoss": "785.10"
}

The same call with the Python SDK:

summary = client.get_unrealized_tax_lots()

print(f"Total P/L as of {summary.as_of}: {summary.total_profit_loss}")
for lot in summary.lots:
    print(f"{lot.symbol}: {lot.quantity} shares, gain/loss {lot.gain_loss}")

Drill into a single symbol

The overview shows the position as a whole. To pick lots to sell, fetch the per-symbol detail, which lists every lot with its open date, holding term, and — most importantly — its lotSelectionId:

curl --request GET \
  --url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/taxlots/unrealized/AAPL \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

The response breaks the 15 shares into their two lots — 10 long-term shares bought at $150.00 and 5 short-term shares bought at $180.00:

{
  "asOf": "2026-07-21",
  "symbol": "AAPL",
  "companyName": "Apple Inc.",
  "lots": [
    {
      "quantity": "10",
      "costBasis": "1500.00",
      "unitCost": "150.00",
      "currentPrice": "212.34",
      "currentValue": "2123.40",
      "gainLoss": "623.40",
      "shortTermGainLoss": "0.00",
      "longTermGainLoss": "623.40",
      "openDate": "2024-01-15",
      "term": "LONG",
      "lotSelectionId": "AAPL;2024-01-15;150.00;10"
    },
    {
      "quantity": "5",
      "costBasis": "900.00",
      "unitCost": "180.00",
      "currentPrice": "212.34",
      "currentValue": "1061.70",
      "gainLoss": "161.70",
      "shortTermGainLoss": "161.70",
      "longTermGainLoss": "0.00",
      "openDate": "2026-03-10",
      "term": "SHORT",
      "lotSelectionId": "AAPL;2026-03-10;180.00;5"
    }
  ]
}

The lotSelectionId is the handle you pass back when selling. If it is null, the lot cannot be selected — for example because the symbol is undergoing a corporate action, or the lot was deduced from trade history. Separately, the outOfDateStatusfield flags when a lot's information is out of date (for example, an order or trade on the symbol today) — lot instructions are only accepted while the information is current. With the Python SDK:

detail = client.get_unrealized_tax_lots_for_symbol("AAPL")

for lot in detail.lots or []:
    print(
        f"Opened {lot.open_date}: {lot.quantity} shares at {lot.unit_cost} "
        f"({lot.term}), lot ID: {lot.lot_selection_id}"
    )

Lot prices and gains are calculated once per trading session and do not change intraday. To evaluate lots against a price of your choosing, pass an explicit price — for example ?price=215.00 on the request above, or price="215.00" in the SDK call.

Sell specific lots

Now place a sell order that names its lots. Suppose you want to sell 12 of the 15 shares: all 10 long-term shares, plus 2 of the 5 short-term shares — selling part of a lot is allowed. Attach one taxLotMatchingInstructionsentry per lot, using each lot's lotSelectionId as the taxLotId:

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": "SELL",
    "orderType": "LIMIT",
    "limitPrice": "212.00",
    "expiration": {
      "timeInForce": "DAY"
    },
    "quantity": "12",
    "openCloseIndicator": "CLOSE",
    "taxLotMatchingInstructions": [
      {
        "taxLotId": "AAPL;2024-01-15;150.00;10",
        "quantity": "10"
      },
      {
        "taxLotId": "AAPL;2026-03-10;180.00;5",
        "quantity": "2"
      }
    ]
  }'

The response confirms the order was submitted:

{
  "orderId": "550e8400-e29b-41d4-a716-446655440000"
}

With the Python SDK, build the same order with GatewayTaxLotMatchingInstruction objects:

import uuid
from decimal import Decimal

from public_api_sdk import (
    GatewayTaxLotMatchingInstruction,
    InstrumentType,
    OpenCloseIndicator,
    OrderExpirationRequest,
    OrderInstrument,
    OrderRequest,
    OrderSide,
    OrderType,
    TimeInForce,
)

order = OrderRequest(
    order_id=str(uuid.uuid4()),
    instrument=OrderInstrument(symbol="AAPL", type=InstrumentType.EQUITY),
    order_side=OrderSide.SELL,
    order_type=OrderType.LIMIT,
    limit_price=Decimal("212.00"),
    expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
    quantity=Decimal("12"),
    open_close_indicator=OpenCloseIndicator.CLOSE,
    tax_lot_matching_instructions=[
        GatewayTaxLotMatchingInstruction(
            tax_lot_id="AAPL;2024-01-15;150.00;10",
            quantity="10",
        ),
        GatewayTaxLotMatchingInstruction(
            tax_lot_id="AAPL;2026-03-10;180.00;5",
            quantity="2",
        ),
    ],
)

new_order = client.place_order(order)
print(f"Order submitted: {new_order.order_id}")

📝 Rules for tax lot instructions

  • Only allowed on SELL equity orders that close a position, and only for MARKET orders or good-for-the-day LIMIT orders.
  • At most 8 instructionsper order, all for the order's symbol, and their quantities must sum exactly to the order quantity.
  • A lot's instruction quantity must be greater than 0 and no more than the lot's quantity — partial lots are fine.
  • Instructions are only accepted while the tax lot information is current — check outOfDateStatus to see how a lot is out of date. A lot with a null lotSelectionId cannot be selected at all.
  • There is no guarantee the instructions are applied exactly as specified.
  • Order placement is asynchronous – poll the order status after submission to confirm execution.

Going further

Two more endpoints round out the workflow. You can export every unrealized lot as a CSV file via /userapigateway/trading/{accountId}/taxlots/csv/unrealized (returned as a base64-encoded file, or get_unrealized_tax_lots_csv() in the SDK). And the single-leg preflight endpoint accepts the same taxLotMatchingInstructions field, so you can validate a lot-specific sell before submitting it.

For full request and response schemas, see the tax lot selling endpoint reference.