---
title: "Start a wallet action (setup, order, cancel, login)"
method: POST
path: "/v3/actions"
tags: ["Actions"]
---

# Start a wallet action (setup, order, cancel, login)

`POST /v3/actions`

## Request

- Base URL: `https://execution.kairos.trade`
- URL: `https://execution.kairos.trade/v3/actions`
- Auth: one of:
  - API key in header `X-Kairos-Partner-Id`
  - API key in header `X-Client-Id` and API key in header `X-Api-Key` and API key in header `X-Api-Secret`

Starts one action for a self-custody wallet and returns its first **step**. Kairos never holds the wallet's key: each step either asks the wallet for one EIP-1193 request (`status: needs_signature`) or reports progress (`working`) or the outcome (`done` / `failed`).

**The step loop.**
1. `POST /v3/actions` → a step.
2. While `status` is `needs_signature`: switch the wallet to `chainId` (when non-null), run `request` through the wallet's EIP-1193 provider, and `POST /v3/actions/{id}` with `{"signature": "0x…"}` (or `{"result": …}` for `wallet_sendCalls`). Answer before `expiresAtMs`.
3. While `status` is `working`: `GET /v3/actions/{id}` every 1–2 s (or after `holdUntilMs` when present).
4. Stop at `done` (read `result`) or `failed` (read `error` and `errorCode`).

**Action types.** `setup` prepares the wallet on each requested venue; `order` places one order and runs any missing setup first, in the same action; `cancel` cancels orders on every venue with one Polygon signature; `login` returns a sign-in token for the private reads. There are no other action types.

**Body rules.** Field names are **camelCase only**: unknown fields — including snake_case spellings such as `time_in_force`, `post_only`, `client_order_id`, `expires_at`, `order_ids` — are silently ignored, so a snake_case order becomes a `GTC`, non-post-only order without a client id. `price` and `size` must be JSON **strings**; a JSON number is rejected with a plain-text `422`. `expiresAt` is an integer (Unix seconds).

**Idempotency.** `Idempotency-Key` is **not** honoured here. Use `clientOrderId` on orders: the same `clientOrderId` with the same terms returns the existing order (nothing is signed), and different terms are refused with `CLIENT_ORDER_ID_CONFLICT`.

**Timing.** An action lives 15 minutes from creation and stays readable for 10 more minutes, then reads `404`. Polymarket and Predict.fun order signatures are accepted for about 60 s, Hyperliquid order signatures for 300 s, and cancel signatures (every venue) for 300 s; `expiresAtMs` on the step is authoritative.

**Errors before the first signature are HTTP errors.** When the action fails before the wallet was ever asked to sign (for example every requested venue is unavailable), the response is that error's status and body rather than a `failed` step.

Send `X-Kairos-Wallet-Api: 2`; without it this route serves the original Polymarket-only contract.

## Headers

- `X-Kairos-Wallet-Api` '2', required

## Request body

