> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kairos.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors, limits and idempotency

> Every endpoint, field, result and error of the wallet API.

Send `X-Kairos-Wallet-Api: 2` and either `X-Kairos-Partner-Id` or an API key on every request ([authentication](https://docs.kairos.trade/wallets/authentication.md)). Bodies are JSON with **camelCase** field names. The machine-readable schema is in the [API reference](https://docs.kairos.trade/api-reference.md).

## Endpoints

| Method | Path | Does | Needs a sign-in token |
| - | - | - | - |
| `POST` | `/v3/actions` | Start an action | No |
| `POST` | `/v3/actions/{id}` | Answer the current step | No |
| `GET` | `/v3/actions/{id}` | Read an action | No |
| `GET` | `/v3/wallets/{address}` | Whether the wallet can trade, per venue | No |
| `GET` | `/v3/wallets/{address}/orders` | Orders on every venue | Yes |
| `GET` | `/v3/wallets/{address}/positions` | Positions on every venue | Yes |
| `GET` | `/v3/fees` | Fees the wallet owes (preview) | Yes |
| `POST` | `/v3/fees/settlements` | Open a fee settlement (preview) | Yes |
| `GET` | `/v3/fees/settlements/{id}` | Read and advance a settlement (preview) | Yes |
| `POST` | `/v3/fees/settlements/{id}/answer` | Answer a settlement (preview) | Yes |
| `POST` | `/v3/trade-plans` | Prepare a trade plan (preview) | Yes |
| `GET` | `/v3/trade-plans/{id}` | Read a plan (preview) | Yes |
| `POST` | `/v3/trade-plans/{id}/answers` | Send plan signatures (preview) | Yes |
| `POST` | `/v3/trade-plans/{id}/execute` | Execute a plan (preview) | Yes |
| `POST` | `/v3/trade-plans/{id}/reports` | Report client-sent funding (preview) | Yes |
| `POST` | `/v3/onchain-intents` | Prepare a split, merge or redeem (preview) | Yes |
| `GET` | `/v3/onchain-intents/{id}` | Read an intent (preview) | Yes |
| `POST` | `/v3/onchain-intents/{id}/execute` | Execute an intent (preview) | Yes |
| `POST` | `/v3/onchain-intents/{id}/reports` | Report a client-sent batch (preview) | Yes |

"Needs a sign-in token" applies with a partner id. An API key whose account owns the wallet doesn't need one. Preview endpoints are documented on [fees](https://docs.kairos.trade/wallets/fees.md), [trade plans](https://docs.kairos.trade/wallets/trade-plans.md) and [on-chain intents](https://docs.kairos.trade/wallets/onchain.md).

## Start an action

```json theme={null}
{ "wallet": "0x…", "action": { "type": "order", "market": "predictfun:1027194", "outcome": "Yes", "side": "buy", "price": "0.52", "size": "10" } }
```

| `type` | Fields |
| - | - |
| `setup` | Optional `venues`: any of `polymarket`, `predictfun`, `hyperliquid`. Default: every venue |
| `order` | `market`, `outcome`, `side` (`buy`/`sell`), `price`, `size`. Optional: `timeInForce` (default `GTC`), `expiresAt` (unix seconds, `GTD` only), `postOnly`, `clientOrderId` |
| `cancel` | `orderIds` (Kairos order ids, up to 100) **or** `all: true` |
| `login` | None |

* `market` is `venue:market_id`: `polymarket:0x…`, `predictfun:1027194`, `hyperliquid:11492`. A bare `0x…` condition id is Polymarket.
* `price` and `size` are **strings**. Prices are 0–1; sizes are shares.
* If the wallet isn't set up on the order's venue, the order runs the missing setup first, in the same action.
* Per-venue rules (time in force, decimals, minimums) are on [venues](https://docs.kairos.trade/wallets/venues.md).

### `clientOrderId`

Your own id for an order, unique per wallet and venue. Use it to retry an order safely. Up to 128 characters, and it can't start with `trade-plan:` (`400 clientOrderId uses a reserved prefix`). On Hyperliquid it must be `0x` plus 32 lowercase hex characters.

| You send the same `clientOrderId` with | You get |
| - | - |
| The same terms and partner, and the earlier order was placed | The earlier order, as a `done` action. Nothing is signed or placed |
| The same terms, and the earlier action was never signed | A fresh `needs_signature` step. Sign it again |
| Any different term (market, outcome, side, price, size, `timeInForce`, `postOnly`, `expiresAt`) | `409` with `code: "CLIENT_ORDER_ID_CONFLICT"`. Nothing is asked or placed |
| Anything, after the venue refused the earlier order (`NO_MATCH`, `VENUE_REJECTED`) | A new order. A refusal frees the id |

Terms compare as numbers, so `"0.5"` and `"0.50"` are the same price. An order whose outcome is unknown keeps its id.

## The step

Every action response is a step:

```json theme={null}
{
  "id": "…",
  "type": "order",
  "status": "needs_signature",
  "request": { "method": "eth_signTypedData_v4", "params": ["0x…", "{…}"] },
  "description": "Confirm this Predict.fun order",
  "chainId": "0x38",
  "venue": "predictfun",
  "expiresAtMs": 1793130772999,
  "result": null,
  "error": null
}
```

| Field | Type | Means |
| - | - | - |
| `id` | string | The action id. It stays the same for the whole action |
| `type` | string | `setup`, `order`, `cancel` or `login` |
| `status` | string | See below |
| `request` | object or `null` | On `needs_signature`: the EIP-1193 request for the wallet ([every kind](https://docs.kairos.trade/wallets/signing.md)) |
| `description` | string or `null` | Text to show the user |
| `chainId` | string or `null` | The chain the wallet must be on for `request` (EIP-155 hex). `null` means any chain |
| `venue` | string or `null` | The venue this step is for |
| `fallbackRequests` | array | With `wallet_sendCalls` only: the same calls as separate `eth_sendTransaction`s |
| `expiresAtMs` | integer | On `needs_signature`: the last moment (unix ms) a signature is accepted |
| `holdUntilMs` | integer | On a `working` order: when the market's matching delay ends |
| `result` | object or `null` | On `done` (and on a held order): the [result](#results) |
| `error` | string or `null` | On `failed`: why |
| `errorCode` | string | On `failed`, when there is a [code](#error-codes) |

| `status` | Do |
| - | - |
| `needs_signature` | Switch to `chainId` if set, pass `request` to the wallet, and post `{ "signature": "0x…" }`. For `wallet_sendCalls`, post `{ "result": … }` |
| `working` | `GET` the action again in about 2 seconds, or at `holdUntilMs` |
| `done` | Read `result` |
| `failed` | Read `error` and `errorCode`. Start a new action to retry |

### Timing

| What | How long |
| - | - |
| A whole action | Ends `done` or `failed` within **15 minutes** of being started. The time doesn't restart when you post |
| Reading a finished action | 10 more minutes after those 15. Then it's `404` |
| Signing a Polymarket or Predict.fun order | About 60 seconds |
| Signing a Hyperliquid order or a cancel | 5 minutes |
| Signing setup and sign-in steps | Until about 2 minutes before the action's 15 minutes run out |
| A post or refreshing `GET` | Waits up to about 25 seconds (45 on some multi-venue steps), then answers `working` |

`expiresAtMs` gives the exact deadline for each step. A signature that arrives later fails the action with `SIGNATURE_EXPIRED`.

Once you post a signature, the work runs to completion on Kairos's side, even if your request times out or drops. Posting the same signature again never places a second order: it returns the action as it is now.

## Results

### Setup

```json theme={null}
{
  "ready": false,
  "tradingWallet": "0x…",
  "transactionHash": "0x…",
  "venues": {
    "polymarket":  { "ready": true, "tradingWallet": "0x…", "transactionHash": "0x…" },
    "predictfun":  { "ready": false, "next": "fund", "tradingWallet": "0x…", "venueDetail": { "reason": "needs about 0.0004 BNB on BNB Chain to pay gas for 3 Predict.fun approvals", "approvals": 3, "gasWei": "…" } },
    "hyperliquid": { "ready": false, "error": "Hyperliquid is unavailable; retry shortly" }
  }
}
```

* `ready` is `true` when every requested venue is ready. `tradingWallet` and `transactionHash` at the top are Polymarket's.
* A venue that needs funds (`next: "fund"`) or can't be used (`next: "blocked"`) still ends the action `done`, with the reason in `venueDetail.reason`.
* Setup continues past a venue that fails, and reports it as `{ ready: false, error }`. The action fails only if every venue failed.
* Predict.fun adds `sessionExpiresAt`.

### Order

| Field | Type | Means |
| - | - | - |
| `orderId` | string | The Kairos order id. Use it to cancel |
| `status` | string | The order's status now, for example `live`, `delayed`, `executing`, `filled` or `cancelled` |
| `venue`, `market` | string | Where it was placed. `market` is `venue:market_id` |
| `venueOrderId` | string or `null` | The venue's id for the order |
| `venueStatus` | string or `null` | What the venue answered when the order was posted (Polymarket: `matched`, `live`, `delayed`, `unmatched`). It doesn't change afterwards |
| `filledSize` | decimal string or `null` | Shares matched. `null` means not known yet, never zero |
| `filledNotional` | decimal string or `null` | What the matched shares traded for, in the venue's currency, before fees |
| `avgPrice` | decimal string or `null` | Average fill price, 0–1. `null` when nothing filled |
| `final` | boolean | `true`: matching is finished; nothing more will fill and nothing rests. `false`: the order is resting, or its fills are still being confirmed |
| `success` | boolean | `false` when the order ended without doing anything, failed at the venue, or isn't confirmed yet |
| `message` | string or `null` | Why `success` is `false` |

`message` is one of:

* `No fill: the venue matched none of this order`
* `The order failed at the venue; any fills shown were reverted`
* `The venue rejected the order`
* `Outcome not confirmed yet; check this wallet's orders before placing it again`

How results end:

* A `FAK`, `FOK` or `IOC` order normally ends with `final: true`. One that matched nothing fails with `NO_MATCH` instead.
* A `GTC` or `GTD` order that rests ends `done` with `final: false`. Follow it in [orders](https://docs.kairos.trade/wallets/portfolio.md).
* Predict.fun reports no fills when an order is posted, so its results have `final: false`.
* If a final match later fails to settle on-chain, the action shows `status: "failed"`, the reverted fills, `final: true` and `success: false`.

Amounts are gross. Venue fees are charged on top, or out of proceeds, by the venue. Order results and order rows carry no fee fields.

### Delayed markets

Some Polymarket markets hold every order that could take liquidity for a short delay before matching it. Sports markets do this during games (usually 1–3 seconds), and some others hold for 250 ms. Market data shows the delay as `match_delay_ms` ([Show markets](https://docs.kairos.trade/wallets/markets.md#matching-delay)).

A held order keeps its action `working`. The step carries `holdUntilMs` and a provisional `result` with `final: false`:

```json theme={null}
{
  "id": "…",
  "type": "order",
  "status": "working",
  "description": "Waiting for the venue to match your order",
  "holdUntilMs": 1793130718126,
  "result": { "orderId": "…", "status": "delayed", "venueStatus": "delayed", "filledSize": null, "final": false, "success": true, "message": null, "…": "…" },
  "error": null
}
```

When the delay ends, the action becomes `done` with the real fill and `final: true`. If the venue matches none of it, it is still `done`, with `filledSize: "0"`, `final: true`, `success: false` and `message: "No fill: the venue matched none of this order"`. In the rare case that the action's 15 minutes run out first, it ends `done` with what is known and `final: false`. You don't need a sign-in token to follow a held order.

### Cancel

```json theme={null}
{
  "cancelled": ["<orderId>"],
  "alreadyClosed": ["<orderId>"],
  "failed": [{ "orderId": "<orderId>", "reason": "order not found" }],
  "venueDetail": { "predictfun": { "error": "Predict.fun is unavailable; retry shortly" } }
}
```

* Cancel by id: `failed` lists each order that couldn't be cancelled, with `reason`. Ids that aren't this wallet's orders are reported as `order not found`.
* Cancel all: `failed` is a count, and `venueDetail` holds per-venue counts: `{ "polymarket": { "cancelled": 1, "failed": 0 } }`.
* If no venue confirmed and one may have acted, the action fails with `CANCEL_OUTCOME_UNKNOWN` and keeps the `result`.

### Login

`{ "token": "…", "expiresAt": "2026-10-29T12:00:00Z" }`. Send it as `Authorization: SIWE <token>` ([details](https://docs.kairos.trade/wallets/authentication.md#the-sign-in-token)).

## Wallet status

`GET /v3/wallets/{address}`:

```json theme={null}
{
  "wallet": "0x…",
  "ready": false,
  "next": "fund",
  "tradingWallet": "0x…",
  "balance": "25",
  "venues": {
    "polymarket":  { "ready": true, "next": null, "tradingWallet": "0x…", "balance": "25", "currency": "pUSD", "venueDetail": { "chainId": "0x89" }, "asOf": "…" },
    "predictfun":  { "ready": false, "next": "fund", "tradingWallet": "0x…", "balance": "0", "currency": "USDT",
                     "venueDetail": { "chainId": "0x38", "gasBalance": "0", "missingApprovals": 3, "sessionExpiresAt": null }, "asOf": "…" },
    "hyperliquid": { "ready": true, "next": null, "tradingWallet": "0x…", "balance": "23.977265", "currency": "USDC",
                     "venueDetail": { "chainId": "0xa4b1", "network": "Mainnet" }, "asOf": "…" }
  }
}
```

| Field | Means |
| - | - |
| `ready` | Every available venue is ready |
| `next` | What the wallet needs next: `"setup"`, `"wait"` (approvals it sent are confirming; nothing to sign), `"fund"`, `"blocked"` (that account can't be used here; see `venueDetail.reason`), or `null` when it can trade |
| `venues.{venue}.balance` | Collateral in whole units of `currency`. `null` when it couldn't be read |
| `venues.{venue}.asOf` | When the venue was read. Status can be up to about 30 seconds old |
| `venues.{venue}.available` | `false`, with `error`, when the venue couldn't be read. It doesn't count towards `ready` |
| `tradingWallet`, `balance` (top level) | Polymarket's |

`venueDetail` keys:

| Venue | Keys |
| - | - |
| Polymarket | `chainId` |
| Predict.fun | `chainId`, `gasBalance` (BNB), `missingApprovals`, `sessionExpiresAt` |
| Hyperliquid | `chainId`, `network`, `transferFromPerps: { available, note }`, and when blocked or missing an agent: `blocked`, `reason`, `accountMode`, `code` |

Venues that aren't offered on your deployment are left out.

## Orders

`GET /v3/wallets/{address}/orders?open=true&limit=50&venue=polymarket`

| Query | Means |
| - | - |
| `open` | `true` for live orders only. Default: every status |
| `limit` | 1–500, default 50. The newest orders across venues come first. There's no cursor |
| `venue` | One venue only |

```json theme={null}
{
  "orders": [{
    "orderId": "…", "venue": "predictfun", "market": "predictfun:1027194", "marketId": "1027194",
    "outcome": "Yes", "tokenId": "…", "side": "buy", "type": "limit", "price": "0.52", "size": "10",
    "filledSize": "0", "avgFillPrice": null, "timeInForce": "GTD", "postOnly": false, "expiresAt": null,
    "status": "live", "venueOrderId": "…", "clientOrderId": "…",
    "createdAt": "2026-10-16T00:00:00Z", "updatedAt": "…"
  }],
  "venueDetail": { "hyperliquid": { "error": "…" } }
}
```

`status` is one of `pending`, `queued`, `locked`, `executing`, `live`, `partial`, `filled`, `cancelled`, `expired`, `failed`, `orphaned`. `venueDetail` appears only when a venue couldn't be read.

## Positions

`GET /v3/wallets/{address}/positions?venue=polymarket`. The snapshot is nested under `positions`:

```json theme={null}
{
  "positions": {
    "positions": [{ "venue": "polymarket", "market": "polymarket:0x…", "marketId": "0x…", "outcome": "Yes", "tokenId": "…",
                    "size": "4.5", "availableToSell": "4.5", "avgPrice": "0.52", "realizedPnl": "0", "holdingWallet": "0x…", "lastTradeAt": "…" }],
    "resolved":  [{ "venue": "polymarket", "market": "…", "marketId": "…", "outcome": "Yes", "tokenId": "…", "size": "2", "redeemable": true, "holdingWallet": "0x…" }],
    "closed":    [{ "venue": "polymarket", "tokenId": "…" }],
    "asOf": "…"
  },
  "venueDetail": { "<venue>": { "error": "…" } }
}
```

`resolved` lists positions whose market resolved; `redeemable` is `true` for winners ([redeem them](https://docs.kairos.trade/wallets/onchain.md)). `closed` lists positions closed to zero. For realized P\&L on closed positions, use [trader stats](https://docs.kairos.trade/rest/trader-stats.md).

## Errors

An error is JSON: `{ "error": "message" }`, sometimes with `code`. The HTTP layer answers some malformed requests in plain text instead ([details](https://docs.kairos.trade/wallets/http.md#errors-youll-see-without-the-sdk)).

**After a signature is posted, failures come back as HTTP `200` with `status: "failed"`** and an `errorCode`, not as error statuses.

| Status | Means |
| - | - |
| `400` | Bad input, or a signature that isn't from this wallet (post a correct one; the step stays open) |
| `401` | No partner id or key, an invalid key, or a missing, expired or foreign sign-in token on a read |
| `403` | Unknown partner id, a key without `trade:execute`, the wallet API or a venue not enabled for this account, or a disabled wallet |
| `404` | Unknown or expired action, or a read for a wallet the token or key doesn't own |
| `409` | The wallet isn't set up; or it's already linked to a Kairos account (that user trades in the Kairos app); `CLIENT_ORDER_ID_CONFLICT`; or `this action is busy; GET it and retry` |
| `413` | Body too large |
| `415`, `422` | Missing `Content-Type`, or a body that doesn't match the schema (plain text) |
| `429` | Too many requests, or `too many new wallets; try again later` ([limits](https://docs.kairos.trade/wallets/authentication.md#rate-limits)) |
| `500` | Internal error. For an order, treat as outcome unknown |
| `502`, `504` | A venue didn't answer. `GET /v3/actions/{id}` before retrying |
| `503` | A venue or dependency is temporarily unavailable. Retry shortly |

### Error codes

A `failed` action carries one of these in `errorCode`. The same codes appear as `code` on an HTTP error body.

| Code | Means | Was anything placed? |
| - | - | - |
| `NO_MATCH` | A `FAK`, `FOK` or `IOC` order found nothing to match at its price | No. The `clientOrderId` is free again |
| `VENUE_REJECTED` | The venue refused the order. `error` has its reason | No. The `clientOrderId` is free again |
| `SIGNATURE_EXPIRED` | The signature arrived after `expiresAtMs`, or never arrived | No |
| `ORDER_OUTCOME_UNKNOWN` | Kairos couldn't confirm whether the venue took the order | **Maybe.** See below |
| `CANCEL_OUTCOME_UNKNOWN` | Kairos couldn't confirm whether the cancel went through | Check the order |
| `CLIENT_ORDER_ID_CONFLICT` | The `clientOrderId` names an order with different terms | No |
| `HYPERLIQUID_AGENT_MISSING` | Hyperliquid no longer lists the wallet's agent. Run setup, or place the order again to re-approve it | No |
| `POLYMARKET_V2_TRADING_DISABLED` | Polymarket's newer exchange is paused for this market | No |

Failures without a code, such as a wallet that isn't ready or `too many orders; slow down`, are described in `error`.

> [!WARNING]
>
> **`ORDER_OUTCOME_UNKNOWN`: the order may be live.** It happens when the venue doesn't answer clearly (a timeout or a server error) and Kairos can't find the order there afterwards. Check `/v3/wallets/{address}/orders?open=true` before placing it again, or retry with the same `clientOrderId` and terms: you get the existing order back, never a second one.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.