Learn how to browse prediction markets, read an event's outcomes and contract pricing, pull quotes and price history, and place your first event contract order.
A prediction market is a question about a future event, such as "Fed decision in Oct 2026?". The API models it in three levels:
eventSymbol such as KALSHI.KXFEDDECISION-26OCT..Y) and a NO contract (symbol ending in .N).Contracts are priced in dollars between 0.00 and 1.00. A contract that settles in your favor pays $1.00, and one that settles against you pays $0.00, so the price is the market's implied probability of that result. A YES contract trading at 0.80 means the market puts the outcome at roughly 80%.
Example: a hypothetical trade
Suppose an event asks "Will it snow in Central Park on December 25?" The YES contract has an ask of $0.30, so the market puts snow at about 30%. You think it's more likely than that, so you buy 10 YES contracts at $0.30 for $3.00. What happens next:
If you thought it wouldn't snow, you'd buy the NO contract instead, at around $0.70. Ten NO contracts would cost about $7.00 and pay $10.00 if the day stays dry. These figures leave out commission and exchange fees, which the preflight step below shows you before you trade.
The browsing endpoints only need an access token, but quotes and orders need an account ID. Retrieve it with the accounts endpoint:
curl --request GET \
--url https://api.public.com/userapigateway/trading/account \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'Use the ID of the brokerage account you want to trade from:
{
"accounts": [
{
"accountId": "YOUR_ACCOUNT_ID",
"accountType": "BROKERAGE",
"optionsLevel": "NONE",
"brokerageAccountType": "CASH",
"tradePermissions": "BUY_AND_SELL"
}
]
}Every step below includes an HTTP example and a Python example. To follow along in Python, install version 0.1.26 or later of the Public Python SDK. That release added prediction market support:
pip install "publicdotcom-py>=0.1.26"The SDK generates access tokens for you. Create the client with the API secret key from your Public account settings, and set your account ID as the default so you don't have to pass it to every call:
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_ID"
),
)Start by listing the categories you can browse with Get event categories:
curl --request GET \
--url https://api.public.com/userapigateway/eventcontract/summary/categories \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'Each category lists its subcategories and the recurrence frequencies you can filter on (trimmed for brevity):
{
"categories": [
{
"category": "Crypto",
"subcategories": ["All", "BTC", "ETH", "SOL", "XRP"],
"eventFrequency": {
"show": true,
"frequencies": [
"ALL",
"ONCE",
"FIFTEEN_MINUTES",
"ONE_HOUR",
"ONE_DAY",
"ONE_WEEK",
"ONE_MONTH",
"ONE_YEAR"
]
}
},
{
"category": "Economics",
"subcategories": ["All", "Fed", "GDP", "Housing", "Inflation", "Jobs & Economy"],
"eventFrequency": {
"show": false,
"frequencies": []
}
}
]
}When eventFrequency.show is true, the category has recurring events (for example, an hourly Bitcoin price market), and you can narrow results by frequency in the next step.
The same call with the Python SDK:
response = client.get_event_categories()
for category in response.categories:
print(f"{category.category}: {', '.join(category.subcategories)}")Pass a category, and optionally a subcategory, to Get event summary to list its events. sortingMode is required and accepts VOLUME, EXPIRATION, or RECENTLY_ADDED:
curl --request POST \
--url https://api.public.com/userapigateway/eventcontract/summary \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"category": "Economics",
"subcategory": "Fed",
"sortingMode": "VOLUME",
"displayResolvedEvents": false
}'The response is a page of event summaries. Summaries don't include outcomes or prices; they give you the eventSymbol to look up next:
{
"content": [
{
"eventSymbol": "KALSHI.KXRATECUTCOUNT-26DEC31",
"title": "Number of rate cuts in 2026?",
"volume": "20985664.14",
"resolutionTime": "2027-01-01T04:59:00+00:00",
"resolved": false,
"halted": false,
"category": "Economics",
"subcategories": ["Fed"],
"symbols": []
},
{
"eventSymbol": "KALSHI.KXFEDDECISION-26OCT",
"title": "Fed decision in Oct 2026?",
"volume": "18273927.72",
"resolutionTime": "2026-10-28T17:59:00+00:00",
"resolved": false,
"halted": false,
"category": "Economics",
"subcategories": ["Fed"],
"symbols": []
}
],
"nextToken": "MTAw"
}Each page holds up to 100 events. When the response includes a nextToken, send the same request with "nextToken": "MTAw" added to fetch the next page. The token is absent on the last page.
Other optional fields narrow the results further: createdWithinDays returns only recently listed events, and the filters object accepts eventIds, frequencies, and a resolutionTimeStart / resolutionTimeEnd window. When you send filters, include both eventIds and frequencies, even if one of them is an empty array.
In Python, build an EventSummaryRequest and follow next_token until it's None to collect every page:
from public_api_sdk import EventSortingMode, EventSummaryRequest
request = EventSummaryRequest(
category="Economics",
subcategory="Fed",
sorting_mode=EventSortingMode.VOLUME,
display_resolved_events=False,
)
events = []
while True:
page = client.get_event_summary(request)
events.extend(page.content)
if page.next_token is None:
break
request.next_token = page.next_token
for event in events[:5]:
print(f"{event.event_symbol}: {event.title} (volume {event.volume})")Pass an eventSymbol to Get event details to load its outcomes, the YES/NO contracts for each outcome, and their current pricing. By default every outcome is returned; set includeAllOutcomes=false to return a shorter list for events with many outcomes:
curl --request GET \
--url 'https://api.public.com/userapigateway/eventcontract/details/KALSHI.KXFEDDECISION-26OCT?includeAllOutcomes=false' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'The response describes the full event. It's trimmed here to its first outcome; this event returns five:
{
"eventSymbol": "KALSHI.KXFEDDECISION-26OCT",
"exchange": "KALSHI",
"resolved": false,
"halted": false,
"title": "Fed decision in Oct 2026?",
"category": "Economics",
"volume": "18273927.72",
"subcategories": ["Fed"],
"cftcContract": {
"contractTermsUrl": "https://assets.kalshi.com/contract_terms/FEDDECISION.pdf",
"prohibitions": [
"Persons who are employed by any of the Source Agencies are not permitted to trade on the Contract.",
"Persons who hold any material, non-public information on the Underlying are not permitted to trade on the Contract."
],
"resolutionSources": [
{
"name": "Federal Reserve",
"url": "https://www.federalreserve.gov"
}
]
},
"outcomeCount": 5,
"outcomes": [
{
"outcomeId": "KXFEDDECISION-26OCT-H0",
"title": "Fed maintains rate",
"rules": "If the Federal Reserve does a Hike of 0bps on October 28, 2026, then the market resolves to Yes. ...",
"volume": "8227362.2",
"state": "STATE_OPEN",
"timeline": {
"openTime": "2025-09-29T14:00:00+00:00",
"closeTime": "2026-10-28T17:59:00+00:00",
"expectedExpirationTime": "2026-10-28T18:05:00+00:00",
"latestExpirationTime": "2027-01-27T18:01:00+00:00",
"settlementTime": "2026-10-28T18:09:57+00:00",
"settlementDelaySeconds": 297
},
"settledOutcome": "SETTLED_OUTCOME_UNSPECIFIED",
"trading": "BUY_AND_SELL",
"contracts": [
{
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"predictedOutcome": "YES",
"bid": "0.79",
"ask": "0.80",
"last": "0.80",
"openInterest": "1555662.72",
"dailyGainValue": "0.00",
"dailyGainPercentage": "0.00",
"probability": "0.80"
},
{
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.N",
"predictedOutcome": "NO",
"bid": "0.20",
"ask": "0.21",
"last": "0.20",
"openInterest": "1555662.72",
"dailyGainValue": "0.00",
"dailyGainPercentage": "0.00",
"probability": "0.20"
}
]
}
]
}The fields to check before trading:
contracts[].symbol is the symbol you quote and trade. Use the .Y contract to bet the outcome happens and the .N contract to bet it doesn't.rules states exactly what makes the outcome resolve to YES. Read it, along with the contract terms at cftcContract.contractTermsUrl, before you trade.trading must be BUY_AND_SELL to open a position. LIQUIDATION_ONLY allows only closing trades, and DISABLED allows none.state moves from STATE_OPEN to STATE_CLOSED at timeline.closeTime, then to STATE_SETTLED once the result is known. After settlement, settledOutcome reports SETTLED_OUTCOME_YES or SETTLED_OUTCOME_NO.probabilityis derived from the YES contract's last price. For a NO contract, it is 1 minus that price.In Python, the response is parsed into typed models, with prices as Decimalvalues and states as enums. This prints each outcome's implied probability and whether it's open for new positions:
from public_api_sdk import EventContractSide, EventTradingMode
details = client.get_event_details(
"KALSHI.KXFEDDECISION-26OCT", include_all_outcomes=False
)
print(f"{details.title} ({details.outcome_count} outcomes)")
for outcome in details.outcomes:
yes = next(
contract
for contract in outcome.contracts
if contract.predicted_outcome == EventContractSide.YES
)
tradable = outcome.trading == EventTradingMode.BUY_AND_SELL
print(
f" {outcome.title}: {yes.probability:.0%} "
f"(bid {yes.bid} / ask {yes.ask}) "
f"symbol={yes.symbol} tradable={tradable}"
)Event details give you a pricing snapshot. For a live quote with bid and ask sizes, pass contract symbols to the quotes endpoint with type EVENTCONTRACT:
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": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"type": "EVENTCONTRACT"
},
{
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.N",
"type": "EVENTCONTRACT"
}
]
}'Each quote includes the bid, ask, and their sizes in contracts:
{
"quotes": [
{
"instrument": {
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"type": "EVENTCONTRACT"
},
"outcome": "SUCCESS",
"last": "0.8000",
"lastTimestamp": "2026-10-13T15:37:07.607717+00:00",
"bid": "0.79",
"bidSize": 265,
"ask": "0.80",
"askSize": 335,
"openInterest": 1555662,
"previousClose": "0.84",
"oneDayChange": {
"change": "-0.0400",
"percentChange": "-4.76"
}
},
{
"instrument": {
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.N",
"type": "EVENTCONTRACT"
},
"outcome": "SUCCESS",
"last": "0.2",
"lastTimestamp": "2026-10-13T15:37:07.607829+00:00",
"bid": "0.20",
"bidSize": 335,
"ask": "0.21",
"askSize": 265,
"openInterest": 1555662,
"previousClose": "0.16",
"oneDayChange": {
"change": "0.04",
"percentChange": "25.00"
}
}
]
}The same call with the Python SDK:
from public_api_sdk import InstrumentType, OrderInstrument
quotes = client.get_quotes(
[
OrderInstrument(
symbol="KALSHI.KXFEDDECISION-26OCT-H0.Y",
type=InstrumentType.EVENTCONTRACT,
),
OrderInstrument(
symbol="KALSHI.KXFEDDECISION-26OCT-H0.N",
type=InstrumentType.EVENTCONTRACT,
),
]
)
for quote in quotes:
print(
f"{quote.instrument.symbol}: "
f"bid {quote.bid} x {quote.bid_size}, ask {quote.ask} x {quote.ask_size}"
)To chart how implied probabilities have moved, request bars for up to 8 contracts of one event in a single call. This endpoint uses charting identifiers, which you build from the symbols you already have:
eventSymbol plus -EVENT, for example KALSHI.KXFEDDECISION-26OCT-EVENT.-EVENTCONTRACT appended, for example KALSHI.KXFEDDECISION-26OCT-H0.Y-EVENTCONTRACT.Pass the period (DAY, WEEK, MONTH, or ALL) in the path and a comma-separated list of contract symbols in the symbols query parameter:
curl --request GET \
--url 'https://api.public.com/userapigateway/historicdata/event-contracts/KALSHI.KXFEDDECISION-26OCT-EVENT/bars/WEEK?symbols=KALSHI.KXFEDDECISION-26OCT-H0.Y-EVENTCONTRACT' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'The response holds one chart per contract (bars trimmed for brevity):
{
"period": "WEEK",
"charts": [
{
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y-EVENTCONTRACT",
"previousClosePrice": "0.8100",
"currentPrice": "0.8000",
"totalGainLoss": "-0.0100",
"totalGainLossPercentage": "-1.23",
"bars": [
{
"timestamp": "2026-10-06T11:37:07-04:00",
"open": "0.8100",
"close": "0.8300",
"high": "0.8300",
"low": "0.8100",
"value": "0.8300",
"volume": "1095",
"gainAmount": "0.0200",
"gainPercentage": "2.47"
},
{
"timestamp": "2026-10-06T12:37:07-04:00",
"open": "0.8300",
"close": "0.8300",
"high": "0.8300",
"low": "0.8200",
"value": "0.8300",
"volume": "299",
"gainAmount": "0.0200",
"gainPercentage": "2.47"
}
]
}
]
}Bar prices are on the same 0.00 to 1.00 scale as quotes, so you can read them directly as implied probabilities. A .N symbol returns NO prices. Charts in one response can start at different times, so align them by timestamp rather than by position. A symbol with no prices in the period is left out of charts.
The same call with the Python SDK:
from public_api_sdk import EventContractBarPeriod
event_symbol = "KALSHI.KXFEDDECISION-26OCT"
contract_symbols = [
"KALSHI.KXFEDDECISION-26OCT-H0.Y",
"KALSHI.KXFEDDECISION-26OCT-H25.Y",
]
response = client.get_event_contract_bars(
event_id=f"{event_symbol}-EVENT",
period=EventContractBarPeriod.WEEK,
symbols=[f"{symbol}-EVENTCONTRACT" for symbol in contract_symbols],
)
for chart in response.charts:
print(
f"{chart.symbol}: {len(chart.bars)} bars, "
f"now {chart.current_price} ({chart.total_gain_loss_percentage}% this week)"
)Once you've picked a contract, run a preflight check to see the estimated cost and fees before you commit. This example buys 10 YES contracts on "Fed maintains rate" with a limit price of $0.80. quantity is the number of contracts:
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": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"type": "EVENTCONTRACT"
},
"orderSide": "BUY",
"orderType": "LIMIT",
"limitPrice": "0.80",
"expiration": {
"timeInForce": "DAY"
},
"quantity": "10"
}'The response shows the order value plus commission and exchange fees:
{
"instrument": {
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"type": "EVENTCONTRACT"
},
"estimatedCommission": "0.1000000",
"regulatoryFees": {
"secFee": "0.00",
"tafFee": "0.00",
"exchangeFee": "0.1000000"
},
"orderValue": "8.0000000",
"estimatedQuantity": "10.00000",
"estimatedCost": "8.20",
"buyingPowerRequirement": "8.2000000",
"marginRequirement": {
"longMaintenanceRequirement": "1.0000",
"longInitialRequirement": "1.0000"
},
"marginImpact": {
"marginUsageImpact": "8.20",
"initialMarginRequirement": "8.20"
}
}Ten contracts at $0.80 is an $8.00 order value, and the fees bring the estimated cost to $8.20. The margin requirement of 1.0000 means event contracts are fully paid: the full cost comes out of your buying power.
The same preflight with the Python SDK:
from decimal import Decimal
from public_api_sdk import (
InstrumentType,
OrderExpirationRequest,
OrderInstrument,
OrderSide,
OrderType,
PreflightRequest,
TimeInForce,
)
contract = OrderInstrument(
symbol="KALSHI.KXFEDDECISION-26OCT-H0.Y",
type=InstrumentType.EVENTCONTRACT,
)
preflight = client.perform_preflight_calculation(
PreflightRequest(
instrument=contract,
order_side=OrderSide.BUY,
order_type=OrderType.LIMIT,
limit_price=Decimal("0.80"),
expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
quantity=Decimal("10"),
)
)
print(f"Order value: ${preflight.order_value:.2f}")
print(f"Commission: ${preflight.estimated_commission:.2f}")
print(f"Estimated cost: ${preflight.estimated_cost:.2f}")Place the order with the same fields plus 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": "5f0c2a7e-3b9d-4e61-9a0f-2c8d7b14e6a3",
"instrument": {
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"type": "EVENTCONTRACT"
},
"orderSide": "BUY",
"orderType": "LIMIT",
"limitPrice": "0.80",
"expiration": {
"timeInForce": "DAY"
},
"quantity": "10"
}'The response confirms the order was submitted:
{
"orderId": "5f0c2a7e-3b9d-4e61-9a0f-2c8d7b14e6a3"
}Order placement is asynchronous. A successful response means the order was received, not that it was filled.
In Python, reuse the contract from the preflight step. The SDK returns a NewOrder handle you can use to track the order:
import uuid
from public_api_sdk import OrderRequest
new_order = client.place_order(
OrderRequest(
order_id=str(uuid.uuid4()),
instrument=contract,
order_side=OrderSide.BUY,
order_type=OrderType.LIMIT,
limit_price=Decimal("0.80"),
expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
quantity=Decimal("10"),
)
)
print(f"Order submitted: {new_order.order_id}")Poll the order to see whether it has filled:
curl --request GET \
--url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/order/5f0c2a7e-3b9d-4e61-9a0f-2c8d7b14e6a3 \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'The response shows the current status and fill details:
{
"orderId": "5f0c2a7e-3b9d-4e61-9a0f-2c8d7b14e6a3",
"instrument": {
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"type": "EVENTCONTRACT"
},
"createdAt": "2026-10-13T15:40:12.481920Z",
"type": "LIMIT",
"side": "BUY",
"status": "FILLED",
"quantity": "10",
"notionalValue": null,
"expiration": {
"timeInForce": "DAY",
"expirationTime": null
},
"limitPrice": "0.80",
"stopPrice": null,
"closedAt": "2026-10-13T15:40:13.027514Z",
"openCloseIndicator": "OPEN",
"filledQuantity": "10",
"averagePrice": "0.80",
"legs": null,
"rejectReason": null
}In Python, wait_for_status polls the order for you until it reaches one of the statuses you pass, or the timeout runs out:
from public_api_sdk import OrderStatus
order = new_order.wait_for_status(
[
OrderStatus.FILLED,
OrderStatus.CANCELLED,
OrderStatus.REJECTED,
OrderStatus.EXPIRED,
],
timeout=60,
)
print(
f"{order.status.value}: {order.filled_quantity} filled "
f"at an average of {order.average_price}"
)Check your portfolio to confirm the new event contract position:
curl --request GET \
--url https://api.public.com/userapigateway/trading/{YOUR_ACCOUNT_ID}/portfolio/v2 \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'The position appears alongside your other holdings with type EVENTCONTRACT (other positions omitted):
{
"accountId": "YOUR_ACCOUNT_ID",
"accountType": "BROKERAGE",
"buyingPower": {
"cashOnlyBuyingPower": "991.80",
"buyingPower": "991.80",
"optionsBuyingPower": "991.80"
},
"positions": [
{
"instrument": {
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"name": "Fed decision in Oct 2026? - Fed maintains rate - Yes",
"type": "EVENTCONTRACT"
},
"quantity": "10",
"currentValue": "8.00",
"percentOfPortfolio": "0.80",
"costBasis": {
"totalCost": "8.20",
"unitCost": "0.82",
"gainValue": "-0.20",
"gainPercentage": "-2.44"
}
}
]
}In Python, filter the positions by instrument type to list only your event contracts:
portfolio = client.get_portfolio()
for position in portfolio.positions:
if position.instrument.type == InstrumentType.EVENTCONTRACT:
print(
f"{position.instrument.symbol}: {position.quantity} contracts, "
f"current value ${position.current_value}"
)You have two ways out of a position:
SELLorder for the same contract symbol, as shown below. This works while the outcome's trading field is BUY_AND_SELL or LIQUIDATION_ONLY.timeline.settlementTime, which already includes the settlementDelaySeconds buffer.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": "9b3e1d42-7c5a-4f08-b6d2-e1a4c9f07d58",
"instrument": {
"symbol": "KALSHI.KXFEDDECISION-26OCT-H0.Y",
"type": "EVENTCONTRACT"
},
"orderSide": "SELL",
"orderType": "LIMIT",
"limitPrice": "0.85",
"expiration": {
"timeInForce": "DAY"
},
"quantity": "10"
}'The same sell order with the Python SDK:
sell_order = client.place_order(
OrderRequest(
order_id=str(uuid.uuid4()),
instrument=contract,
order_side=OrderSide.SELL,
order_type=OrderType.LIMIT,
limit_price=Decimal("0.85"),
expiration=OrderExpirationRequest(time_in_force=TimeInForce.DAY),
quantity=Decimal("10"),
)
)
print(f"Sell order submitted: {sell_order.order_id}")📝 Important Notes
KALSHI.KXFEDDECISION-26OCT), while quotes and orders use a contract symbol ending in .Y or .N. Price history adds -EVENT and -EVENTCONTRACT suffixes to those symbols.halted on the event and trading on the outcome before you place an order. Halted or disabled markets reject new orders.cftcContract. Some people, such as employees of the resolution source, aren't permitted to trade certain contracts.