- WalletActionRequest: Body of `POST /v3/actions`. **camelCase only** — unknown fields, including snake_case spellings, are ignored without error.
  - `wallet` string, required: EVM address, `0x` + 40 hex.
  - `action` union, required: The action to run, tagged by `type`.
    - WalletSetupAction: Prepare the wallet to trade. Per venue: - **Polymarket** (Polygon, `0x89`): one or two signatures, no gas — connect the wallet, then approve its trading wallet (a smart wallet owned by the user's address) when it is not approved yet. - **Predict.fun** (BNB Chain, `0x38`): when approvals are missing, one `wallet_sendCalls` batch the wallet pays BNB gas for; then a sign-in signature, no gas. Without enough BNB the venue ends `ready: false, next: "fund"`. - **Hyperliquid** (Arbitrum, `0xa4b1`; `0x66eee` on testnet): one or two signatures, no gas — allow Kairos to place trades for the account (it cannot withdraw), plus a builder-fee approval where one applies. An account that was never funded on Hyperliquid ends `ready: false, next: "fund"`. Setup continues past a venue that fails and fails only when every venue failed. A wallet already ready returns `done` at once.
      - `type` 'setup', required
      - `venues` WalletVenue[]: Venues to set up. Omitted or empty means every venue offered on this deployment. Case-insensitive; duplicates are ignored.
    - WalletOrderAction: Place one order. Missing setup on the order's venue runs first, inside the same action. Signatures per order: Polymarket and Predict.fun one; Hyperliquid one, plus a perps-to-spot transfer prompt when spot USDC is short and perps can cover it. Hyperliquid (HIP-4 outcome markets): `price` above 0 and below 1 with at most 5 decimals, whole-share `size`, `timeInForce` `GTC`, `FAK` or `IOC` only, `postOnly` only with `GTC`, a minimum notional on buys, and `clientOrderId` as `0x` + 32 lowercase hex.
      - `type` 'order', required
      - `market` string, required: `venue:market_id` — `polymarket:0x<64-hex condition id>`, `predictfun:<digits>`, `hyperliquid:<digits>` — or a bare Polymarket condition id.
      - `outcome` string, required: Outcome name as the venue lists it, e.g. `Yes`.
      - `side` 'buy' | 'sell', required: Case-insensitive.
      - `price` string, required: Limit price on the 0–1 scale, as a **string**. A JSON number is rejected (`422`).
      - `size` string, required: Shares, as a **string**.
      - `timeInForce` 'GTC' | 'GTD' | 'FOK' | 'FAK' | 'IOC': Case-insensitive. `GTD` requires `expiresAt`. On Predict.fun a `GTC` order rests until shortly before the venue session ends.
      - `postOnly` boolean
      - `clientOrderId` string: Your idempotency key for this order. The same id with the same terms returns the existing order without a new signature; different terms fail with `CLIENT_ORDER_ID_CONFLICT`. Must not be blank or start with `trade-plan:`. Hyperliquid requires `0x` + 32 lowercase hex.
      - `expiresAt` integer: Expiry for `GTD`, Unix **seconds** (an integer, not a string). Predict.fun caps it shortly before the venue session ends.
    - WalletCancelAction: Cancel orders on every venue with one signature. Send exactly one of `orderIds` (1–100 Kairos order ids) or `all: true`. The cancel is signed as Kairos typed data on Polygon (`chainId 0x89`) for every venue, so the wallet may be asked to switch to Polygon.
      - `type` 'cancel', required
      - `orderIds` string[]: Kairos `orderId`s (UUIDs), not venue order ids. A non-UUID is a plain-text `422`.
      - `all` boolean: Cancel every open order of this wallet on every venue.
    - WalletLoginAction: Sign in with Ethereum (`personal_sign`, no gas) to get a 24-hour token for `Authorization: SIWE <token>`. The wallet must have completed setup on at least one venue. The token is stateless: a second login does not revoke the first.
      - `type` 'login', required

## Response `200`

The action's first step. Usually `needs_signature`; `done` when nothing needs signing (a wallet already set up, or a `clientOrderId` that already names this exact order).

- WalletActionStep: The current step of an action. Every response from `/v3/actions` and `/v3/actions/{id}` is one of these.
  - `id` string, uuid, required: Action id. One id for the whole action, across venues.
  - `type` 'setup' | 'order' | 'cancel' | 'login', required
  - `status` 'needs_signature' | 'working' | 'done' | 'failed', required: `needs_signature` — run `request` and answer; `working` — poll; `done` — read `result`; `failed` — read `error` and `errorCode`.
  - `request` WalletEip1193Request, required: An EIP-1193 request to run through the wallet's provider exactly as given (`provider.request({method, params})`). - `eth_signTypedData_v4`: `params` = `[address, typedData]` — `typedData` is a **JSON string**; pass it unchanged. - `personal_sign`: `params` = `[hexMessage, address]`. - `wallet_sendCalls` (EIP-5792): `params` = `[{version, chainId, from, atomicRequired, calls}]`; the wallet pays gas. - `eth_sendTransaction`: `params` = `[{from, to, data, value, chainId?}]`; the wallet pays gas.
    - `method` 'eth_signTypedData_v4' | 'personal_sign' | 'wallet_sendCalls' | 'eth_sendTransaction', required
    - `params` unknown[], required
      - unknown
  - `description` string, nullable, required: Text to show with the wallet prompt (e.g. `Confirm this Hyperliquid order`), or progress text while `working`.
  - `chainId` string, nullable, required: EIP-155 chain id (hex) the wallet must be on to answer — `0x89` Polygon, `0x38` BNB Chain, `0xa4b1` Arbitrum One (`0x66eee` Arbitrum Sepolia on Hyperliquid testnet). `null` for `personal_sign` and when nothing is asked.
  - `venue` 'polymarket' | 'predictfun' | 'hyperliquid', required: Venue id. `predict.fun` is accepted as an input alias of `predictfun`.
  - `fallbackRequests` WalletEip1193Request[]: Present with `wallet_sendCalls` requests only — the same calls as individual `eth_sendTransaction` requests, for wallets without EIP-5792. Send them in order and answer with `{"result": ["0xhash", …]}`.
    - `method` 'eth_signTypedData_v4' | 'personal_sign' | 'wallet_sendCalls' | 'eth_sendTransaction', required
    - `params` unknown[], required
      - unknown
  - `expiresAtMs` integer: Last accepted millisecond (Unix ms) for answering this prompt. Answers after it fail the action with `SIGNATURE_EXPIRED`.
  - `holdUntilMs` integer: On a `working` order the venue is still matching — poll again after this time (Unix ms). `result` then holds a provisional outcome with `final: false`.
  - `result` union, required: Outcome on `done` (and the provisional outcome of a held order). Shape depends on `type`.
    - WalletSetupResult: `result` of a `setup` action.
      - `ready` boolean, required: Every requested venue is ready.
      - `venues` object: Outcome per requested venue.
      - `tradingWallet` string, nullable: Polymarket's trading wallet, when Polymarket was set up; `null` when Polymarket's setup failed.
      - `transactionHash` string, nullable: Polymarket's approval transaction, when Polymarket was set up.
    - WalletOrderResult: `result` of an `order` action. No fee fields: Kairos and venue fees are not reported here.
      - `orderId` string, uuid, required: Kairos order id — use it to cancel.
      - `status` string, required: Order status: Polymarket's answer (`matched`, `live`, `delayed`, `unmatched`) or the Kairos order status on Hyperliquid (`executing`, `live`, `partial`, `filled`, `cancelled`, `failed`, …). Read `final` and `success` rather than matching on this.
      - `venueOrderId` string, nullable
      - `venueStatus` string, nullable: The venue's first answer.
      - `filledSize` string, nullable: Shares filled; `null` when unknown.
      - `filledNotional` string, nullable: Collateral spent or received, before venue fees.
      - `avgPrice` string, nullable
      - `final` boolean, required: Matching is finished.
      - `success` boolean, required: `false` when the order did nothing, failed, or is not confirmed yet.
      - `message` string, nullable: Set when `success` is false: - `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`
      - `venue` 'polymarket' | 'predictfun' | 'hyperliquid': Venue id. `predict.fun` is accepted as an input alias of `predictfun`.
      - `market` string: `venue:market_id`.
    - WalletCancelResult: `result` of a `cancel` action. If nothing was confirmed and a venue may have acted, the action fails with `CANCEL_OUTCOME_UNKNOWN` and keeps this result.
      - `cancelled` string[], required: Order ids cancelled.
      - `alreadyClosed` string[]: By id — orders already filled, cancelled or expired.
      - `failed` union, required: By id — the failures; with `all` — the number of failures.
        - WalletCancelFailure[]
          - `orderId` string, required
          - `reason` string, required: The venue's reason, `cancel failed; retry`, `order not found` (also for ids that are not this wallet's), `venue unavailable; retry`, or an outcome-unknown message.
          - `code` string: `CANCEL_OUTCOME_UNKNOWN` when the venue may have cancelled.
        - integer
      - `venueDetail` object, required: Per venue: `{cancelled, failed}` counts for `all`, or `{error}` when the venue could not be reached (`{error, code: "CANCEL_OUTCOME_UNKNOWN"}` when it may have cancelled). Empty when every venue answered on a by-id cancel.
    - WalletLoginResult: `result` of a `login` action. Send it as `Authorization: SIWE <token>`.
      - `token` string, required: Opaque sign-in token (at most 4096 bytes).
      - `expiresAt` string, date-time, required: 24 hours after issue.
  - `error` string, nullable, required: Why the action failed; non-null on `failed`.
  - `errorCode` 'NO_MATCH' | 'VENUE_REJECTED' | 'ORDER_OUTCOME_UNKNOWN' | 'CANCEL_OUTCOME_UNKNOWN' | 'SIGNATURE_EXPIRED' | 'CLIENT_ORDER_ID_CONFLICT' | 'HYPERLIQUID_AGENT_MISSING': Machine-readable reason on a `failed` step; the key is omitted when the failure has no code. Codes may be added; treat an unknown code as a failure whose order was not placed. | Code | Meaning | Order placed? | |---|---|---| | `NO_MATCH` | An immediate order (FOK/FAK/IOC) found nothing to match. | No | | `VENUE_REJECTED` | The venue refused the order. | No | | `ORDER_OUTCOME_UNKNOWN` | The submission's outcome is unknown. Read the wallet's orders before placing again. | Maybe | | `CANCEL_OUTCOME_UNKNOWN` | Some venue may have cancelled. Read the wallet's orders. | Maybe | | `SIGNATURE_EXPIRED` | The signing window closed before a signature was accepted. Nothing was sent. | No | | `CLIENT_ORDER_ID_CONFLICT` | The `clientOrderId` names an order with different terms. | No | | `HYPERLIQUID_AGENT_MISSING` | Hyperliquid no longer lists this wallet's trading approval. Run `setup` again. | No |

