---
title: "Prepare a trade plan (one buy, optionally funded cross-chain)"
method: POST
path: "/v3/trade-plans"
tags: ["Trade plans (preview)"]
---

# Prepare a trade plan (one buy, optionally funded cross-chain)

`POST /v3/trade-plans`

## Request

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

Preview: available on staging (`https://staging-execution.kairos.trade`); production returns `503` until release.

A trade plan is **one buy order on one venue**, authorized up front in one batch of signatures, with an explicit fee ceiling (`tradeBudget`) and hard deadlines. When the venue account is short of collateral, the plan can also bridge **exactly the shortfall** from the same wallet's USDC on Polygon (`137`) or Arbitrum (`42161`) through gasless permits. Kairos runs funding first and submits the order only after the destination credit is final.

Use `/v3/actions` for sells, cancels, setup and login; trade plans are buy-only and the venue wallet must already be set up through a `setup` action.

**Flow.** Prepare → check `order` (the server may normalize it: canonical `outcome`; on Predict.fun a `GTC` order becomes a `GTD` order bounded by the venue session) → sign every `requests[]` entry whose `actionable` is `true` → `POST /v3/trade-plans/{id}/answers` and/or `POST /v3/trade-plans/{id}/execute` before `executeBefore` → poll `GET /v3/trade-plans/{id}`.

An unfunded plan needs 2 signatures (`plan`, `trade`); a funded plan adds `funding-1` and `funding-2`, signed on the **source** chain. Every signature must be a plain 65-byte ECDSA signature (low-s) from `wallet`.

**Idempotency.** `Idempotency-Key` is required. The same key with the same body returns the stored plan in its current state; with a different body it is `409`. `clientOrderId` is unique per wallet and venue across plans.

**Fees.** `tradeBudget.maxPlatformFeeBps` is the Kairos maximum plus the partner maximum (`partnerFee.maxBps`), never above 1000 bps (10%). Partner fees are configured by Kairos per partner and pinned into the plan the wallet signs. Fees are not deducted in the order: they accrue as debt and are paid through `/v3/fees/settlements`. When the wallet's debt is at or above the partner's threshold, prepare returns `409 FEE_SETTLEMENT_REQUIRED`.

## Headers

- `Idempotency-Key` string, required

## Request body

- WalletTradePlanPrepareRequest: Body of `POST /v3/trade-plans`. camelCase; unknown fields are refused.
  - `wallet` string, required: EVM address, `0x` + 40 hex.
  - `ctfDependencies` string[]: Polymarket only — ids of on-chain intents this buy depends on (e.g. a merge that frees collateral). At most 16, distinct.
  - `interactionMode` 'single_batch' | 'allow_followup', required: Both values currently behave the same — every request is a signature collected up front.
  - `submission` WalletTradePlanSubmission: Who submits each part. Defaults to Kairos for both. `trade` accepts only `kairos`.
    - `funding` 'kairos' | 'client': `client` — you POST the signed funding permits yourself (see `requests[].externalSubmission`) and report them.
    - `trade` 'kairos'
  - `order` WalletTradePlanOrderTerms, required: The order. Decimals must be **canonical** (`"0.5"`, not `"0.50"`; `"1"`, not `"1.0"`). `size` must match the venue's share lot exactly. Hyperliquid: whole-share `size`, at most 5 price decimals, `GTC`/`FAK`/`IOC` only. In a response this is the **effective** order — check it before signing: `outcome` is canonical and on Predict.fun a `GTC` order may come back as `GTD` with `expiresAt` set.
    - `market` string, required: `venue:market_id` (required form here) — `polymarket:0x<64-hex>`, `predictfun:<digits>`, `hyperliquid:<digits>`.
    - `outcome` string, required
    - `side` 'buy', required: Trade plans are buy-only.
    - `price` string, required: Canonical decimal, above 0 and below 1.
    - `size` string, required: Canonical decimal, shares.
    - `timeInForce` 'GTC' | 'GTD' | 'FOK' | 'FAK' | 'IOC', required
    - `postOnly` boolean, required: Only with `GTC` or `GTD`.
    - `clientOrderId` string, required: Required. Printable ASCII, unique per wallet and venue across plans; must not start with `trade-plan:`.
    - `expiresAt` string, date-time: Required exactly when `timeInForce` is `GTD`. RFC 3339, whole seconds, after `tradeSubmitBefore`.
  - `funding` WalletTradePlanFundingInput: Bridge the shortfall from the wallet's USDC on another chain. Gasless permits; Kairos quotes exactly the amount the plan needs. Routes: | Source | → Polymarket (pUSD, Polygon) | → Predict.fun (USDT, BNB Chain) | → Hyperliquid (spot USDC, mainnet) | |---|---|---|---| | Polygon USDC (`137`, `0x3c499c542cef5e3811e1192ce70d8cc03d5c3359`) | no | yes | yes | | Arbitrum USDC (`42161`, `0xaf88d065e77c8cc2239327c5edb3a432268e5831`) | yes | yes | yes | An unsupported route returns `503 no verified gasless funding route is available`.
    - `sourceChainId` '137' | '42161', required: Decimal chain id.
    - `sourceToken` string, required: That chain's native USDC address.
    - `maxAmountRaw` string, required: Most the wallet will spend on the source chain, bridge fees included (integer string, base units, > 0).
    - `maxGasCostRaw` string, required: Gas cap; routes are gasless, so `"0"` works.
    - `slippageBps` integer, required
  - `tradeSubmitBefore` string, date-time, required: Last moment Kairos may submit the order. RFC 3339 UTC, whole seconds, in the future and at most 5 minutes out.

