Build an unsigned EIP-712 order payload for self-custody signing (step 1 of 2)
The first step of the “external-signing” / bring-your-own-key institutional lane (Polymarket and Predict.fun, EOA only): the server builds the canonical EIP-712 typed-data message for the order you describe and stashes it single-use in Redis under a fresh payload_id (60 s TTL). NO order is placed and NOTHING is signed by Kairos — the response gives you a 32-byte digest (or the full typed-data payload) to sign yourself, off your own key, then hand back to POST /v2/orders/submit. This removes the custodial signing hop from the order path for approved market makers / institutional desks.
Venue. provider selects the venue: polymarket (default) or predictfun. On Predict.fun you supply nothing venue-specific — the server resolves the market’s isYieldBearing/isNegRisk pair (which selects one of four verifying contracts), its feeRateBps (part of the signed struct) and its price tick from authoritative metadata, and cross-checks your intent.neg_risk against the market. Predict.fun’s signed Order struct is NOT the same shape as Polymarket’s.
market_id and outcome are METADATA ONLY — they are never part of the signed digest, so they can’t be tampered with post-signing — but both are REQUIRED: the server resolves intent.token_id against market_id and rejects a mismatch, a blank market_id, or a missing outcome with 400.
Recommendation: omit intent.salt and intent.timestamp_ms and let the server stamp them — this guarantees a fresh digest per intent. Only set them yourself if you need to reproduce a digest deterministically (or if you’re building the order fully client-side over the one-RTT WebSocket submit_signed_order command instead of this two-RTT REST flow, which REQUIRES you to set both — and is Polymarket-only).
Allowlist. Restricted to User.executionFastlaneEnabled accounts, flipped by a Kairos admin during onboarding — an unapproved account gets 403 external-signing execution is not enabled for this account, never a bare 401, so a valid-but-unapproved caller gets an unambiguous signal. The gate fails closed on a DB error, which is also a 403 (external-signing authorization check failed) rather than a 500. The flag is read through a short-lived cache, so a revocation takes effect within a couple of seconds rather than instantly.
Only signature_type: 0 (EOA) is accepted on this lane — Poly1271 / smart-wallet signature types are rejected at intake (400) rather than failing later at /submit with an opaque signer mismatch, and signer_address (if sent) must equal owner_address.
Admission. The same size/price bounds, time-in-force and post-only capability gates, circuit breakers and per-user order rate limit that guard POST /orders are applied here, gated by the FASTLANE_ADMISSION deployment setting (off | shadow | enforce). Under off/shadow a narrower gate still runs: the execution circuit breakers and the post-only capability check. A rate-limit slot is charged HERE, on /intent, not on /submit.
Errors are the minimal {"error": "..."} shape on this whole lane — no error_details, no code.
Auth & scope. Requires trade:execute for the selected provider, AND the fastlane allowlist above. Unlike POST /orders, this endpoint does NOT require a service token / CSRF token — a session JWT alone is sufficient.
curl --request POST \
--url https://execution.kairos.trade/v2/orders/intent \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <api-key>' \
--header 'X-Api-Secret: <api-key>' \
--header 'X-Client-Id: <api-key>' \
--data '
{
"intent": {
"token_id": "71360012345678901234567890123456789012345678901234567890123456",
"side": "buy",
"price": "0.52",
"size": "100",
"time_in_force": "GTC",
"neg_risk": true,
"owner_address": "0x0000000000000000000000000000000000dEaD",
"signature_type": 0,
"post_only": false,
"expiration_unix_secs": 123,
"signer_address": "<string>",
"salt": "<string>",
"timestamp_ms": "<string>"
},
"provider": "polymarket",
"market_id": "<string>",
"outcome": "Yes"
}
'import requests
url = "https://execution.kairos.trade/v2/orders/intent"
payload = {
"intent": {
"token_id": "71360012345678901234567890123456789012345678901234567890123456",
"side": "buy",
"price": "0.52",
"size": "100",
"time_in_force": "GTC",
"neg_risk": True,
"owner_address": "0x0000000000000000000000000000000000dEaD",
"signature_type": 0,
"post_only": False,
"expiration_unix_secs": 123,
"signer_address": "<string>",
"salt": "<string>",
"timestamp_ms": "<string>"
},
"provider": "polymarket",
"market_id": "<string>",
"outcome": "Yes"
}
headers = {
"X-Client-Id": "<api-key>",
"X-Api-Key": "<api-key>",
"X-Api-Secret": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Client-Id': '<api-key>',
'X-Api-Key': '<api-key>',
'X-Api-Secret': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
intent: {
token_id: '71360012345678901234567890123456789012345678901234567890123456',
side: 'buy',
price: '0.52',
size: '100',
time_in_force: 'GTC',
neg_risk: true,
owner_address: '0x0000000000000000000000000000000000dEaD',
signature_type: 0,
post_only: false,
expiration_unix_secs: 123,
signer_address: '<string>',
salt: '<string>',
timestamp_ms: '<string>'
},
provider: 'polymarket',
market_id: '<string>',
outcome: 'Yes'
})
};
fetch('https://execution.kairos.trade/v2/orders/intent', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://execution.kairos.trade/v2/orders/intent",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'intent' => [
'token_id' => '71360012345678901234567890123456789012345678901234567890123456',
'side' => 'buy',
'price' => '0.52',
'size' => '100',
'time_in_force' => 'GTC',
'neg_risk' => true,
'owner_address' => '0x0000000000000000000000000000000000dEaD',
'signature_type' => 0,
'post_only' => false,
'expiration_unix_secs' => 123,
'signer_address' => '<string>',
'salt' => '<string>',
'timestamp_ms' => '<string>'
],
'provider' => 'polymarket',
'market_id' => '<string>',
'outcome' => 'Yes'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Api-Key: <api-key>",
"X-Api-Secret: <api-key>",
"X-Client-Id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://execution.kairos.trade/v2/orders/intent"
payload := strings.NewReader("{\n \"intent\": {\n \"token_id\": \"71360012345678901234567890123456789012345678901234567890123456\",\n \"side\": \"buy\",\n \"price\": \"0.52\",\n \"size\": \"100\",\n \"time_in_force\": \"GTC\",\n \"neg_risk\": true,\n \"owner_address\": \"0x0000000000000000000000000000000000dEaD\",\n \"signature_type\": 0,\n \"post_only\": false,\n \"expiration_unix_secs\": 123,\n \"signer_address\": \"<string>\",\n \"salt\": \"<string>\",\n \"timestamp_ms\": \"<string>\"\n },\n \"provider\": \"polymarket\",\n \"market_id\": \"<string>\",\n \"outcome\": \"Yes\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Client-Id", "<api-key>")
req.Header.Add("X-Api-Key", "<api-key>")
req.Header.Add("X-Api-Secret", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://execution.kairos.trade/v2/orders/intent")
.header("X-Client-Id", "<api-key>")
.header("X-Api-Key", "<api-key>")
.header("X-Api-Secret", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"intent\": {\n \"token_id\": \"71360012345678901234567890123456789012345678901234567890123456\",\n \"side\": \"buy\",\n \"price\": \"0.52\",\n \"size\": \"100\",\n \"time_in_force\": \"GTC\",\n \"neg_risk\": true,\n \"owner_address\": \"0x0000000000000000000000000000000000dEaD\",\n \"signature_type\": 0,\n \"post_only\": false,\n \"expiration_unix_secs\": 123,\n \"signer_address\": \"<string>\",\n \"salt\": \"<string>\",\n \"timestamp_ms\": \"<string>\"\n },\n \"provider\": \"polymarket\",\n \"market_id\": \"<string>\",\n \"outcome\": \"Yes\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://execution.kairos.trade/v2/orders/intent")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Client-Id"] = '<api-key>'
request["X-Api-Key"] = '<api-key>'
request["X-Api-Secret"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"intent\": {\n \"token_id\": \"71360012345678901234567890123456789012345678901234567890123456\",\n \"side\": \"buy\",\n \"price\": \"0.52\",\n \"size\": \"100\",\n \"time_in_force\": \"GTC\",\n \"neg_risk\": true,\n \"owner_address\": \"0x0000000000000000000000000000000000dEaD\",\n \"signature_type\": 0,\n \"post_only\": false,\n \"expiration_unix_secs\": 123,\n \"signer_address\": \"<string>\",\n \"salt\": \"<string>\",\n \"timestamp_ms\": \"<string>\"\n },\n \"provider\": \"polymarket\",\n \"market_id\": \"<string>\",\n \"outcome\": \"Yes\"\n}"
response = http.request(request)
puts response.read_body{
"payload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"unsigned_payload": {
"domain": {
"name": "Polymarket CTF Exchange",
"version": "2",
"chain_id": 137,
"verifying_contract": "0xE111180000d2663C0091e4f400237545B87B996B"
},
"primary_type": "Order",
"types": {},
"message": {},
"eip712_digest_hex": "0x9a1c2b3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff0"
},
"eip712_digest_hex": "<string>",
"expires_at_us": 123
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Unauthorized"
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Insufficient scope",
"code": "<string>"
}Authorizations
Credential client id (kairos_ck_...). Must be sent together with X-Api-Key and X-Api-Secret.
64-char hex API key.
64-char hex API secret.
Body
Body for POST /v2/orders/intent.
The unsigned order intent — intent field of OrderIntentRequest, mirrored inside the one-RTT WSS submit_signed_order command.
Show child attributes
Show child attributes
Target venue for the external-signing lane. Only these two venues are implemented; Kalshi and Solana variants exist in the internal types but are not reachable on /v2/orders/*. The one-RTT WebSocket submit_signed_order command and the whole /v2/onchain/* lane are Polymarket-only and have no provider field.
polymarket, predictfun "polymarket"
Condition id — METADATA ONLY, not part of the signed digest, but REQUIRED in practice. The server resolves intent.token_id against it and rejects a blank value with 400 market_id is required for external orders, a token that does not belong to it with 400 token_id does not belong to the supplied market_id. Max 256 chars.
REQUIRED — outcome label (e.g. "Yes"/"No"); metadata only, not signed. A missing/empty value is 400 outcome is required for external orders, and a label that does not match the resolved token is 400 token_id does not match the supplied outcome. Max 64 chars.
"Yes"
Response
Unsigned EIP-712 payload + digest. Sign eip712_digest_hex as a raw 32-byte hash, or run unsigned_payload through eth_signTypedData — both yield an identical signature.
Response for POST /v2/orders/intent.
Single-use handle for this stored intent. Pass back to POST /v2/orders/submit.
Full EIP-712 typed-data payload — pass directly to eth_signTypedData as an alternative to raw-hash-signing eip712_digest_hex.
Show child attributes
Show child attributes
Convenience copy of unsigned_payload.eip712_digest_hex — the digest to sign.
Wall-clock expiry, UNIX microseconds (60 s out by default, EXTERNAL_SIGNING_INTENT_TTL_SECS). The stored intent is claimed with an atomic Redis GETDEL at the very top of /submit, BEFORE any signature verification — so it is consumed by ANY submit attempt, not only a successful one. A /submit that fails verification burns the payload_id; retrying needs a fresh POST /v2/orders/intent.
Was this page helpful?
curl --request POST \
--url https://execution.kairos.trade/v2/orders/intent \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <api-key>' \
--header 'X-Api-Secret: <api-key>' \
--header 'X-Client-Id: <api-key>' \
--data '
{
"intent": {
"token_id": "71360012345678901234567890123456789012345678901234567890123456",
"side": "buy",
"price": "0.52",
"size": "100",
"time_in_force": "GTC",
"neg_risk": true,
"owner_address": "0x0000000000000000000000000000000000dEaD",
"signature_type": 0,
"post_only": false,
"expiration_unix_secs": 123,
"signer_address": "<string>",
"salt": "<string>",
"timestamp_ms": "<string>"
},
"provider": "polymarket",
"market_id": "<string>",
"outcome": "Yes"
}
'import requests
url = "https://execution.kairos.trade/v2/orders/intent"
payload = {
"intent": {
"token_id": "71360012345678901234567890123456789012345678901234567890123456",
"side": "buy",
"price": "0.52",
"size": "100",
"time_in_force": "GTC",
"neg_risk": True,
"owner_address": "0x0000000000000000000000000000000000dEaD",
"signature_type": 0,
"post_only": False,
"expiration_unix_secs": 123,
"signer_address": "<string>",
"salt": "<string>",
"timestamp_ms": "<string>"
},
"provider": "polymarket",
"market_id": "<string>",
"outcome": "Yes"
}
headers = {
"X-Client-Id": "<api-key>",
"X-Api-Key": "<api-key>",
"X-Api-Secret": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Client-Id': '<api-key>',
'X-Api-Key': '<api-key>',
'X-Api-Secret': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
intent: {
token_id: '71360012345678901234567890123456789012345678901234567890123456',
side: 'buy',
price: '0.52',
size: '100',
time_in_force: 'GTC',
neg_risk: true,
owner_address: '0x0000000000000000000000000000000000dEaD',
signature_type: 0,
post_only: false,
expiration_unix_secs: 123,
signer_address: '<string>',
salt: '<string>',
timestamp_ms: '<string>'
},
provider: 'polymarket',
market_id: '<string>',
outcome: 'Yes'
})
};
fetch('https://execution.kairos.trade/v2/orders/intent', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://execution.kairos.trade/v2/orders/intent",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'intent' => [
'token_id' => '71360012345678901234567890123456789012345678901234567890123456',
'side' => 'buy',
'price' => '0.52',
'size' => '100',
'time_in_force' => 'GTC',
'neg_risk' => true,
'owner_address' => '0x0000000000000000000000000000000000dEaD',
'signature_type' => 0,
'post_only' => false,
'expiration_unix_secs' => 123,
'signer_address' => '<string>',
'salt' => '<string>',
'timestamp_ms' => '<string>'
],
'provider' => 'polymarket',
'market_id' => '<string>',
'outcome' => 'Yes'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Api-Key: <api-key>",
"X-Api-Secret: <api-key>",
"X-Client-Id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://execution.kairos.trade/v2/orders/intent"
payload := strings.NewReader("{\n \"intent\": {\n \"token_id\": \"71360012345678901234567890123456789012345678901234567890123456\",\n \"side\": \"buy\",\n \"price\": \"0.52\",\n \"size\": \"100\",\n \"time_in_force\": \"GTC\",\n \"neg_risk\": true,\n \"owner_address\": \"0x0000000000000000000000000000000000dEaD\",\n \"signature_type\": 0,\n \"post_only\": false,\n \"expiration_unix_secs\": 123,\n \"signer_address\": \"<string>\",\n \"salt\": \"<string>\",\n \"timestamp_ms\": \"<string>\"\n },\n \"provider\": \"polymarket\",\n \"market_id\": \"<string>\",\n \"outcome\": \"Yes\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Client-Id", "<api-key>")
req.Header.Add("X-Api-Key", "<api-key>")
req.Header.Add("X-Api-Secret", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://execution.kairos.trade/v2/orders/intent")
.header("X-Client-Id", "<api-key>")
.header("X-Api-Key", "<api-key>")
.header("X-Api-Secret", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"intent\": {\n \"token_id\": \"71360012345678901234567890123456789012345678901234567890123456\",\n \"side\": \"buy\",\n \"price\": \"0.52\",\n \"size\": \"100\",\n \"time_in_force\": \"GTC\",\n \"neg_risk\": true,\n \"owner_address\": \"0x0000000000000000000000000000000000dEaD\",\n \"signature_type\": 0,\n \"post_only\": false,\n \"expiration_unix_secs\": 123,\n \"signer_address\": \"<string>\",\n \"salt\": \"<string>\",\n \"timestamp_ms\": \"<string>\"\n },\n \"provider\": \"polymarket\",\n \"market_id\": \"<string>\",\n \"outcome\": \"Yes\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://execution.kairos.trade/v2/orders/intent")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Client-Id"] = '<api-key>'
request["X-Api-Key"] = '<api-key>'
request["X-Api-Secret"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"intent\": {\n \"token_id\": \"71360012345678901234567890123456789012345678901234567890123456\",\n \"side\": \"buy\",\n \"price\": \"0.52\",\n \"size\": \"100\",\n \"time_in_force\": \"GTC\",\n \"neg_risk\": true,\n \"owner_address\": \"0x0000000000000000000000000000000000dEaD\",\n \"signature_type\": 0,\n \"post_only\": false,\n \"expiration_unix_secs\": 123,\n \"signer_address\": \"<string>\",\n \"salt\": \"<string>\",\n \"timestamp_ms\": \"<string>\"\n },\n \"provider\": \"polymarket\",\n \"market_id\": \"<string>\",\n \"outcome\": \"Yes\"\n}"
response = http.request(request)
puts response.read_body{
"payload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"unsigned_payload": {
"domain": {
"name": "Polymarket CTF Exchange",
"version": "2",
"chain_id": 137,
"verifying_contract": "0xE111180000d2663C0091e4f400237545B87B996B"
},
"primary_type": "Order",
"types": {},
"message": {},
"eip712_digest_hex": "0x9a1c2b3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff0"
},
"eip712_digest_hex": "<string>",
"expires_at_us": 123
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Unauthorized"
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Insufficient scope",
"code": "<string>"
}{
"error": "Insufficient scope",
"code": "<string>"
}
