---
title: "Submit your externally-signed order signature (step 2 of 2)"
method: POST
path: "/v2/orders/submit"
tags: ["Orders"]
---

# Submit your externally-signed order signature (step 2 of 2)

`POST /v2/orders/submit`

## Request

- Base URL: `https://execution.kairos.trade`
- URL: `https://execution.kairos.trade/v2/orders/submit`
- Auth: API key in header `X-Client-Id` and API key in header `X-Api-Key` and API key in header `X-Api-Secret`

Completes the self-custody order flow started by `POST /v2/orders/intent`: hand back `payload_id` and the 65-byte signature you produced over the returned digest. The server atomically claims the stored intent with a Redis `GETDEL` (single-use — a replayed or concurrent second submit for the same `payload_id` gets `400 payload_id not found or expired`), recomputes the EIP-712 digest itself (never trusting anything in this request beyond the signature), `ecrecover`s the signer, confirms it matches the intent's declared owner AND is a wallet registered to the authenticated caller, then forwards the assembled signed order to the venue CLOB.
**The claim happens BEFORE any verification**, so the `payload_id` is consumed even when the request goes on to fail — a bad signature burns the intent.
**Synchronous result.** `status` is the venue's immediate result string, passed through verbatim. For a marketable order that crossed immediately this IS your fill confirmation; for a resting order it confirms the order is live. `exchange_order_id` is the venue's handle — alongside the internal `order_id` it is the durable identifier for cancels.
**Async fills.** A resting order's later fills are NOT returned here — they stream over your authenticated `/ws` connection as `partially_filled` / `filled` / `status_changed` events (deduped on the venue trade id; automatic reconciliation ensures fills are never silently lost, just occasionally a beat slower without `/ws`). Poll `GET /orders/{order_id}` if you are not holding a WebSocket open.
**A persistence failure does NOT fail the request.** If the order lands at the venue but the Kairos row cannot be written, the `200` is still returned and the failure is logged — treat the venue as authoritative.
**Retries.** The claimed intent is single-use and deleted on claim — a retry or a reprice requires a brand-new `POST /v2/orders/intent` (fresh `salt`/`timestamp_ms` → fresh digest → fresh signature). There is no silent server-side re-sign. The rate-limit slot was already charged at `/intent` and is not charged again here.
**Auth & scope.** Requires the fastlane allowlist (re-checked here, independent of the check at `/intent`) and `trade:execute` for the provider recorded on the stored intent. No service token / CSRF token is required.

## Request body

- OrderSubmitSignedRequest: Body for `POST /v2/orders/submit`.
  - `payload_id` string, uuid, required: The `payload_id` returned by `POST /v2/orders/intent`. Single-use — claimed atomically on the first `/submit` call.
  - `signature_hex` string, required: 0x-prefixed 65-byte secp256k1 signature (`r || s || v`) over `eip712_digest_hex`.

## Response `200`

Venue's synchronous placement result.

- OrderSubmitSignedResponse: Response for `POST /v2/orders/submit` (and the equivalent one-RTT WSS `submit_signed_order` command).
  - `order_id` string, uuid, required: Internal Kairos order id — usable for `GET /orders/{order_id}` and the cancel endpoints.
  - `exchange_order_id` string, nullable: Venue-assigned order handle. An EMPTY venue id is treated as a failed submission and returned as `400 venue returned an empty order id; order not tracked`, so on a `200` this is in practice always populated; it is `null` only on the idempotent-replay response path.
  - `status` string, required: The venue's immediate result string, passed through verbatim — Kairos does not normalize or validate it. Polymarket's observed values are `matched`, `live` and `delayed`; `unmatched` appears on the fill-polling path. Treat this as an open string, not a closed enum.

## Other responses

- `400`: Exact `error` strings: - `payload_id not found or expired` — wrong / stale / already-claimed id - `payload_id expired` — the id resolved but its wall-clock `expires_at_us` has passed - `payload_id does not belong to authenticated user` - `invalid signature hex` — not decodable hex - `invalid signature: signature must be 65 bytes (got N)` / `invalid signature: failed to parse signature: …` / `invalid signature: ecrecover failed: …` - `signature recovered 0x… does not match owner 0x…` — usually the EIP-191-vs-raw-digest mistake - `recovered signer is not a registered wallet for this user` — also returned when the wallet lookup itself errors - `polymarket credentials not configured` - Predict.fun only: `predict.fun trading is not enabled for this account — no venue signing wallet is registered`; `this predict.fun account is a Kernel smart account, which the external-signing lane does not support yet …`; `owner_address is not your registered predict.fun trading wallet` - `venue returned an empty order id; order not tracked` - Under `FASTLANE_ADMISSION=enforce`, the re-run admission rejections (same set as `/intent`, minus the rate limit).
- `401`: No valid credential presented — missing/invalid API-key headers or an invalid/expired session token.
- `403`: `external-signing execution is not enabled for this account`; `external-signing authorization check failed`; `API key missing trade:execute scope`; `API-key access to <provider> is disabled`; or (enforce-mode admission) `Trading not enabled for user`.
- `500`: `internal server error` — Redis claim failure, stored intent no longer deserializes or rebuilds, the recomputed digest differs from the stored one (refused), or an internal failure assembling the order. The order may already be LIVE on the venue — verify via `GET /orders/{order_id}` or `/ws` before retrying.
- `502`: `CLOB error: <venue message>` — the venue rejected the signed order (balance/allowance/tick); the venue's message is passed through verbatim.
- `503`: `API-key access to <provider> is unavailable` (access cache read failed); `verification temporarily unavailable` (an EIP-1271 RPC timeout — reachable only on a lane that accepted a non-EOA signature type, so not expected today); or an enforce-mode circuit-breaker rejection. NOTE: the `payload_id` was already consumed, so a retry needs a fresh intent.

---

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