## Other responses

- `400`: JSON `{"error": …}` for a request Kairos refuses before any signature, for example: - `wallet must be a 0x address`; `partner id header is not valid ASCII` or a malformed partner id - `unknown venue "x"`; `market is required`; `market must be venue:market_id, e.g. predictfun:1027194, or a Polymarket condition id`; `a Polymarket market is its condition id (0x + 64 hex)`; `a Predict.fun market is predictfun:<market_id>`; `a Hyperliquid market is hyperliquid:<market_id>`; `unknown venue in market "…"` - `unknown Polymarket market; for another venue use venue:market_id`; `unknown Predict.fun market`; `unknown Hyperliquid market or outcome: market=… outcome="…"` - `side must be buy or sell`; `price must be a decimal string`; `timeInForce must be GTC, GTD, FOK, FAK, or IOC`; `expiresAt is required for GTD` - Hyperliquid: `timeInForce on Hyperliquid must be GTC, FAK, or IOC`; `postOnly needs timeInForce GTC`; `clientOrderId must be 0x followed by 32 lowercase hex characters on this exchange`; price, size and minimum-notional refusals - `clientOrderId uses a reserved prefix` (ids starting `trade-plan:` are reserved); a blank or over-128-character `clientOrderId` - `pass either orderIds or all: true`; `at most 100 orderIds per cancel` A **plain-text** `400` (not JSON) means the body is not valid JSON.
- `401`: `send X-Kairos-Partner-Id or your API key (X-Client-Id, X-Api-Key, X-Api-Secret)` — no partner id and no API key (a delegation header never qualifies); `invalid API key`.
- `403`: Tenant refusals (`unknown partner id`; `this partner id is used with its API key; send the key instead of X-Kairos-Partner-Id`; `this API key lacks the trade:execute scope`; `the wallet API is not enabled for this account`), or a trading refusal: `API-key access to <venue> is disabled`, `this wallet is disabled`, `Hyperliquid trading is disabled for this wallet`, `external-signing execution is not enabled for this account`.
- `409`: - `this wallet is not set up yet; run a setup action first` — `cancel` and `login` need a wallet that has completed setup on at least one venue. - `this wallet is linked to a Kairos account; trade with it in the Kairos app`. - `client_order_id already names a different order` with `code` and `errorCode` = `CLIENT_ORDER_ID_CONFLICT`. - `code: POLYMARKET_V2_TRADING_DISABLED` — the market trades on a Polymarket contract version Kairos has not enabled yet.
- `413`: Plain text (`Failed to buffer the request body: length limit exceeded`) when the body is over the limit: 16 KiB for fee settlements, 64 KiB for trade plans and on-chain intents, the server-wide 2 MiB for actions. Above 64 KiB the answer may instead be `{"error": "request body too large"}`.
- `415`: Plain text — the request has no `Content-Type: application/json` header.
- `422`: **Plain text** when the body is valid JSON of the wrong shape: a missing required field, an unknown `type`, a non-UUID in `orderIds`, `price`/`size` sent as numbers, or `expiresAt` sent as a string. JSON when Hyperliquid refuses before signing: `not enough USDC on Hyperliquid for this order: it needs N, spot has S and perps can release W`.
- `429`: `rate limit exceeded for this endpoint; retry later`. The default limit is 120 requests per minute per client IP per path (1000 when `X-Client-Id` is sent), over a sliding 60 s window; `GET` and `POST` on one path share it. No `Retry-After` header is sent.
- `500`: `internal error`.
- `502`: A venue did not answer cleanly, e.g. `Hyperliquid is unavailable; retry shortly`, `Predict.fun is unavailable; retry shortly`. Nothing was signed; retry.
- `503`: `partner registry unavailable; retry shortly`; `rate limiter unavailable`; `<venue> is not available right now`; `no venue is available right now`; `wallet sign-in is not configured` (login); `API-key access to <venue> is unavailable`. Nothing was signed; retry later.
- `504`: `… did not answer in time; GET /v3/actions/{id} before retrying`. The action may have progressed: read it before starting another.

---

[API](https://skmtc.dev/kairos/apis/kairos-partner-api.md) - [All operations](https://skmtc.dev/kairos/apis/kairos-partner-api/llms.txt) - [OpenAPI document](https://skmtc.dev/kairos/apis/kairos-partner-api/revisions/f4baf0e325ef?raw)
