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)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.
| 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.
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.
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__.
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 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.
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.
- Retries & idempotency — what does and doesn't get retried, why POST/DELETE never retry, recommended patterns for safely retrying writes.
::: kalshi.errors.KalshiError
::: kalshi.errors.KalshiAuthError
::: kalshi.errors.AuthRequiredError
::: kalshi.errors.KalshiNotFoundError
::: kalshi.errors.KalshiValidationError
::: kalshi.errors.KalshiRateLimitError
::: kalshi.errors.KalshiConflictError
::: kalshi.errors.KalshiTimeoutError
::: kalshi.errors.KalshiPoolExhaustedError
::: kalshi.errors.KalshiNetworkError
::: kalshi.errors.KalshiServerError
::: kalshi.errors.KalshiWebSocketError
::: kalshi.errors.KalshiConnectionError
::: kalshi.errors.KalshiSequenceGapError
::: kalshi.errors.KalshiBackpressureError
::: kalshi.errors.KalshiOrderbookUnavailableError
::: kalshi.errors.KalshiSubscriptionError
::: kalshi.fix.KalshiFixError
::: kalshi.fix.FixConnectionError
::: kalshi.fix.FixLogonError
::: kalshi.fix.FixSequenceError
::: kalshi.fix.FixCodecError
::: kalshi.fix.FixDecodeError
::: kalshi.fix.FixRejectError
::: kalshi.fix.FixSessionError