Orders¶
Place, amend, cancel, and inspect orders. The largest single resource in the SDK and the surface you'll spend the most time with as a trader.
Every method here requires auth.
Quick reference¶
Order writes go through the V2 event-market family (/portfolio/events/orders/*).
Reads stay on /portfolio/orders/*.
| Method | Endpoint | Retry |
|---|---|---|
create_v2(*, request) |
POST /portfolio/events/orders |
never — see Retries & idempotency |
batch_create_v2(*, request) |
POST /portfolio/events/orders/batched |
never |
cancel_v2(order_id, *, subaccount, exchange_index, market_ticker) |
DELETE /portfolio/events/orders/{order_id} |
never |
batch_cancel_v2(*, request) |
DELETE /portfolio/events/orders/batched |
never |
amend_v2(order_id, *, request, subaccount) |
POST /portfolio/events/orders/{order_id}/amend |
never |
decrease_v2(order_id, *, request, subaccount) |
POST /portfolio/events/orders/{order_id}/decrease |
never |
get(order_id) |
GET /portfolio/orders/{order_id} |
yes (GET) |
list(...) / list_all(...) |
GET /portfolio/orders |
yes |
~~fills(...) / fills_all(...)~~ |
moved to PortfolioResource in v3.0.0 — see Portfolio › Fills; the old methods remain as deprecated aliases until removal in a future release. |
|
queue_positions(*, market_tickers, event_ticker) |
GET /portfolio/orders/queue_positions |
yes |
queue_position(order_id) |
GET /portfolio/orders/{order_id}/queue_position |
yes |
V2 uses BookSideLiteral ("bid" / "ask"), a single price field
(not paired yes/no), and requires client_order_id as a server-side
idempotency key. The surface is model-only — every write method takes a
pre-built request model rather than kwargs.
Place an order¶
import uuid
from decimal import Decimal
from kalshi import KalshiClient, CreateOrderV2Request
with KalshiClient.from_env() as client:
resp = client.orders.create_v2(request=CreateOrderV2Request(
ticker="KXPRES-24-DJT",
client_order_id=str(uuid.uuid4()), # required — idempotency key
side="bid", # BookSideLiteral — "bid" / "ask"
count=Decimal("10"), # Decimal — never float
price=Decimal("0.65"), # Decimal dollars — never float
time_in_force="good_till_canceled", # TimeInForceLiteral
self_trade_prevention_type="taker_at_cross",
# subaccount=1, # route to subaccount 1
# exchange_index=0,
))
print(resp.order_id, resp.remaining_count, resp.fill_count)
The write surface is model-only: there is no kwarg overload. Always build a
CreateOrderV2Request. side is the book side "bid" / "ask" (not
"yes" / "no"), count and price are Decimal, and client_order_id
is required.
Time-in-force¶
| Value | Behavior |
|---|---|
"fill_or_kill" |
Fill instantly in full or cancel. |
"good_till_canceled" |
Rest until canceled or filled. Server default if you omit. |
"immediate_or_cancel" |
Fill whatever you can immediately, cancel the rest. |
client_order_id for safe retries¶
POST /portfolio/events/orders is never automatically retried. Set a
fresh client_order_id per call so you can safely retry from your app layer
without double-filling. Reusing a client_order_id for a second call returns
the original order rather than placing a new one — generate a fresh UUID4
per request. See Retries & idempotency.
Self-trade prevention¶
SelfTradePreventionTypeLiteral values:
"taker_at_cross"— your taker order is canceled if it would cross your own resting order."maker"— your resting maker order is canceled if a taker side of yours crosses it.
Batch create¶
import uuid
from decimal import Decimal
from kalshi import BatchCreateOrdersV2Request, CreateOrderV2Request
result = client.orders.batch_create_v2(request=BatchCreateOrdersV2Request(
orders=[
CreateOrderV2Request(
ticker="X-A", client_order_id=str(uuid.uuid4()),
side="bid", count=Decimal("10"), price=Decimal("0.60"),
time_in_force="good_till_canceled", self_trade_prevention_type="taker_at_cross",
),
CreateOrderV2Request(
ticker="X-B", client_order_id=str(uuid.uuid4()),
side="ask", count=Decimal("10"), price=Decimal("0.42"),
time_in_force="good_till_canceled", self_trade_prevention_type="taker_at_cross",
),
],
))
for entry in result.orders:
if entry.error is not None:
print("failed:", entry.error)
else:
print(entry.order_id, entry.fill_count)
Each child CreateOrderV2Request has extra="forbid", so a typo in any leg
fails at construction before the round trip.
Cancel¶
result = client.orders.cancel_v2("ord_abc")
print(result.reduced_by, result.ts_ms) # FixedPointCount, int
cancel_v2 returns a CancelOrderV2Response (with the count actually
canceled). A 204 No Content from the server is treated as a protocol
violation and raises KalshiError.
For batch cancels, build a BatchCancelOrdersV2Request. Each entry can carry
its own subaccount / market_ticker:
from kalshi import BatchCancelOrdersV2Request, BatchCancelOrdersV2RequestOrder
result = client.orders.batch_cancel_v2(request=BatchCancelOrdersV2Request(
orders=[
BatchCancelOrdersV2RequestOrder(order_id="ord_abc", subaccount=0),
BatchCancelOrdersV2RequestOrder(order_id="ord_def", subaccount=1),
],
))
for entry in result.orders:
if entry.error is not None:
print("failed:", entry.order_id, entry.error)
else:
print(entry.order_id, "canceled")
Cancel is not server-idempotent
cancel_v2(...) (and batch_cancel_v2(...)) propagate a 404 as
KalshiNotFoundError when the order is already canceled, fully filled, or
never existed. The SDK does not automatically swallow that — the
caller owns safe-retry idempotency. Treat 404 as "already canceled" only
if you can rule out a typo'd order_id:
Amend¶
from decimal import Decimal
from kalshi import AmendOrderV2Request
resp = client.orders.amend_v2(
"ord_abc",
subaccount=0, # query param
request=AmendOrderV2Request(
ticker="KXPRES-24-DJT", # same shape as create
side="bid", # BookSideLiteral
price=Decimal("0.66"),
count=Decimal("12"), # total/max fillable count
exchange_index=0, # body field
),
)
print(resp.old_order.order_id, resp.order.order_id)
AmendOrderV2Response carries both the previous and the new order. ticker
and side are part of the API's amend payload.
Decrease¶
Reduce the size of a resting order without canceling. Exactly one of
reduce_by or reduce_to:
from decimal import Decimal
from kalshi import DecreaseOrderV2Request
resp = client.orders.decrease_v2(
"ord_abc",
subaccount=0,
request=DecreaseOrderV2Request(reduce_by=Decimal("3")), # decrease size by 3
)
resp = client.orders.decrease_v2(
"ord_abc",
request=DecreaseOrderV2Request(reduce_to=Decimal("5")), # decrease size to 5
)
DecreaseOrderV2Request enforces XOR on reduce_by / reduce_to at
construction — passing both or neither raises ValidationError.
List orders¶
for order in client.orders.list_all(status="resting"):
print(order.order_id, order.ticker, order.remaining_count)
status accepts an OrderStatusLiteral: "resting", "canceled",
"executed". min_ts / max_ts (Unix seconds) bound by created time.
Fills (fills / fills_all) moved to PortfolioResource in v3.0.0 — see
Portfolio › Fills. client.orders.fills(...) /
client.orders.fills_all(...) still work in v3.0.0 but emit a
DeprecationWarning and will be removed in a future release.
Queue position¶
positions = client.orders.queue_positions(market_tickers=["KXPRES-24-DJT"])
for p in positions:
print(p.order_id, p.queue_position)
q = client.orders.queue_position("ord_abc") # returns Decimal
queue_position(order_id) returns a bare Decimal, not a model — that's the
shape Kalshi's endpoint emits.
Batch error handling¶
Per-entry error handling is built into BatchCreateOrdersV2ResponseEntry
and BatchCancelOrdersV2ResponseEntry — successful entries carry the
order/fill data, failed entries carry an error dict with the rest of the
fields nulled (create) or zeroed (cancel).
subaccount / exchange_index routing¶
This trips people up. The V2 spec routes subaccount and exchange_index
to different places depending on the endpoint:
| Endpoint | subaccount |
exchange_index |
|---|---|---|
cancel_v2 |
query | query |
amend_v2 |
query | body (on AmendOrderV2Request) |
decrease_v2 |
query | body (on DecreaseOrderV2Request) |
create_v2 / batch_*_v2 |
n/a — body on request model | body |
This is faithful to the OpenAPI spec — amend_v2/decrease_v2 only
declare SubaccountQueryDefaultPrimary in their parameters list,
while cancel_v2 declares both that and ExchangeIndexQuery. Don't try
to "normalize" it; the SDK matches the spec.
Reference¶
kalshi.resources.orders.OrdersResource ¶
Bases: SyncResource
Sync orders API.
fills ¶
fills(
*,
ticker: str | None = None,
order_id: str | None = None,
min_ts: int | None = None,
max_ts: int | None = None,
limit: int | None = None,
cursor: str | None = None,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None
) -> Page[Fill]
List trade fills.
fills_all ¶
fills_all(
*,
ticker: str | None = None,
order_id: str | None = None,
min_ts: int | None = None,
max_ts: int | None = None,
limit: int | None = None,
subaccount: int | None = None,
max_pages: int | None = None,
extra_headers: dict[str, str] | None = None
) -> Iterator[Fill]
Auto-paginate trade fills.
cancel_v2 ¶
cancel_v2(
order_id: str,
*,
subaccount: int | None = None,
exchange_index: int | None = None,
market_ticker: str | None = None,
extra_headers: dict[str, str] | None = None
) -> CancelOrderV2Response
Cancel an event-market order (V2).
market_ticker (spec v3.22.0) is required when exchange_index
is -1 (auto-route by ticker); omit it otherwise.
amend_v2 ¶
amend_v2(
order_id: str,
*,
request: AmendOrderV2Request,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None
) -> AmendOrderV2Response
Amend an event-market order (V2).
Per OpenAPI spec v3.18.0, this endpoint's subaccount is a
query parameter while exchange_index lives in the request
body (on AmendOrderV2Request). The asymmetry mirrors the
spec exactly — do not move exchange_index into params.
decrease_v2 ¶
decrease_v2(
order_id: str,
*,
request: DecreaseOrderV2Request,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None
) -> DecreaseOrderV2Response
Decrease an event-market order (V2).
Same spec-driven asymmetry as :meth:amend_v2: subaccount is
a query param; exchange_index lives on the body model. Note
also that cancel_v2 carries both as query params (no body) —
that endpoint declares ExchangeIndexQuery in its parameters
list, while amend/decrease declare only SubaccountQueryDefaultPrimary.
kalshi.resources.orders.AsyncOrdersResource ¶
Bases: AsyncResource
Async orders API.
list_all ¶
list_all(
*,
ticker: str | None = None,
event_ticker: list[str] | str | None = None,
status: OrderStatusLiteral | None = None,
min_ts: int | None = None,
max_ts: int | None = None,
limit: int | None = None,
subaccount: int | None = None,
max_pages: int | None = None,
extra_headers: dict[str, str] | None = None
) -> AsyncIterator[Order]
Returns an async iterator — use async for.
fills
async
¶
fills(
*,
ticker: str | None = None,
order_id: str | None = None,
min_ts: int | None = None,
max_ts: int | None = None,
limit: int | None = None,
cursor: str | None = None,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None
) -> Page[Fill]
List trade fills (async).
fills_all ¶
fills_all(
*,
ticker: str | None = None,
order_id: str | None = None,
min_ts: int | None = None,
max_ts: int | None = None,
limit: int | None = None,
subaccount: int | None = None,
max_pages: int | None = None,
extra_headers: dict[str, str] | None = None
) -> AsyncIterator[Fill]
Auto-paginate trade fills (async). Use async for.
cancel_v2
async
¶
cancel_v2(
order_id: str,
*,
subaccount: int | None = None,
exchange_index: int | None = None,
market_ticker: str | None = None,
extra_headers: dict[str, str] | None = None
) -> CancelOrderV2Response
Cancel an event-market order (V2).
market_ticker (spec v3.22.0) is required when exchange_index
is -1 (auto-route by ticker); omit it otherwise.
amend_v2
async
¶
amend_v2(
order_id: str,
*,
request: AmendOrderV2Request,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None
) -> AmendOrderV2Response
Amend an event-market order (V2).
Per OpenAPI spec v3.18.0, this endpoint's subaccount is a
query parameter while exchange_index lives in the request
body (on AmendOrderV2Request). The asymmetry mirrors the
spec exactly — do not move exchange_index into params.
decrease_v2
async
¶
decrease_v2(
order_id: str,
*,
request: DecreaseOrderV2Request,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None
) -> DecreaseOrderV2Response
Decrease an event-market order (V2).
Same spec-driven asymmetry as :meth:amend_v2: subaccount is
a query param; exchange_index lives on the body model. Note
also that cancel_v2 carries both as query params (no body) —
that endpoint declares ExchangeIndexQuery in its parameters
list, while amend/decrease declare only SubaccountQueryDefaultPrimary.