Repository navigation
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);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'));
}
}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. |
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 throwsRATE_LIMITEDimmediately.0disables 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 whenmaxRetryWaitis set, and still capped by it.
- Algolia
- AzureBlob
- Binance
- CloudflareDNS
- CloudflareEmail
- CloudflareSaaS
- CloudflareTurnstile
- CoinGecko
- Discord
- DodoPayments
- GCS
- GoogleAnalytics
- GoogleCertificateManager
- GoogleWebRisk
- Kalshi
- ntfy
- OpenExchange
- OpenWeatherMap
- PayPal
- Polymarket
- Pyth
- Razorpay
- Resend
- S3
- SendGrid
- Sentry
- Slack
- Stripe
- Telegram
- Twilio
- UpstashRedis
- URLhaus