## Response `200`

The prepared plan in `needs_signatures`. A replay with the same `Idempotency-Key` and body returns the stored plan in its current state.

- WalletTradePlan: A trade plan. Statuses: `needs_signatures` → `ready_to_execute` → (`/execute`) `funding_pending` → `trade_pending` → `completed`, with `reconciling` while an outcome is being confirmed. Terminal: `completed`, `expired`, `failed`, `funded_trade_failed`, `funded_trade_expired`. `audience` and `tradeExecutionRegion` are opaque values signed into the plan — do not interpret or change them.
  - `contractVersion` 1, required
  - `id` string, uuid, required
  - `planHash` string, required: Hash of the plan's terms; every answer must echo it.
  - `revision` string, required: Increases on every stored change (`"0"` at prepare).
  - `wallet` string, required: The signing wallet, lowercase.
  - `ctfDependencies` WalletCtfDependency[]
    - `intent` WalletOnchainDependency, required: A confirmed-before-me intent, pinned by hash.
      - `intentId` string, uuid, required
      - `intentHash` string, required
      - `account` string, required
      - `chainId` string, required
      - `effects` WalletOnchainEffect[], required
        - `token` union, required: A token, tagged by `kind`.
          - WalletOnchainTokenErc20
            - `kind` 'erc20', required
            - `contract` string, required
          - WalletOnchainTokenErc1155
            - `kind` 'erc1155', required
            - `contract` string, required
            - `tokenId` string, required: Decimal token id.
        - `direction` 'debit' | 'credit', required
        - `amountRaw` string, required
    - `anticipatedCreditRaw` string, required
  - `partnerId` string, required
  - `audience` string, required: Opaque signed field.
  - `interactionMode` 'single_batch' | 'allow_followup', required
  - `submission` WalletTradePlanSubmission, required: Who submits each part. Defaults to Kairos for both. `trade` accepts only `kairos`.
    - `funding` 'kairos' | 'client': `client` — you POST the signed funding permits yourself (see `requests[].externalSubmission`) and report them.
    - `trade` 'kairos'
  - `status` 'needs_signatures' | 'ready_to_execute' | 'funding_pending' | 'trade_pending' | 'reconciling' | 'completed' | 'expired' | 'failed' | 'funded_trade_failed' | 'funded_trade_expired', required
  - `venue` 'polymarket' | 'predictfun' | 'hyperliquid', required: Venue id. `predict.fun` is accepted as an input alias of `predictfun`.
  - `tradeExecutionRegion` string, required: Opaque signed field.
  - `order` WalletTradePlanOrderTerms, required: The order. Decimals must be **canonical** (`"0.5"`, not `"0.50"`; `"1"`, not `"1.0"`). `size` must match the venue's share lot exactly. Hyperliquid: whole-share `size`, at most 5 price decimals, `GTC`/`FAK`/`IOC` only. In a response this is the **effective** order — check it before signing: `outcome` is canonical and on Predict.fun a `GTC` order may come back as `GTD` with `expiresAt` set.
    - `market` string, required: `venue:market_id` (required form here) — `polymarket:0x<64-hex>`, `predictfun:<digits>`, `hyperliquid:<digits>`.
    - `outcome` string, required
    - `side` 'buy', required: Trade plans are buy-only.
    - `price` string, required: Canonical decimal, above 0 and below 1.
    - `size` string, required: Canonical decimal, shares.
    - `timeInForce` 'GTC' | 'GTD' | 'FOK' | 'FAK' | 'IOC', required
    - `postOnly` boolean, required: Only with `GTC` or `GTD`.
    - `clientOrderId` string, required: Required. Printable ASCII, unique per wallet and venue across plans; must not start with `trade-plan:`.
    - `expiresAt` string, date-time: Required exactly when `timeInForce` is `GTD`. RFC 3339, whole seconds, after `tradeSubmitBefore`.
  - `tradingWallet` string, required: Where the collateral must sit — Polymarket's trading wallet, or the wallet itself on Predict.fun and Hyperliquid.
  - `tradePayloadDigest` string, required: Signing digest of the `trade` request.
  - `tradeBudget` WalletTradePlanBudget, required: The collateral ceiling the wallet authorizes. Raw amounts are integer strings in collateral base units.
    - `collateral` union, required: A collateral asset, tagged by `kind`.
      - WalletCollateralEvm
        - `kind` 'evm', required
        - `chainId` string, required: Decimal chain id.
        - `token` string, required: Token address.
      - WalletCollateralHypercore
        - `kind` 'hypercore', required
        - `environment` 'mainnet' | 'testnet', required
        - `ledger` 'spot', required
        - `token` 'USDC', required
    - `decimals` integer, required: 6 (pUSD, Hyperliquid USDC) or 18 (BNB Chain USDT).
    - `notionalRaw` string, required: Order notional.
    - `maxCollateralFeeRaw` string, required: Upper bound on fees: `notional × maxPlatformFeeBps / 10000` plus the venue's own fee bound, rounded up.
    - `maxPlatformFeeBps` integer, required: Kairos maximum plus `partnerFee.maxBps`. Prepare is refused (`422`) above 1000 (10%).
    - `maxDebitRaw` string, required: `notionalRaw + maxCollateralFeeRaw` — the most this plan can take from the trading account.
    - `existingSpendableRaw` string, required: Collateral already available at the venue, net of other plans' holds, when prepared (a snapshot, not a reservation).
    - `partnerFee` WalletPartnerFeeTerms: The partner fee this plan pins, signed by the wallet as part of the plan. Partner fee schedules are configured by Kairos for each partner — a default, optionally per venue, optionally per user — as up to 16 bands selected by order notional (USD) and the user's 30-day trading volume; each band has a maker and a taker shape. The band in force when the plan is prepared is pinned here; later schedule changes do not affect the plan. At fill time the partner rate is `maker` or `taker` evaluated at the fill price, charged on notional. Combined with the Kairos rate it never exceeds `tradeBudget.maxPlatformFeeBps` (at most 1000 bps, 10%). Polymarket and Predict.fun only.
      - `scheduleId` string, uuid, required
      - `maxBps` integer, required: The highest rate either shape reaches.
      - `maker` union, required: How a partner fee rate varies with the fill price `p` (0–1). Rates are integer bps, 0–1000.
        - WalletFeeShapeFlat: The same rate at every price.
          - `model` 'flat', required
          - `bps` integer, required
        - WalletFeeShapeCurve: Linear between points (2–32, prices strictly increasing within 0–1), flat beyond the end points.
          - `model` 'curve', required
          - `points` object[], required
            - `price` string, required: Decimal string, 0–1.
            - `feeBps` integer, required
        - WalletFeeShapeParabolic: `edgeBps + 4·(centerBps − edgeBps)·p·(1 − p)` — `edgeBps` at prices 0 and 1, `centerBps` at 0.5 (may be lower than the edge).
          - `model` 'parabolic', required
          - `edgeBps` integer, required
          - `centerBps` integer, required
      - `taker` union, required: How a partner fee rate varies with the fill price `p` (0–1). Rates are integer bps, 0–1000.
        - WalletFeeShapeFlat: The same rate at every price.
          - `model` 'flat', required
          - `bps` integer, required
        - WalletFeeShapeCurve: Linear between points (2–32, prices strictly increasing within 0–1), flat beyond the end points.
          - `model` 'curve', required
          - `points` object[], required
            - `price` string, required: Decimal string, 0–1.
            - `feeBps` integer, required
        - WalletFeeShapeParabolic: `edgeBps + 4·(centerBps − edgeBps)·p·(1 − p)` — `edgeBps` at prices 0 and 1, `centerBps` at 0.5 (may be lower than the edge).
          - `model` 'parabolic', required
          - `edgeBps` integer, required
          - `centerBps` integer, required
  - `funding` WalletTradePlanFunding, required: The funding quote. Raw amounts are integer strings in base units.
    - `sourceChainId` string, required
    - `sourceToken` string, required
    - `sourceDecimals` integer, required
    - `quoteId` string, required: Opaque quote id.
    - `providerRequestId` string, required: The bridge provider's request id.
    - `amountInRaw` string, required: Source debit including bridge fees (at most `maxAmountRaw`).
    - `maxGasCostRaw` string, required: Always `"0"`.
    - `destination` union, required: A collateral asset, tagged by `kind`.
      - WalletCollateralEvm
        - `kind` 'evm', required
        - `chainId` string, required: Decimal chain id.
        - `token` string, required: Token address.
      - WalletCollateralHypercore
        - `kind` 'hypercore', required
        - `environment` 'mainnet' | 'testnet', required
        - `ledger` 'spot', required
        - `token` 'USDC', required
    - `destinationDecimals` integer, required
    - `recipient` string, required: Equals `tradingWallet`.
    - `refundRecipient` string, required: Equals `wallet`; the bridge provider returns a failed bridge here, on the source chain.
    - `requiredArrivalRaw` string, required: The shortfall — `maxDebitRaw` minus what is already spendable.
    - `minimumArrivalRaw` string, required: Guaranteed floor, at least `requiredArrivalRaw`. Arrival below it fails the plan (`FUNDING_SHORTFALL`).
    - `expectedArrivalRaw` string, required: Estimate.
    - `quoteExpiresAt` string, date-time, required
    - `fundingSubmitBefore` string, date-time, required: Deadline for the funding permits; also the plan's `executeBefore`.
    - `maxAmountRaw` string, required
    - `slippageBps` integer, required
  - `createdAt` string, date-time, required
  - `executeBefore` string, date-time, required: Deadline for answers and `/execute` — `funding.fundingSubmitBefore` when funded, else `tradeSubmitBefore`.
  - `tradeSubmitBefore` string, date-time, required
  - `requests` WalletTradePlanRequest[], required
    - `id` string, required
    - `purpose` 'plan' | 'trade' | 'funding', required
    - `kind` 'signature', required
    - `signer` string, required: Equals `wallet`.
    - `chainId` string, nullable, required: Chain to sign on, hex.
    - `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
    - `requestHash` string, required: Hash of the canonical request; pinned by `planHash`.
    - `collectAfter` string[], required
    - `executeAfter` string[], required: Requests that must run before this one (e.g. `trade` after the funding ids).
    - `externalSubmission` WalletTradePlanExternalSubmission: Client-owned funding only: POST the signed permit here yourself, `funding-1` first, with `{kind, requestId, api}` as the body and the signature as the `signature` query parameter.
      - `endpoint` string, uri, required
      - `method` 'POST', required
      - `kind` 'multicall-authorization' | 'eip3009', required
      - `requestId` string, required: Equals `funding.providerRequestId`.
      - `api` string, nullable, required: The Relay API to call; `swap` on every current route.
    - `status` 'ready' | 'accepted' | 'confirmed', required: `ready` → `accepted` (signature stored) → `confirmed` (executed).
    - `actionable` boolean, required: `true` when the client should sign this request now.
  - `execution` WalletTradePlanExecution, required: Progress of a committed plan.
    - `committed` boolean, required
    - `phase` 'collecting' | 'funding' | 'trade' | 'finished', required
    - `fundingStatus` 'not_required' | 'not_started' | 'pending' | 'awaiting_client' | 'unknown' | 'confirmed', required: `unknown` while the bridge is in flight or uncertain.
    - `tradeStatus` 'not_started' | 'unknown' | 'accepted' | 'failed' | 'expired', required: `accepted` — the venue acknowledged the order (not necessarily filled).
    - `movementId` string: The bridge provider's request id, once known.
    - `orderId` string: Kairos order id.
    - `venueOrderId` string
    - `venueOrderStatus` string
    - `confirmedArrivalRaw` string: Collateral confirmed at the destination.
  - `error` WalletPlanErrorView, required: Why a plan is in flight or stopped (returned with HTTP `200`). | `code` | Meaning | |---|---| | `OUTCOME_UNKNOWN` | Funding or the trade is in flight; keep polling (`nextAction: reconcile`). | | `PLAN_EXPIRED` | Not executed before `executeBefore`. | | `TRADE_EXPIRED` | The order was not submitted by `tradeSubmitBefore`, or funding arrived after it. | | `FUNDING_SHORTFALL` | Less than `minimumArrivalRaw` arrived. | | `COLLATERAL_CHANGED` | The balance fell below `maxDebitRaw` before submission. | | `VENUE_REJECTED` | The venue refused the signed order. | | `PREFLIGHT_REFUSED` | A pre-submission check refused the order. | | `NATIVE_NOT_DISPATCHED` | The trade authorization expired before submission. | `nextAction` is `reconcile` (keep polling) or `inspect_funds` (terminal: check where the collateral is — bridged funds stay in `tradingWallet`).
    - `code` 'OUTCOME_UNKNOWN' | 'PLAN_EXPIRED' | 'TRADE_EXPIRED' | 'FUNDING_SHORTFALL' | 'COLLATERAL_CHANGED' | 'VENUE_REJECTED' | 'PREFLIGHT_REFUSED' | 'NATIVE_NOT_DISPATCHED', required
    - `message` string, required
    - `nextAction` 'reconcile' | 'inspect_funds', required

## Other responses

- `400`: `Idempotency-Key is required`; `invalid Idempotency-Key`; `invalid trade plan: <reason>` (e.g. `funded trade plans currently accept buys`, a non-canonical decimal such as `"0.50"`, a deadline outside the 5-minute window); market format errors; `size must align exactly with the venue share lot`; `unknown CTF prerequisite`; `CTF prerequisite belongs to another wallet or audience`; `unsupported source chain`; `clientOrderId uses a reserved prefix`. Plain text for malformed JSON.
- `401`: Tenant failures (`send X-Kairos-Partner-Id or your API key (X-Client-Id, X-Api-Key, X-Api-Secret)`, `invalid API key`), or the wallet proof failed: - `malformed sign-in token` — not `SIWE <token>` as issued, over 4096 bytes, or an invalid lifetime - `sign-in token was issued for another partner or node` — issued for another partner or environment - `sign-in token has expired; run a login action` - `sign-in token signature is not from its wallet` - `Missing authorization` — no `Authorization` header and no API key to prove the wallet; `Invalid API credentials`, `API credential has been revoked` or `Invalid token` when the proving credential is not `SIWE` and fails
- `403`: `API key missing trade:execute scope`; `wallet partner is disabled`; `API-key access to <venue> is disabled`; `external-signing execution is not enabled for this account`; or a tenant refusal (see `POST /v3/actions`).
- `404`: `wallet not found` — the wallet has no Kairos account, or the caller does not own it; `trade plan not found` — `wallet` differs from the wallet the API key is bound to.
- `409`: - `FEE_SETTLEMENT_REQUIRED` (structured body below) — settle through `/v3/fees/settlements`, wait for `confirmed`, then prepare again. - `Idempotency-Key already names a different request`; `clientOrderId already names another trade plan`. - `set up the venue wallet before preparing a trade plan`; `set up the Hyperliquid wallet before preparing a trade plan`; `renew the Predict.fun wallet session` — run a `setup` action first. - `collateral is short; supply an explicit funding source`. - `wait for canonical CTF prerequisite confirmation`; `trade plan changed; read it before retrying`.
- `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`: JSON `the combined Kairos and partner fee exceeds the 10% platform ceiling`; or plain text for a body of the wrong shape (unknown or missing fields, wrong types).
- `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`.
- `503`: Production: `trade plans are disabled` until the lane is released. Staging: a dependency is unavailable — `venue is unavailable for trade plans`; `wallet funding is unavailable`; `no verified gasless funding route is available` (unsupported source → destination pair, e.g. Polygon USDC → Polymarket); `confirmed Polymarket fee schedule is unavailable`; `partner fee policy is unavailable`; `trade plan fee storage is unavailable`; `Polygon balance is unavailable`; `canonical CTF collateral snapshot is unavailable`; `partner registry unavailable; retry shortly`; `rate limiter unavailable`.

---

[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)
