Errors¶
All SDK exceptions inherit from KalshiError. HTTP responses are mapped to
typed exceptions in the transport layer before any resource code sees them,
so you can try/except against the specific failure mode instead of
inspecting status codes.
from kalshi import KalshiClient, KalshiNotFoundError, KalshiRateLimitError
try:
market = client.markets.get("DOES-NOT-EXIST")
except KalshiNotFoundError as e:
print(e.status_code, str(e))
except KalshiRateLimitError as e:
print("backoff hint:", e.retry_after)
Hierarchy¶
KalshiError # base, .status_code: int | None
├── KalshiAuthError # 401 / 403
│ └── AuthRequiredError # preflight on unauth'd client
├── KalshiNotFoundError # 404
├── KalshiValidationError # 400 (carries .details: dict[str, str])
├── KalshiRateLimitError # 429 (carries .retry_after: float | None)
├── KalshiConflictError # 409 (e.g., duplicate client_order_id)
├── KalshiTimeoutError # request timed out; commit-status unknown on POST
├── KalshiPoolExhaustedError # local pool full; request never sent
├── KalshiNetworkError # TCP/TLS/DNS/protocol fault after retries
├── KalshiServerError # 5xx
└── KalshiWebSocketError # base for WS errors
├── KalshiConnectionError # handshake / reconnect failure
├── KalshiSequenceGapError # exposed for custom resync handlers
├── KalshiBackpressureError # queue full with ERROR overflow
└── KalshiSubscriptionError # subscribe / unsubscribe rejected
The KalshiError base carries an optional status_code: int | None.
HTTP-derived exceptions populate it; WebSocket and AuthRequiredError
leave it None.
HTTP status → exception¶
| Status | Exception | Notes |
|---|---|---|
400 |
KalshiValidationError |
.details is populated from body["details"] or body["errors"] when present and dict-shaped. |
401 / 403 |
KalshiAuthError |
Bad signature, expired key, missing scope. |
404 |
KalshiNotFoundError |
Unknown ticker, missing order, etc. |
409 |
KalshiConflictError |
Duplicate client_order_id or other state conflict. |
429 |
KalshiRateLimitError |
.retry_after parsed from the Retry-After header if it's a non-negative finite numeric (HTTP-date form falls back to computed backoff). |
5xx |
KalshiServerError |
All server-side failures. |
| anything else | KalshiError |
Catch-all, with status_code set. |
AuthRequiredError is the one HTTP-shaped exception that fires before the
network — calling a private endpoint on an unauthenticated client raises it
preflight, without sending the request. status_code is None. Since it
subclasses KalshiAuthError, catching the parent covers both.
The mapping is performed by _map_error in
kalshi/_base_client.py.
Validation errors¶
Two distinct things can go wrong with payloads:
- Server-side request validation (
400 Bad Request) — surfaces asKalshiValidationError, withdetails: dict[str, str]populated from the server's response when available. Use it to report field-level problems back to the user. - Pydantic validation on the response — if the server returns a body that
doesn't match the SDK's typed model (a wire-format drift), Pydantic's own
ValidationErrorbubbles up. It is not a subclass ofKalshiError— treat it as a bug report against the SDK's model layer, not as a transient error.
Client-side validation on request bodies (Pydantic models with extra="forbid")
also raises Pydantic's ValidationError directly, before the network. A
misspelled kwarg in a resource method raises TypeError first; phantom keys
passed via request=Model(...) fail at Model(...) construction.
Transport-level wrapping¶
Non-HTTP failures are wrapped to a typed exception with the original as
__cause__:
- Timeouts raise
KalshiTimeoutError. On retryable verbs (GET,HEAD,OPTIONS), the transport retries first and only raises once retries are exhausted. OnPOST/DELETEthe timeout is raised immediately — the server may or may not have processed the request. For order create, query withclient_order_idto determine whether the request committed. - Connection pool exhaustion raises
KalshiPoolExhaustedError. The request never reached the wire, so it's safe to retry regardless of HTTP method. Persistent pool exhaustion means you should raiseKalshiConfig.limits.max_connections. - Network failures (DNS, TLS, TCP RST, HTTP/2 RST_STREAM, half-close)
raise
KalshiNetworkError. On idempotent verbs (GET,HEAD,OPTIONS) the transport retries first; onPOST/DELETE/PUTonlyhttpx.ConnectErroris retried (request never reached the wire — mirrorsKalshiPoolExhaustedError). All other transport faults on non-idempotent verbs surface immediately so the caller can reconcile a possibly-committed request viaclient_order_id. The originalhttpxexception is preserved via__cause__.
Catching everything from the SDK¶
from kalshi import KalshiError
try:
do_things(client)
except KalshiError as e:
log.exception("SDK call failed (status=%s)", e.status_code)
A bare except KalshiError covers every SDK-raised exception except the
Pydantic ValidationError you'd get from a malformed response (that one is a
bug, not a runtime error).
WebSocket errors¶
WebSocket failures are a separate sub-hierarchy under
KalshiWebSocketError:
KalshiConnectionError— raised when the initial connect fails, when the auth handshake is rejected, or whenws_max_retriesis exhausted on a reconnect attempt. Also surfaces fromConnectionManager.send()/recv()if you call them without being connected.KalshiSubscriptionError— server rejected asubscribe/unsubscribe/update_subscriptioncommand. Carries:error_code: int | None— the server's machine-readable code (also accepted positionally for back-compat).channel: str | None— the channel name involved.client_id: int | None— the durable client-side id used for the command.op: Literal["subscribe", "unsubscribe", "update_subscription"] | None— which operation was rejected.
KalshiBackpressureError— raised fromMessageQueue.put()when the queue is full and the overflow strategy isERROR. The receive loop treats this as fatal: it broadcasts sentinels to every active iterator and exits. See WebSocket → Backpressure. Carries:channel: str | None— the channel whose queue overflowed.sid: int | None— server-side subscription id (populated at thebroadcast_errorsite since the queue itself doesn't track sid).client_id: int | None— durable client-side id.maxsize: int | None— the configured queue ceiling at the time of overflow.
KalshiSequenceGapError— exposed for callers wiring their own resync logic on top of the SDK's primitives. The built-in receive loop does not raise this — it recovers from gaps silently (drops the message, clears local orderbook state, waits for the next snapshot). Carries:channel: str | None— the channel where the gap appeared.sid: int | None— server-side subscription id.client_id: int | None— durable client-side id.last_seq: int | None— last in-order sequence successfully consumed.next_seq: int | None— sequence number observed that exposed the gap.
A subscription's iterator continues to yield across reconnects — the SDK
re-issues the subscribe and patches the new server-side sid into the durable
client-side id. You won't see KalshiConnectionError from inside async for;
you'll see it from the connect() context manager if the socket can't be
re-established at all.
FIX errors¶
The FIX subsystem has its own sub-hierarchy under KalshiFixError
(itself a subclass of KalshiError, so except KalshiError still catches it).
Import them from kalshi.fix. FIX is a TCP/TLS protocol with no HTTP status, so
status_code is always None.
FixConnectionError— TCP/TLS connect failed, was refused, or the reconnect attempts were exhausted. Original transport error via__cause__.FixLogonError— the gateway rejected the logon;.reasoncarries theTextfrom the Logout when present (bad signature, CompID, SendingTime skew, a missingResetSeqNumFlag=Y).FixSequenceError— an unrecoverable sequence condition (a backwardsMsgSeqNum, or a forward gap on a non-retransmission session); carries.expected/.received.FixCodecError— a malformed frame (BeginString/BodyLength/CheckSum/tag=value);.rawholds the offending bytes when available.FixDecodeError— a registered inbound message failed schema validation (one off-spec field); carries.raw+.msg_type, original via__cause__. See FIX → Error handling.FixRejectError— the gateway rejected a message we sent (session Reject 35=3 or BusinessMessageReject 35=j); carries the structured reject fields.FixSessionError— a session-level protocol violation or unexpected lifecycle event.
See also¶
- Retries & idempotency — what does and doesn't get retried, why POST/DELETE never retry, recommended patterns for safely retrying writes.
Exception reference¶
kalshi.errors.KalshiError ¶
Bases: Exception
Base exception for all Kalshi SDK errors.
retry_after carries the server's RFC 7231 §7.1.3 Retry-After hint
(parsed to seconds) when present. Populated for 408/429/503/504 and
other retryable 5xx responses; None otherwise. The transport
retry loop reads it via plain attribute access regardless of error
subclass (#322).
kalshi.errors.KalshiAuthError ¶
kalshi.errors.AuthRequiredError ¶
kalshi.errors.KalshiNotFoundError ¶
kalshi.errors.KalshiValidationError ¶
kalshi.errors.KalshiRateLimitError ¶
kalshi.errors.KalshiConflictError ¶
kalshi.errors.KalshiTimeoutError ¶
KalshiTimeoutError(
message: str,
status_code: int | None = None,
retry_after: float | None = None,
)
Bases: KalshiError
Request timed out. The server may or may not have processed it.
On POST endpoints like order create, query the server using
client_order_id to determine whether the request committed.
kalshi.errors.KalshiPoolExhaustedError ¶
KalshiPoolExhaustedError(
message: str,
status_code: int | None = None,
retry_after: float | None = None,
)
Bases: KalshiError
Connection pool exhausted; request never reached the wire.
Safe to retry regardless of HTTP method. Raise
KalshiConfig.limits.max_connections if this fires under normal load.
kalshi.errors.KalshiNetworkError ¶
KalshiNetworkError(
message: str,
status_code: int | None = None,
retry_after: float | None = None,
)
Bases: KalshiError
Transport-level network failure (TCP/TLS/DNS/protocol).
Raised after the retry loop has exhausted attempts on a transient
httpx.TransportError — ConnectError, ReadError,
WriteError, RemoteProtocolError, etc. The original httpx
exception is chained via __cause__.
kalshi.errors.KalshiServerError ¶
kalshi.errors.KalshiWebSocketError ¶
kalshi.errors.KalshiConnectionError ¶
kalshi.errors.KalshiSequenceGapError ¶
kalshi.errors.KalshiBackpressureError ¶
KalshiBackpressureError(
message: str,
*,
channel: str | None = None,
sid: int | None = None,
client_id: int | None = None,
maxsize: int | None = None
)
Bases: KalshiWebSocketError
Message queue overflow with ERROR strategy.
Carries structured context so consumers iterating multiple subscriptions can route the failure by channel/sid/client_id without parsing the error message string.
kalshi.errors.KalshiOrderbookUnavailableError ¶
Bases: KalshiWebSocketError
Local orderbook is empty between teardown and resync snapshot.
Raised by :class:KalshiWebSocket.orderbook when the underlying
delta stream yields an item but the orderbook manager has no book
for the requested ticker. This can happen between a gap-triggered
remove_by_sid and the resync snapshot landing — the iterator
previously yielded an empty :class:Orderbook, which is
indistinguishable from a real market with zero liquidity and could
cause a strategy to place orders against an empty book.
kalshi.errors.KalshiSubscriptionError ¶
kalshi.fix.KalshiFixError ¶
Bases: KalshiError
Base exception for all Kalshi FIX errors.
FIX is a TCP/TLS protocol with no HTTP status, so status_code is always
None (the field exists on :class:kalshi.errors.KalshiError for the
REST transport and is kept for cross-surface except KalshiError use).
kalshi.fix.FixConnectionError ¶
Bases: KalshiFixError
TCP/TLS connection failed, was refused, or max reconnect attempts exceeded.
The original transport exception (OSError / ssl.SSLError /
asyncio.TimeoutError) is chained via __cause__.
kalshi.fix.FixLogonError ¶
Bases: KalshiFixError
Logon (35=A) was rejected by the acceptor.
The acceptor answers a failed Logon with a Logout (35=5) carrying a
human-readable Text (58); that text is surfaced in reason when
present. Common causes: bad RawData signature, SendingTime outside the 30s
skew window (SessionRejectReason=10), unknown CompID, or a missing
ResetSeqNumFlag=Y on a non-retransmission session.
kalshi.fix.FixSequenceError ¶
Bases: KalshiFixError
An unrecoverable sequence-number condition.
Two cases, both fatal to the session:
- The acceptor's
MsgSeqNumis lower than expected (per spec the connection is terminated — a lower number means the peer's view of the session is corrupt). - A forward gap was detected on a session that does not support
retransmission (
KalshiNR/KalshiDC), so it cannot be recovered viaResendRequest.
kalshi.fix.FixCodecError ¶
Bases: KalshiFixError
A FIX frame could not be encoded or decoded.
Raised on malformed framing — missing BeginString / BodyLength /
CheckSum, a BodyLength that disagrees with the bytes on the wire, a
CheckSum mismatch, or a field that is not tag=value. raw carries
the offending bytes (truncated) for debugging when available.
kalshi.fix.FixDecodeError ¶
Bases: KalshiFixError
A registered inbound application message failed schema validation.
Distinguishes a malformed known message (a real message with an off-spec
field — e.g. a bad DollarDecimal / FixedPointCount) from an
unregistered message type. :func:~kalshi.fix.messages.decode_app_message
collapses both to None;
:func:~kalshi.fix.messages.decode_app_message_strict raises this for the
former so the failure is observable (see FixSession's on_decode_error
hook). raw carries the offending message and msg_type its MsgType
(both always present); the underlying validation error chains via __cause__.
kalshi.fix.FixRejectError ¶
FixRejectError(
message: str,
*,
ref_seq_num: int | None = None,
ref_tag_id: int | None = None,
ref_msg_type: str | None = None,
reject_reason: int | None = None,
text: str | None = None
)
Bases: KalshiFixError
The acceptor rejected a message we sent.
Raised for an inbound session-level Reject (35=3) or BusinessMessageReject
(35=j). Carries the structured reject fields so callers can route on them
without parsing Text.
kalshi.fix.FixSessionError ¶
Bases: KalshiFixError
A session-level protocol violation or unexpected lifecycle event.
Covers an unexpected Logout, a liveness failure (no Heartbeat/TestRequest response within the interval), or an inbound message that breaks the session state machine.