A Python SDK for interacting with the Public Trading API, providing a simple and intuitive interface for trading operations, market data retrieval, and account management.
$ pip install publicdotcom-py
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install .
$ pip install -e .
$ pip install -e ".[dev]" # for dev dependencies
$ # run example
$ python example.py
$ pytest
Inside of the examples folder are multiple python scripts showcasing specific ways to use the SDK. To run these Python files, first add your API_SECRET_KEY and DEFAULT_ACCOUNT_NUMBER to the .env.example file and change the filename to .env.
from public_api_sdk import PublicApiClient, PublicApiClientConfiguration, ApiKeyAuthConfig
# Initialize the client
client = PublicApiClient(
ApiKeyAuthConfig(api_secret_key="INSERT_API_SECRET_KEY"),
config=PublicApiClientConfiguration(
default_account_number="INSERT_ACCOUNT_NUMBER"
)
)
# Get accounts
accounts = client.get_accounts()
# Get a quote
from public_api_sdk import OrderInstrument, InstrumentType
quotes = client.get_quotes([
OrderInstrument(symbol="AAPL", type=InstrumentType.EQUITY)
])
The SDK ships an AsyncPublicApiClient for use in async/await code. It uses httpx as the HTTP transport, so no background threads are needed.
import asyncio
from public_api_sdk import (
AsyncPublicApiClient,
AsyncPublicApiClientConfiguration,
ApiKeyAuthConfig,
OrderInstrument,
InstrumentType,
)
async def main():
async with AsyncPublicApiClient(
auth_config=ApiKeyAuthConfig(api_secret_key="INSERT_API_SECRET_KEY"),
config=AsyncPublicApiClientConfiguration(
default_account_number="INSERT_ACCOUNT_NUMBER"
),
) as client:
accounts = await client.get_accounts()
quotes = await client.get_quotes([
OrderInstrument(symbol="AAPL", type=InstrumentType.EQUITY)
])
for q in quotes:
print(f"{q.instrument.symbol}: ${q.last}")
asyncio.run(main())
The async with block automatically cancels any active price subscriptions and closes the HTTP connection when the block exits — no try/finally or manual close() call required.
The PublicApiClient is initialized with an API secret key create in your settings page at public.com and optional configuration. The SDK client will handle generation and refresh of access tokens:
from public_api_sdk import PublicApiClient, PublicApiClientConfiguration
from public_api_sdk.auth_config import ApiKeyAuthConfig
config = PublicApiClientConfiguration(
default_account_number="INSERT_ACCOUNT_NUMBER", # Optional default account
)
client = PublicApiClient(
ApiKeyAuthConfig(api_secret_key="INSERT_API_SECRET_KEY"),
config=config
)
ApiKeyAuthConfig accepts an optional validity_minutes argument (default: 15) that controls how long each minted access token stays valid on the server. Valid range is 5 to 1440 minutes (24 hours); values outside this range raise ValueError.
# Long-lived session for a batch job — refresh every ~1 hour
client = PublicApiClient(
ApiKeyAuthConfig(
api_secret_key="INSERT_API_SECRET_KEY",
validity_minutes=60,
),
config=config,
)
The SDK automatically refreshes the token ahead of its expiry (5 minutes of slack), so you don't need to manage refresh yourself — longer validity_minutes just means fewer token-mint round trips.
The default_account_number configuration option simplifies API calls by eliminating the need to specify account_id in every method call. When set, any method that accepts an optional account_id parameter will automatically use the default account number if no account ID is explicitly provided.
# With default_account_number configured
from public_api_sdk import OrderInstrument, InstrumentType
config = PublicApiClientConfiguration(
default_account_number="INSERT_ACCOUNT_NUMBER"
)
client = PublicApiClient(
ApiKeyAuthConfig(api_secret_key="INSERT_API_SECRET_KEY"),
config=config
)
instruments = [
OrderInstrument(symbol="AAPL", type=InstrumentType.EQUITY),
OrderInstrument(symbol="MSFT", type=InstrumentType.EQUITY)
]
# No need to specify account_id
portfolio = client.get_portfolio() # Uses default account number
quotes = client.get_quotes(instruments) # Uses default account number
# You can still override with a specific account
other_portfolio = client.get_portfolio(account_id="DIFFERENT123") # Uses "DIFFERENT123"
# Without default_account_number
config = PublicApiClientConfiguration()
client = PublicApiClient(
ApiKeyAuthConfig(api_secret_key="INSERT_API_SECRET_KEY"),
config=config
)
# Must specify account_id for each call
portfolio = client.get_portfolio(account_id="INSERT_ACCOUNT_NUMBER") # Required
quotes = client.get_quotes(instruments, account_id="INSERT_ACCOUNT_NUMBER") # Required
This is particularly useful when working with a single account, as it reduces code repetition and makes the API calls cleaner.
Retrieve all accounts associated with the authenticated user.
accounts_response = client.get_accounts()
for account in accounts_response.accounts:
print(f"Account ID: {account.account_id}, Type: {account.account_type}")
Get a snapshot of account portfolio including positions, equity, buying power, open orders, and multi-leg option strategies.
portfolio = client.get_portfolio(account_id="YOUR_ACCOUNT_NUMBER") # account_id optional if default set
print(f"Total equity: {portfolio.equity}")
print(f"Buying power: {portfolio.buying_power}")
# Positions include the strategy IDs they belong to (empty list if not part of any strategy)
for position in portfolio.positions:
if position.strategy_ids:
print(f"{position.instrument.symbol} is part of strategies: {position.strategy_ids}")
# Multi-leg option strategies (e.g. spreads). Null if the backend does not support strategies.
if portfolio.strategies:
for strategy in portfolio.strategies:
print(f"\n{strategy.display_name} (id={strategy.strategy_id})")
print(f" Quantity: {strategy.quantity}, current value: ${strategy.current_value}")
for leg in strategy.option_legs:
print(f" {leg.position_type} {leg.ratio_quantity}x {leg.symbol}")
Retrieve paginated account history with optional filtering.
from public_api_sdk import HistoryRequest
history = client.get_history(
HistoryRequest(page_size=10),
account_id="YOUR_ACCOUNT"
)
Retrieve real-time quotes for multiple instruments. Each Quote includes the last/bid/ask, volume, open interest, previous close, a one-day change breakdown, and option-specific details (strike, mid price, and greeks) when the instrument is an option.
from public_api_sdk import OrderInstrument, InstrumentType
quotes = client.get_quotes([
OrderInstrument(symbol="AAPL", type=InstrumentType.EQUITY),
OrderInstrument(symbol="GOOGL", type=InstrumentType.EQUITY)
])
for quote in quotes:
print(f"{quote.instrument.symbol}: ${quote.last} (prev close ${quote.previous_close})")
if quote.one_day_change:
print(f" 1d change: ${quote.one_day_change.change} ({quote.one_day_change.percent_change}%)")
For option quotes (either via get_quotes on an OPTION instrument, or the calls/puts inside get_option_chain), quote.option_details exposes greeks and mid price:
if quote.option_details:
print(f"Strike: ${quote.option_details.strike_price}, mid: ${quote.option_details.mid_price}")
if quote.option_details.greeks:
g = quote.option_details.greeks
print(f" Δ={g.delta} Γ={g.gamma} Θ={g.theta} ν={g.vega} IV={g.implied_volatility}")
All fields on
GreekValuesare optional — the API may omit greeks for illiquid or expired contracts. Always guard withif quote.option_details.greeks:before reading individual values.
Get detailed information about a specific instrument, including trading permissions, short-selling availability, option price increments, and type-specific details (bond or crypto).
from public_api_sdk import (
BondInstrumentDetails,
CryptoInstrumentDetails,
ShortingAvailability,
)
instrument = client.get_instrument(
symbol="AAPL",
instrument_type=InstrumentType.EQUITY
)
print(f"Symbol: {instrument.instrument.symbol}")
print(f"Type: {instrument.instrument.type}")
print(f"Trading: {instrument.trading}")
print(f"Fractional Trading: {instrument.fractional_trading}")
print(f"Option Trading: {instrument.option_trading}")
print(f"Option Spread Trading: {instrument.option_spread_trading}")
# Short-selling — only populated for shortable equities
if instrument.shorting_availability:
print(f"Shorting: {instrument.shorting_availability.value}")
if instrument.shorting_availability == ShortingAvailability.HARD_TO_BORROW:
print(f" HTB rate: {instrument.hard_to_borrow_percentage_rate}%")
# Option price increments — present for optionable equities
if instrument.option_contract_price_increments:
inc = instrument.option_contract_price_increments
print(f"Option increments: below $3 = {inc.increment_below_3}, above $3 = {inc.increment_above_3}")
# Type-specific details (polymorphic on payload_type)
details = instrument.instrument_details
if isinstance(details, CryptoInstrumentDetails):
print(f"Crypto precision: qty={details.crypto_quantity_precision}, price={details.crypto_price_precision}")
print(f"Tradable in NY: {details.tradable_in_new_york}")
elif isinstance(details, BondInstrumentDetails):
print(f"Bond outstanding: {details.has_outstanding}")
Retrieve all available trading instruments with optional filtering.
from public_api_sdk import InstrumentsRequest, InstrumentType, TradingPermission
instruments = client.get_all_instruments(
InstrumentsRequest(
type_filter=[InstrumentType.EQUITY],
trading_filter=[TradingPermission.BUY_AND_SELL],
)
)
Fetch OHLCV bar data for any symbol over a standard time period. The response is split into pre-market, regular-market, and after-hours sessions, each containing a list of Bar objects.
from public_api_sdk import BarPeriod
bars = client.get_bars("AAPL", BarPeriod.YEAR)
print(f"Total expected bars: {bars.total_expected_bars}")
for bar in bars.regular_market.bars:
print(f" {bar.timestamp} O={bar.open} H={bar.high} L={bar.low} C={bar.close} V={bar.volume}")
Available periods: DAY, WEEK, MONTH, QUARTER, HALF_YEAR, YEAR, FIVE_YEARS, YTD, SINCE_PURCHASE.
Override the bar size by passing an aggregation:
from public_api_sdk import BarAggregation
# Today's intraday bars at 5-minute resolution
bars = client.get_bars("AAPL", BarPeriod.DAY, aggregation=BarAggregation.FIVE_MINUTES)
# Past week at 30-minute resolution
bars = client.get_bars("AAPL", BarPeriod.WEEK, aggregation=BarAggregation.THIRTY_MINUTES)
Available aggregations: ONE_MINUTE, FIVE_MINUTES, TEN_MINUTES, FIFTEEN_MINUTES, THIRTY_MINUTES, ONE_HOUR, ONE_DAY, ONE_WEEK, ONE_MONTH, THREE_MONTHS, SIX_MONTHS, ONE_YEAR.
get_bars defaults to InstrumentType.EQUITY. Pass instrument_type to request bars for crypto, options, or indices:
from public_api_sdk import BarAggregation, BarPeriod, InstrumentType
# Bitcoin year-to-date, hourly bars
bars = client.get_bars(
"BTC",
BarPeriod.YTD,
instrument_type=InstrumentType.CRYPTO,
aggregation=BarAggregation.ONE_HOUR,
)
# Index bars
bars = client.get_bars("SPX", BarPeriod.YEAR, instrument_type=InstrumentType.INDEX)
Supported values: EQUITY, CRYPTO, OPTION, INDEX. Any other InstrumentType raises ValueError.
bars.last_regular_trading_session_close is a LastSessionClose model (or None) carrying the prior session's close price and change:
last = bars.last_regular_trading_session_close
if last is not None:
print(f"Prior close: ${last.close} on {last.close_date} ({last.percent_change}%)")
Use BarPeriod.SINCE_PURCHASE with a purchase_date to chart performance from a specific entry point:
bars = client.get_bars(
"AAPL",
BarPeriod.SINCE_PURCHASE,
purchase_date="2024-01-02", # YYYY-MM-DD
)
if bars.total_gain_loss is not None:
print(f"Gain/loss since purchase: ${bars.total_gain_loss} ({bars.total_gain_loss_percentage}%)")
Retrieve available option expiration dates for an underlying instrument.
```python from public_api_sdk import OptionExpirationsRequest, OrderInstrument, InstrumentType
expirations = client.get_option_expirations( OptionExpirationsRequest( instrument=OrderInstrument( symbol="AAPL", type=InstrumentType.EQUITY ) ) ) print(f"Available expirations: {expirations.expiratio
$ claude mcp add publicdotcom-py \
-- python -m otcore.mcp_server <graph>