kairos_ck_... triples) for users who need to call the platform from scripts, bots, or other non-browser clients. This page covers what a credential looks like, what each scope unlocks, which procedures are reachable, and how the auth errors are distinguished — read it before your first authenticated call.
One key authenticates to both:
- The order execution service at
https://execution.kairos.trade/(submit / cancel / query orders). - The RPC server at
https://rpc.kairos.trade/api/rpc/for a curated subset of read and trading procedures.
Credential format
A credential is a three-tuple handed out once at creation time. Lose it and you can’t recover it — create a new one.
All three must be included on every authenticated request as HTTP headers:
Gotcha: send only the API-key headers. Authenticate withX-Client-Id/X-Api-Key/X-Api-Secretalone. When any of the threeX-Api-*headers is present the server authenticates on them exclusively and will not fall back to a cookie orAuthorizationJWT, so a malformed key header fails authentication outright. If your HTTP client injects anAuthorizationheader automatically, disable it for these requests.
CSRF is not required. CSRF protects against browsers auto-attaching
cookies to cross-site requests. API keys live in explicit X-Api-* headers
that browsers never add automatically, so the RPC server skips the CSRF check
when a request authenticates via API key.
Quick start — Python httpx
trade:execute scope.
Gotcha: the two services use different field casing. Order execution
request bodies are snake_case, not the camelCase used by RPC payloads —
exchange_id, market_id, token_id, kind (not orderType), quantity
(not size). A field mismatch (e.g. sending marketId instead of
market_id) fails with a plain deserialization 400, not a helpful
validation error — double check the payload shape if you get an unexpected
400.Scopes
Every credential carries ascopes list. RPC matching accepts exact scopes
and *; additionally, position:read and trade:read satisfy an RPC read
requirement, while trade:execute satisfies RPC trade. These relationships
are one-way. Order execution requires the exact scope and does not treat *
as a wildcard.
Only three scopes are actually issuable: position:read, trade:read, and trade:execute. The bare read / trade names below are the requirements procedures declare, not values you can be granted; * is a legacy wildcard the server still honours but the issuing flow does not hand out.
Because the two services grew their scope checks independently, the naming isn’t uniform — the RPC server uses bare
read/trade for most of its own procedures but issues only the colon-namespaced position:read / trade:read / trade:execute. The inheritance above is what bridges them.
Platform access
Passing a scope check does not guarantee access to every provider. Kairos can disable API-key access to a provider independently of its browser/JWT availability. Selected RPC procedures explicitly enforce this gate, as do provider-scoped order execution requests;kalshi_offchain is checked as
kalshi.
For RPC calls, a disabled provider returns 403 FORBIDDEN with API access is disabled for <provider>. A settings lookup failure returns 500 INTERNAL_SERVER_ERROR with Unable to verify platform API access.
Gotcha: an aggregating procedure fails as a whole if any provider it
touches is disabled. An unfiltered
positions.getPositions or
balances.getAllBalances request may check several providers at once. Add
the narrowest available provider or chain filter when you only need one
venue.403 with error_details.code: "AUTH_INSUFFICIENT_SCOPE", while an unavailable access check returns 503 with code "INTERNAL_ERROR". Some status-only endpoints omit the structured body. This provider gate is distinct from the execution killswitch: platform access affects API-key clients only, while an execution killswitch blocks new order submission for all callers. See REST authentication for the cross-API behavior.
What’s allowed
Exchange balances and wallet operations
The public executor balance and wallet REST routes have moved to RPC. These read-only procedures continue to accept API keys:
Kalshi balances remain in USD and include
portfolio_value and the original
by_shard exchange indexes. Allowance amounts remain decimal strings.
polymarket.getDepositWalletNonce and polymarket.syncBalances require a
browser session. The generic polymarket.submitSignedBatch and
polymarket.getSignedBatchStatus procedures have been removed. Browser
withdrawals use the session-bound signing and status procedures. Granting
allowances still uses POST /exchanges/{exchange_id}/allowances on the executor.
read — general queries (RPC server)
markets.getMarkets,getAllMarkets,getMarketsPage,getTokenOutcome,getTrendingMarkets,getTrendingItemspolymarket.getMarkets,hasCredentials,getWalletInfoexchange.getActiveExchanges,hasCredentialscombo.getComboMarketsperpetuals.venuestxodds.listFixtures,getFixture,getFixtureTimeserieslpRewards.getMyRewardsinfra.executionManifestwallet.list,wallet.getForChainwalletTransactions.list,getRecent,getStats,getByIddeposit.polymarketBridge,polymarketBridgeSupportedAssets,polymarketBridgeQuote,polymarketBridgeStatus,evmWalletBalancesauth.me,user.getMe,user.getKalshiExecutionModetrading.getStatus,trading.getKairosPublicKeyfees.getUserTier,getUserVolumeMetrics,getFeeTiersrewards.getMyPoints,getMyHistory,getMyBreakdownreferrals.getMyStats,getLeaderboard,getShareLink,getMyReferrer,getMyInviteestournaments.getActive,competitions.getActive,getLeaderboard,getRankEventsinvites.listMine,getAllocation,getVolumeProgressgeo.getMyCountry,checkAccess,checkAccessBulkfeatureFlags.get,getMany
position:read — position/portfolio/balance queries (RPC server)
positions.getPositions— authoritative current holdingspositions.getPositionwallet.getPortfolio— unified personal + trading-account portfolio viewwallet.discoverTokensportfolio.getSummary,getActivity,getChartData,getDailyPnLBreakdown,getSettlementsbalances.getWalletBalances,getAllBalances,getGasEstimate,getBalanceForAddresspolymarket.getEoaBalances,getSafeBalancescombo.getComboHistorypnl.getPnLcopytrading.listSubscriptions,allOpenPositions,subscriptionOpenPositions
trade:read — order/fill history
orders.getChartFills(RPC server)copytrading.traderFeed(RPC server) — grouped with the queries above by feature, but gated ontrade:read, notposition:readGET /orders,GET /orders/{order_id},GET /orders/fee-quote(order execution service,execution.kairos.trade)
trade — the one RPC trade mutation
positions.closePosition— derives close-order parameters for a specific position. See Positions.
trade:execute — execution mutations
Order submission and cancellation are not RPC procedures. They are REST endpoints on the order execution service, authenticated with the same three headers:
POST /orders— submit a market or limit orderPOST /orders/{order_id}/cancel— cancel one orderPOST /orders/cancel-all— cancel every open order on an exchange (optionally scoped to onemarket_id)POST /orders/cancel-batch— cancel a specific set of orders in one call
trade:execute, including
supported redeem and CTF split/merge flows. Treat the endpoint’s documented
scope as authoritative; API-key mutation access is allowlisted, not inferred
from the HTTP method.
What’s NOT allowed
A number of high-risk or account-lifecycle operations are only reachable via the web app. Calling any of these with an API key returns403 Forbidden:
- Session / credential lifecycle — issuing, revoking, or listing session tokens; enabling or revoking exchange credentials.
- Wallet lifecycle — creating, renaming, or managing the underlying custodial wallets.
- Profile changes — updating username, display name, execution-mode preferences, or any other profile fields.
- Position maintenance —
positions.recalculatePosition,updatePosition,backfillPositions,syncFromPolymarket,syncFromPredictfun, and the order-reconciliation mutations (orders.reconcilePolymarketFills,orders.backfillTrades). - Copy-trade subscription management —
copytrading.createSubscription,updateSubscription,pauseSubscription,resumeSubscription,cancelSubscription,followTrader, and thebulk*variants. (Reading subscription state is allowed.) - Fund movement —
wallet.transferand the Hyperliquid deposit / transfer / withdraw prepare+submit pairs. - Invites, referrals, notifications, and integrations — managing invites, linking third-party accounts (Telegram, Discord), creating support tickets, saved layouts, or banners.
IP whitelisting
A credential can optionally be pinned to a specific source IP (or list of IPs). When a whitelist is set, requests from any other IP return403 Forbidden (Source IP is not in the credential's whitelist). An empty whitelist means no IP restriction.
Matching is an exact string comparison against the last X-Forwarded-For hop — the entry the load balancer appends, which a caller cannot forge because spoofed entries can only be prepended. CIDR ranges are not supported: list each address individually. The check runs on every request and is never cached.
If your bot runs on a floating IP (residential cloud, CI/CD pipeline, etc.), ask the issuer to leave the whitelist empty. Empty = no restriction.
Rate limits
Rate-limit buckets are keyed on the credential ID, not on the owning user. Two credentials belonging to the same user get independent budgets, and a per-key override applies to exactly one window. Server defaults (per minute), all overridable per deployment and per credential:
A credential can carry absolute per-bucket overrides (
rpc.public / rpc.auth / rpc.mutations / rpc.queries / rpc.expensive), plus separate budgets for order submission, the data API, websockets, and the market-data API. Ask the issuer what your key is set to.
Gotcha: 429 responses carry noRetry-Afterheader. The delay is in the error message —Rate limit exceeded. Please retry in <N> seconds.
Key rotation
To rotate:- Request a new credential from Kairos.
- Update your client to use the new
X-Api-Key/X-Api-Secret. - Verify requests succeed.
- Have Kairos revoke the old credential (sets
status='revoked').
401 Unauthorized — there’s no grace period. Revocation publishes a cache invalidation so every replica of every service drops the credential in near-real-time; if that pub/sub path is unavailable, passive expiry still bounds the window at the credential cache’s TTLs (15 s in-process, 60 s in Redis). Plan rotations accordingly.
There is also no “rotate in place” operation — a rotation is always create new, then revoke old, and the plaintext of the new credential is shown exactly once at creation.
Errors
Every error is the standard tRPC envelope (error.message, error.code, error.data.code, error.data.httpStatus, error.data.path) — see Overview.
Auth failures short-circuit with their own status rather than collapsing to a generic 401 — a whitelist block stays a 403, a backend outage stays a 500.
401 and 403 are intentionally distinguished: 401 means “your credentials are wrong or revoked”, 403 means “your credentials are fine but you cannot call this thing from here.” Handle them differently in your client.

