Skip to content

Kalshi Errors

github-actions[bot] edited this page Oct 19, 2026 · 5 revisions

Kalshi Errors

Kalshi throws KalshiError for invalid configuration, local order pre-flight validation, and vendor responses — see errors/Base.ts and errors/KalshiErrorCodes.ts.

import { KalshiError, KalshiErrorCodes } from '@tundraconnect/kalshi/errors';

const error = new KalshiError('NOT_FOUND');
console.log(error.message);
console.log(KalshiErrorCodes.NOT_FOUND);

Codes

Kalshi returns a machine-readable error envelope ({"error": {"code", "message", "details"?}}, occasionally just {"error": "..."}) — most of these codes are keyed off the HTTP status, with detail carrying the vendor's error.message (or the bare string) when present.

Code Meaning
CONFIG_MISSING_PRIVATE_KEY A /portfolio/* method was called but auth/auth.accessKey/auth.privateKeyPem was never configured.
CONFIG_INVALID_BATCH_SIZE cancelAllOrders({ batchSize }) was given a non-positive or fractional batch size.
SIGNER_FAILED The injected auth.signer rejected or returned no signature.
CONFIG_INVALID_PRIVATE_KEY auth.privateKeyPem isn't a structurally valid PKCS#8 PEM (missing BEGIN/END markers or invalid base64). A structurally-valid-but-not-actually-RSA key instead fails later, on the first authenticated call — see the API docs' "Configuration" section for why that can't be checked synchronously.
ORDER_REJECTED submitOrder()/amendOrder(): a locally-refused (price/count out of range) or venue-rejected order. submitOrders(): a locally-refused order (checked before any request is sent) or a whole-batch rejection — a single row's rejection surfaces as rejected: true on that result instead, never a throw. detail carries the reason either way. A reused clientOrderId (HTTP 409) also sets duplicate: true — that order was not placed again.
INVALID_REQUEST A 400/422 response outside the order-submission paths above — detail carries the vendor's error text.
NOT_FOUND A 404 response (getMarket()/getEvent()/getSeries() map this to null instead of throwing).
AUTH_FAILED A 401 response — bad/expired credentials, or a signature that doesn't verify (see KalshiAuth.ts's doc comment for the quirks that cause this silently: mismatched timestamp, a query string left in the signed path, seconds instead of milliseconds).
RATE_LIMITED A 429 response.
RESPONSE_ERROR A success response's body failed schema validation.
TIMEOUT No answer from Kalshi within the timeout (seconds); timeoutSeconds says which deadline passed.
NETWORK_ERROR The request failed before any response (DNS, TLS or connection failure); the original error is the cause.
SERVICE_UNAVAILABLE A 5xx response.
UNKNOWN_ERROR An unmapped status was returned, or an unknown code was supplied.

TIMEOUT, NETWORK_ERROR, RATE_LIMITED and SERVICE_UNAVAILABLE are transient: they mean "no answer yet — try again later". err.transient is true for exactly these and false for every other code (a definite refusal, or a misconfiguration that retrying will not fix). The set is also exported as KALSHI_TRANSIENT_CODES from @tundraconnect/kalshi/errors.

Context Meaning
timeoutSeconds Set on TIMEOUT: the deadline, in seconds, that passed with no answer (the call's own timeout, else the client's timeout option).

The resolved code is also available as a public, readonly error.code property — branch on failure mode without matching against .message:

if (error instanceof KalshiError && error.code === 'ORDER_REJECTED') {
  // ...
}

Use getContextValue() to read diagnostic metadata. This connect never places auth.privateKeyPem or the KALSHI-ACCESS-SIGNATURE header value into an error's context or message — _isSensitiveHeader redacts the signature header the same way every other connect's vendor-specific auth headers are redacted, so error.message/error.toJSON() are always safe to log.

try {
  await client.submitOrder({
    ticker,
    side: 'BUY',
    price: 0.42,
    count: 3,
    orderType: 'GTC',
  });
} catch (error) {
  if (error instanceof KalshiError) {
    console.log(error.code, error.getContextValue('detail'));
  }
}

Socket codes

KalshiSocket reports these through error and invalid events rather than by throwing — only its constructor (SOCKET_MISSING_CREDENTIALS, CONFIG_INVALID_PRIVATE_KEY) and connect() after close() (SOCKET_CLOSED) throw. A fatal: true error event means the socket has closed for good.

Code Meaning
SOCKET_MISSING_CREDENTIALS auth lacks accessKey, or both privateKeyPem and signer.
SOCKET_VENUE_ERROR A Kalshi error frame; venueCode is Kalshi's numeric code (e.g. 8 unknown channel, 27 rate limit).
SOCKET_INVALID_MESSAGE A known message type failed schema validation (carried by an invalid event with the data).
SOCKET_RECONNECT_EXHAUSTED reconnect.maxAttempts consecutive attempts failed, or reconnect: false and the connection ended.
SOCKET_BUFFER_OVERFLOW More than maxBufferedEvents events went unread; the socket closed rather than drop any.
SOCKET_UNSUPPORTED_RUNTIME No usable global WebSocket; pass a webSocket factory that can send the signed headers.
SOCKET_HANDLER_ERROR An unexpected exception while signing, handling a frame or opening a session; fatal only while signing.
SOCKET_IN_USE A second iterator was requested while one is active.
SOCKET_CLOSED connect() was called on a closed socket.

Backing off after a 429

A rate-limited request throws RATE_LIMITED. Two context values tell you what to do next:

Context Meaning
retryAfterSeconds Seconds the vendor asked you to wait, parsed by RESTler from Retry-After (delta seconds or an HTTP-date), X-RateLimit-Reset-After, RateLimit-Reset, or an epoch-seconds X-RateLimit-Reset. undefined when the response carried none — never a guess.
retried Set only when maxRetryWait is configured: true if RESTler already waited once and was throttled again, false if it did not wait (the hint exceeded the cap, or there was no hint to wait on).

Nothing is retried by default. Opt in with two client options (seconds, each 0–120):

  • maxRetryWait — when a 429 carries a hint no longer than this, RESTler waits that long and retries once; otherwise it throws RATE_LIMITED immediately. 0 disables the retry.
  • defaultRetryWait — the wait used when a 429 carries no hint. Without it a hintless 429 is never retried: RESTler does not invent a delay. Only consulted when maxRetryWait is set, and still capped by it.

← Back to Kalshi

Clone this wiki locally