Skip to main content
The Kairos RPC API exposes account-scoped procedures for positions, orders, balances, bots, and copy-trading state, read straight from the Kairos database and trade pipeline rather than a third-party subgraph. This page is the calling convention — URL shape, auth headers, request/response envelope, rate limits, pagination, and errors — that every RPC page assumes. Read it once before you write your first call.

When to use RPC instead of REST

Use the RPC API instead of the public REST /trader-stats/* endpoints when you need:
  • Current open positions for a user (wallet + all linked wallets).
  • Real-time order status, fills, and execution errors.
  • Programmatic position close / liquidation flows.
  • Bot configuration and history.
REST /trader-stats/* is a cached proxy over third-party on-chain indexing for Polymarket. It’s great for public-facing trader profiles, but it can lag and its volume numbers use Polymarket’s contract-count convention — not notional USD (notional is quantity × price). For anything operational, use RPC.

Base URL

Every procedure follows the <router>.<procedure> naming. Example: positions.getPositions, positions.closePosition, markets.getMarkets. Order submission and cancellation are the one exception — those live on the order execution REST service (https://execution.kairos.trade/), not under this <router>.<procedure> scheme; see API Keys.

Authentication

Programmatic clients authenticate with an API key — the kairos_ck_... triple — passed as three HTTP headers on every request:
See API Keys for how to obtain a credential, the full per-scope list of procedures it unlocks, and the security model. Your key scopes every call to its owner’s account — positions.getPositions only ever returns that user’s positions, across every wallet linked to the account.

Scopes

Keys carry scopes with limited one-way inheritance: position:read/trade:read satisfy RPC read, and trade:execute satisfies RPC trade. The reverse is not true — holding read does not grant position:read. See the full scope table for the exact mapping. Calling a procedure your key isn’t scoped for returns 403 FORBIDDEN.

Access levels

Each procedure declares an access level. For API-key callers: Only the procedures in the API-key allowlist accept API keys; any other procedure returns 403 FORBIDDEN with This procedure does not accept API key authentication.

HTTP semantics (tRPC)

The server speaks tRPC v10 over plain HTTP. If you’re calling from TypeScript, use the generated @kairoslive/kairos client; from anything else, call the HTTP endpoints directly — the wire format is simple.

Queries — GET

Pass the input as a URL-encoded JSON object in ?input=.... The input is wrapped in {"json": ...} by the standard tRPC client, but the server also accepts raw JSON without the wrapper.
Python with httpx:

Mutations — POST

Send the input as JSON in the body, wrapped in {"json": ...}:
Trading mutations like this require an API key with the trade scope. No CSRF token is needed — header auth isn’t cookie-replayable.
Gotcha: mutations are strict about the envelope. Anything other than POST returns 405 METHOD_NOT_SUPPORTED, and a missing or non-application/json Content-Type returns 415 UNSUPPORTED_MEDIA_TYPE. Bodies are capped at 1 MB (413 PAYLOAD_TOO_LARGE), enforced on both Content-Length and the actual read, so chunked encoding can’t bypass it.

Response shape

Successful calls return:
Errors return:
data.code is the tRPC string code, data.httpStatus matches the HTTP status on the response, and data.path echoes the procedure (truncated to 128 characters for unrouted paths). The outer code is the JSON-RPC numeric equivalent: Always extract result.data for success or check for the error field on failure.
Gotcha: a missing result.data with no error field is not an empty result. It indicates a malformed response — treat it as a fatal error, not as “no data”.

Rate limits

Every procedure declares one bucket. Buckets are sliding one-minute windows in Redis, shared across replicas. The identity a window is keyed on depends on how you authenticated:
  • API key → the credential (apikey:<credentialId>). Two credentials owned by the same user get separate budgets.
  • Session JWT → the user.
  • Unauthenticated → the client IP.
Server defaults (all overridable per deployment via RATE_LIMIT_*_RPM, and per credential by Kairos): Exceeding a bucket returns TOO_MANY_REQUESTS (HTTP 429).
Gotcha: there is no Retry-After header on a 429. The wait is embedded in the message — Rate limit exceeded. Please retry in <N> seconds. Parse it, or back off exponentially.
Gotcha: the rate limiter fails closed. If its Redis is unreachable the server returns 500 INTERNAL_SERVER_ERROR with Service temporarily unavailable. Please retry shortly. That is a retryable outage, not a bug in your payload.

Pagination

Queries that list entities use offset/limit pagination unless otherwise documented:
  • limit — integer; <= 0 falls back to the procedure default (50 on the position/portfolio lists), values above 100 are clamped to 100.
  • offset — integer, default 0. positions.getPositions rejects a negative offset with BAD_REQUEST; other list procedures treat out-of-range offsets as an empty page.
Responses include total and hasMore (offset + limit < total) so callers can loop until hasMore is false.

Errors you’ll see

Unrouted paths (404) are themselves rate limited on a separate per-IP bucket, so a scanner probing for procedure names gets 429s rather than an enumeration oracle.