Placing an order through the Kalshi API is the easy part. The harder part is knowing afterwards what actually happened: how many contracts filled, at what price, what the fee was, what you hold now, and what each market paid when it settled. Kalshi's portfolio endpoints answer each of those questions. This guide walks through the four a trading bot reads most (balance, positions, fills and settlements), the units each field uses, and a worked example that reconciles one trade from order to payout.
Short answer
-
GET /portfolio/balance: cash you can trade with and the value of your open positions.balanceandportfolio_valueare in cents;balance_dollarsis a dollar string. -
GET /portfolio/positions: what you hold in each market. Positiveposition_fpmeans YES contracts, negative means NO. It returns unsettled positions by default. -
GET /portfolio/fills: every match on your orders, with price, count, fee and whether you were the taker. -
GET /portfolio/settlements: what each finished market paid you.revenueis in cents, and a winning contract pays 100 cents. - All four need a signed request with your API key. Older records move to the
/historicalendpoints.
Before you start: base URL and signing
Kalshi's API reference lists https://external-api.kalshi.com/trade-api/v2 as the production Trade API server, with https://api.elections.kalshi.com/trade-api/v2 as a shared server it still supports. The demo environment has matching servers on kalshi.co. Market data can be read without an account, but every /portfolio endpoint is private. Each request carries three headers: your API key ID, a timestamp in milliseconds, and a signature made with your private key. We cover that setup in the Kalshi API Python tutorial and the demo environment guide. Practise these calls in demo first, where a mistake costs nothing.
One more thing to know up front: Kalshi has been moving its responses to fixed-point fields. Counts arrive as strings ending in _fp (for example count_fp), and dollar amounts arrive as strings ending in _dollars. A few older fields are still integers in cents. Parse each field by its documented type rather than guessing, and keep your money math in integer cents or a decimal type, never floating point.
1. Balance: what you can trade with
GET /portfolio/balance returns:
| Field | Unit | Meaning |
|---|---|---|
balance |
cents (integer) | Available balance |
balance_dollars |
dollar string | The same balance, as fixed-point dollars |
portfolio_value |
cents (integer) | Value of your positions |
updated_ts |
Unix timestamp | When the balance last changed |
Kalshi notes that both values include every exchange index unless you pass exchange_index, and that a subaccount parameter reads a subaccount instead of the primary account. The endpoint also accepts API keys limited to the read::portfolio_balance scope, which is handy for a dashboard that should never be able to trade.
A bot uses the balance for one thing above all: deciding whether it can afford the next order. If the cost of an order plus its fee is more than balance, the order will be rejected, so check first and skip cleanly.
2. Positions: what you hold right now
GET /portfolio/positions returns two lists, market_positions and event_positions, plus a cursor for paging. The per-market fields a bot reads most:
| Field | Meaning |
|---|---|
ticker |
The market, for example a KXBTC15M-... window |
position_fp |
Contracts held. Positive is YES, negative is NO |
market_exposure_dollars |
Cost of the position |
total_traded_dollars |
Total spent in this market |
realized_pnl_dollars |
Profit or loss already locked in |
fees_paid_dollars |
Fees on your fills in this market |
By default the endpoint returns only unsettled positions. Pass settlement_status=settled to page through settled ones still in the live data set, or settlement_status=all for both. You can filter by ticker or by a single event_ticker. Page size defaults to 100 and goes up to 1,000.
The sign convention matters. A bot that reads position_fp = "-5" holds five NO contracts, not "minus five" of anything. Treating the sign as a direction keeps you from doubling a position you already have, which is the most common bookkeeping bug in home-made bots.
3. Fills: every match on your orders
An order and a fill are different things. One order for 10 contracts can fill in three pieces at different moments, or partly fill and leave the rest resting. GET /portfolio/fills lists each match. Useful fields:
-
fill_idandorder_id: which fill, and which of your orders it belongs to. Filter byorder_idto see exactly how one order filled. -
ticker,count_fp,yes_price_dollarsandno_price_dollars: where, how many, and at what price. -
outcome_side: the side the fill positioned you for. Kalshi's docs say buy-yes and sell-no produceyes; buy-no and sell-yes produceno.book_sidecarries the same information asbidorask. -
is_taker: true if your order took liquidity from the book. Taker and maker fills can carry different fees. -
fee_cost: the fee on this fill, in fixed-point dollars. -
created_time: when it filled.
You can narrow the list with min_ts and max_ts (Unix timestamps). Fills from before Kalshi's historical cutoff are only available through GET /historical/fills, so a reconciliation script that looks back far enough has to read both. The older side, action, trade_id and market_ticker fields are still present for compatibility; Kalshi's order direction guide explains how to move to the newer fields.
4. Settlements: what each market paid
When a market resolves, GET /portfolio/settlements records what you held and what you were paid:
-
market_result:yes,noorscalar. -
yes_count_fpandno_count_fp: contracts you held on each side at settlement. -
yes_total_cost_dollarsandno_total_cost_dollars: your cost basis on each side. -
revenue: what the settlement paid, in cents. Winning contracts pay 100 cents each. -
fee_cost: total fees, in fixed-point dollars. -
settled_time: when payouts were processed.
Note the mixed units: revenue is an integer in cents while costs and fees are dollar strings. Convert everything to one unit before you subtract. This endpoint returns only records still in the live data set; archived ones are read through GET /historical/positions, which has different fields.
Worked example: reconciling one trade
Say a bot buys 10 YES contracts at 40¢ in a Bitcoin 15-minute market and the whole order fills as a taker.
The fill. The fill shows count_fp 10, yes_price_dollars 0.40, is_taker true and outcome_side yes. Kalshi's general taker fee formula is 0.07 × contracts × price × (1 − price), rounded up: 0.07 × 10 × 0.40 × 0.60 = $0.168. The fill's fee_cost should be about $0.1680. Check the actual fee schedule for each series, because fees can differ by market and change over time.
The cost. 10 × $0.40 = $4.00 for the contracts, plus $0.168 in fees: $4.168 in total. Your balance drops by about that much, and GET /portfolio/positions now shows position_fp 10 for that ticker.
If the market settles YES. The settlement record shows market_result yes, yes_count_fp 10 and revenue 1000 (cents), which is $10.00. Net result: $10.00 − $4.00 − $0.168 = $5.832.
If it settles NO. revenue is 0. Net result: −$4.168.
The breakeven. To come out even over many trades like this one, the YES side has to win $4.168 ÷ $10.00 = 41.68% of the time, not 40%. The fee moves the bar up by about 1.7 points. That is why the fee field on every fill matters: a strategy that looks even before fees can lose after them. Our fees and breakeven guide works this through at other prices.
A note on rounding
Kalshi's fee rounding page explains that fees are computed to six decimal places, and that balances are aligned to $0.0001 for direct members and $0.01 for members who trade through a broker (FCM). When a fill would leave your balance between those steps, a small rounding fee brings it back, and overpaid rounding is refunded later in the same order. So a reconciliation that matches to the cent can still be a hundredth of a cent off. Allow for that, rather than treating it as a bug.
A simple reconciliation loop
- When the bot places an order, store the
order_idit gets back. - Read
GET /portfolio/fills?order_id=...to see what actually filled, at what price and fee. Do not assume the full count filled. - Compare
GET /portfolio/positionsfor that ticker with the sum of your fills. If they disagree, stop placing new orders in that market until you know why. - After the window closes, read
GET /portfolio/settlements?ticker=...and record revenue, cost and fees for that trade. - Once a day, compare
balancewith what your records say it should be.
Every list endpoint pages with cursor. Keep requesting until the cursor comes back empty, or you will miss records on a busy day. Portfolio calls count against the same rate limits as everything else; see our rate limits and WebSocket guide for ways to cut polling, such as listening for fills on a WebSocket instead of asking every second.
Common mistakes
-
Mixing cents and dollars.
revenueandbalanceare cents; most other money fields are dollar strings. - Using floats for money. Fixed-point strings exist so you do not lose fractions of a cent. Parse them into a decimal type.
- Reading only unsettled positions. A position disappears from the default view once its market settles. Look it up in settlements instead.
- Ignoring partial fills. An order can fill 3 of 10 contracts and rest the other 7 until the market closes.
- Forgetting the historical endpoints. Old fills and positions move out of the live endpoints after Kalshi's cutoff.
Where DeskBot fits
DeskBot uses proprietary trading logic with customer-selected settings. Its dashboard shows supported market views and app activity. See the current product and support pages for the features and requirements of your release.
More on the 15-minute markets: close times and settlement, Bitcoin 15-minute results data, Kalshi trading bot comparison, is a Kalshi trading bot worth it? and Windows vs Mac setup.
How do I get my Kalshi balance through the API?
Send a signed request to GET /portfolio/balance. balance is your available balance in cents, balance_dollars is the same value as a dollar string, and portfolio_value is the value of your positions in cents.
What does a negative position mean in the Kalshi API?
In GET /portfolio/positions, a negative position_fp means you hold NO contracts and a positive one means YES contracts.
Why is my settled position missing from /portfolio/positions?
The endpoint returns unsettled positions by default. Pass settlement_status=settled or all, or read GET /portfolio/settlements. Archived records are in GET /historical/positions.
What is the difference between an order and a fill on Kalshi?
An order is your instruction to trade. A fill is a match against it. One order can produce several fills, or partly fill and rest the remainder.
Is settlement revenue in dollars or cents?
Cents. A winning contract pays 100 cents, so 10 winning contracts show revenue 1000. Fees and cost basis on the same record are dollar strings.
Is DeskBot affiliated with Kalshi?
No. DeskBot is independent software and is not affiliated with, endorsed by or sponsored by Kalshi.
Risk disclaimer: Trading involves risk of loss. Only trade money you can afford to lose. DeskBot does not guarantee profits, returns, win rates or income. Endpoint names and fields are as published in Kalshi's API documentation on 27 October 2026 and can change; check Kalshi's documentation before relying on them. The worked example is illustrative arithmetic, not a result. Nothing on this page is financial advice. DeskBot is not affiliated with Kalshi.
Current product information: Kalshi · Robinhood · Setup and support. Check the relevant product page for current downloads, pricing, renewal and cancellation terms. Desktop and mobile products have separate availability and offers.
Educational content, not financial advice. Trading can lose money. DeskBot does not guarantee profits, returns, win rates or income and is not affiliated with or endorsed by the named exchanges or brokers.
