> ## 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.

# Partner Auth & Self-Serve Onboarding

> Authenticate a partner integration and onboard without an account manager: SIWE wallet login, self-serve signing-wallet registration, Polymarket credential provisioning, partner delegation, and readiness/balance introspection

This page covers the account and credential half of the external execution lane: how a wallet logs in, how signing wallets are registered and revoked, how Polymarket L2 credentials are self-provisioned without Turnkey, how a partner app is delegated authority, and how to read your own readiness. The order flow itself is in [Signing & Order Lifecycle](/external-execution/signing); where to connect is in [Regional Execution Nodes](/external-execution/regional-execution).

Every endpoint here is on the regional node's `/v2` prefix, like the order lane.

## Credentials you can present

The lane accepts three credential shapes, all resolved by the same auth layer:

| Credential | Send | Use it for |
| - | - | - |
| **Kairos JWT** | `Authorization: Bearer <jwt>` (REST) / `Sec-WebSocket-Protocol: authorization, Bearer.<jwt>` (WS) | Interactive and service sessions you already hold |
| **Scoped API key** | `X-Client-Id` + `X-Api-Key` + `X-Api-Secret` | Server-to-server; minted at onboarding, via [SIWE login](#siwe-wallet-login), or self-provisioned |
| **Delegation grant** | `X-Kairos-Delegation` + `X-Kairos-Delegation-Signature` | A partner app acting for a user that signed a scoped, expiring grant — see [Partner delegation](#partner-delegation) |

Any of these authenticates the lane, which is [open to any authenticated account](/external-execution/overview#access) — no allow-list approval. A credential never bypasses scope or signature checks, and authorization is re-evaluated per request rather than carried forward from a prior call. An API key additionally earns a [higher rate limit](/external-execution/overview#rate-limits).

## SIWE wallet login

```
POST /v2/auth/siwe/challenge
POST /v2/auth/siwe/verify
```

Log in with a wallet using [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) (Sign-In With Ethereum). On success Kairos mints a **scoped API key** bound to the user that already owns the wallet — no new token type, no Turnkey session, no admin step.

These two endpoints are **unauthenticated by design**: the wallet signature *is* the authentication. A credential is only minted for an address that is already [registered](/external-execution/overview#getting-started) to a Kairos account; account creation by wallet is out of scope.

### 1. Request a challenge

| Name | Type | Required | Description |
| - | - | - | - |
| `address` | string | Yes | The wallet address that will sign |

```bash theme={null}
curl -X POST https://eu-west-1-polymarket.executor.kairos.trade/v2/auth/siwe/challenge \
  -H "Content-Type: application/json" \
  -d '{ "address": "0xYourEOA" }'
```

```json theme={null}
{
  "nonce": "a1b2c3…",
  "message": "your-node.example wants you to sign in with your Ethereum account:\n0xYourEOA\n\nSign in to Kairos.\n\nURI: https://your-node.example\nVersion: 1\nChain ID: 137\nNonce: a1b2c3…\nIssued At: 2026-07-22T09:14:00Z\nExpiration Time: 2026-07-22T09:19:00Z",
  "expires_at_us": 1750000000000000
}
```

The `message` is the exact EIP-4361 string to sign — pass it through verbatim, do not reconstruct it. The challenge lives for **5 minutes** and the nonce is single-use. Only chain **137 (Polygon)** is accepted, the lane's chain.

### 2. Sign and verify

Sign `message` with `personal_sign` (EIP-191), then:

| Name | Type | Required | Description |
| - | - | - | - |
| `message` | string | Yes | The exact `message` returned by `/challenge` |
| `signature_hex` | string | Yes | `0x`-prefixed 65-byte `personal_sign` signature |

```bash theme={null}
curl -X POST https://eu-west-1-polymarket.executor.kairos.trade/v2/auth/siwe/verify \
  -H "Content-Type: application/json" \
  -d '{ "message": "<the exact string>", "signature_hex": "0x<65-byte sig>" }'
```

```json theme={null}
{
  "client_id": "kairos_ck_…",
  "api_key": "…",
  "api_secret": "…",
  "scopes": ["trade:execute", "trade:read", "position:read"],
  "address": "0xYourEOA"
}
```

Use the returned triple as `X-Client-Id` / `X-Api-Key` / `X-Api-Secret`. **The secret is shown once** — only hashes are stored server-side, so capture it now. The minted key is scoped to `trade:execute`, `trade:read` and `position:read`.

## Self-serve wallet registration

```
POST /v2/wallets/challenge
POST /v2/wallets/register
POST /v2/wallets/revoke
```

Kairos only accepts signatures that recover to a wallet **registered to your account**. Register, replace and revoke signing wallets yourself — no account-manager round-trip.

These endpoints are authenticated (JWT or API key): you are proving ownership of a wallet *and* attaching it to the authenticated account.

### Register

1. `POST /v2/wallets/challenge` with `{ "provider": "polymarket", "address": "0xYourEOA" }`. `provider` is optional and defaults to `polymarket`; supported values are `polymarket` (Polygon) and `predictfun` (BSC). The response returns `nonce`, `message` and `expires_at_us`.
2. Sign `message` with `personal_sign`. The message is the canonical form `Kairos external signer registration\nProvider: …\nAddress: …\nNonce: …`.
3. `POST /v2/wallets/register` with `{ provider, address, nonce, signature_hex, message }`.

```json theme={null}
{ "provider": "polymarket", "address": "0xyoureoa", "status": "active" }
```

The challenge is single-use (claimed atomically on submit) and bound to your user id. If the address is already registered to **another** account, registration is rejected with `409 this address is already registered to another account` rather than silently reassigned. Registering again for the same provider simply replaces the stored address.

### Revoke

`POST /v2/wallets/revoke` with `{ "provider": "polymarket", "address": "0xYourEOA" }` returns `{ "revoked": true }`. Revocation is **immediate and load-bearing**: a revoked wallet stops resolving for order submission, SIWE login and credential provisioning at once. If you revoke the only registered wallet for a provider, that provider's order lane has nothing it will accept until you register another.

## Polymarket credential provisioning

```
POST /v2/credentials/polymarket/intent
POST /v2/credentials/polymarket/submit
```

Polymarket order placement and cancellation need L2 API credentials (`apiKey` / `secret` / `passphrase`), normally derived from a signed L1 auth message. On this lane you provision them yourself with your own EOA — no Turnkey wallet.

Two round trips, mirroring the order intent flow:

1. **`POST /v2/credentials/polymarket/intent`** with `{ "address": "0xYourEOA" }`. The address must already be a registered wallet. The response returns a single-use `payload_id`, the `timestamp` and `nonce` used, the `digest_hex` to sign, a human-readable `message`, and `expires_at_us`.
2. **Sign `digest_hex`** with your EOA (EIP-712 L1 auth) and **`POST /v2/credentials/polymarket/submit`** with `{ "payload_id", "signature_hex" }`.

An account holds one set of Polymarket credentials. If they were provisioned for a **different** signing wallet, submit returns `409` rather than silently switching; pass `"replace_existing": true` to move the account's credentials to the new wallet deliberately. Orders signed by the old wallet will then be rejected by the venue.

```json theme={null}
{ "api_key": "…", "wallet_address": "0xYourEOA" }
```

The L2 `secret` and `passphrase` are **stored encrypted server-side and are never returned**. Kairos uses them to submit and cancel on your behalf, so your client never holds them. The intent is single-use and claimed before verification; ownership is re-checked at submit, so a wallet revoked between intent and submit is rejected.

<Note>
  **You sign an L1 auth message, not an order.** This step only mints the venue credentials; it moves no funds and places no order. It is idempotent in effect — if the credentials already exist at the venue, Kairos derives them instead of erroring.
</Note>

## Deposit wallet (required to trade Polymarket)

Polymarket's CLOB now requires orders to come from a **deposit wallet** — a per-user ERC-1967 proxy it deploys for you — rather than a raw EOA. Create and approve yours yourself; no account manager and no Turnkey wallet involved.

The deposit-wallet address is deterministic from your signing EOA, so you can derive it before deploying.

```
GET  /v2/deposit-wallet?owner_address=0xYourEOA
POST /v2/deposit-wallet/create
POST /v2/deposit-wallet/approvals/intent
POST /v2/deposit-wallet/approvals/submit
```

1. **Derive.** `GET /v2/deposit-wallet?owner_address=…` returns `deposit_wallet_address`. `owner_address` must be a [registered signing wallet](/external-execution/partner-auth#self-serve-wallet-registration).
2. **Create.** `POST /v2/deposit-wallet/create` with `{ "owner_address": "0xYourEOA" }` deploys the proxy via Polymarket's relayer. **No signature is required** and it is idempotent — re-running returns the same address.
3. **Approve.** `POST /v2/deposit-wallet/approvals/intent` returns the missing trading approvals as an EIP-712 `Batch` (domain `DepositWallet`, `verifyingContract` = your deposit wallet) plus `eip712_digest_hex`. Sign that digest with your EOA — a **plain 65-byte signature**, not ERC-7739. Post `{ "payload_id", "signature_hex" }` to `POST /v2/deposit-wallet/approvals/submit`; Kairos forwards the signed batch to the relayer and waits for the on-chain transaction.

```json theme={null}
{ "deposit_wallet_address": "0x6d14…", "deploy_tx_id": "01a0c212-…" }
{ "submitted": true, "transaction_hash": "0xd9a1db0a…" }
```

Then **fund the deposit wallet** (send the settlement asset, e.g. pUSD on Polygon, to `deposit_wallet_address`) and trade from it.

### Ordering from the deposit wallet

Orders use `signature_type: 3` (POLY\_1271) with `owner_address` = the deposit wallet; omit `signer_address` (it defaults to the wallet, which is what the CLOB validates via ERC-1271). The intent returns `unsigned_payload.erc7739_digest_hex`; sign that with the wallet's owner EOA and submit the plain 65-byte signature — Kairos builds the ERC-7739 envelope.

<Warning>
  **Polymarket deposit-wallet orders are currently blocked upstream.** Polymarket's CLOB binds an L2 API key only to the owner EOA, never to the deposit wallet, so it rejects the order with `the order signer address has to be the address of the API KEY`. This is an acknowledged bug on Polymarket's own SDK tracker (py-clob-client-v2 #64/#70/#75/#77) with no server-side workaround. Onboarding and order construction work today; the order will settle once Polymarket fixes the binding.
</Warning>

The intent response carries an extra `unsigned_payload.erc7739_digest_hex`. Sign **that** with your EOA — a plain 65-byte raw-hash signature — and submit it as `signature_hex`. Kairos wraps it into the ERC-7739 envelope the deposit wallet's `isValidSignature` expects and forwards the order; the Polymarket CLOB performs the ERC-1271 check. Kairos makes no on-chain call. Do not sign `eip712_digest_hex` for a deposit-wallet order: that is the bare order digest the wallet contract validates, not what the owner signs.

```json theme={null}
{ "intent": { "token_id": "…", "side": "buy", "price": "0.50", "size": "5", "time_in_force": "GTC", "neg_risk": false, "owner_address": "0xYourDepositWallet", "signature_type": 3 }, "market_id": "0x…", "outcome": "Yes" }
```

If your wallet stack already produces the ERC-7739 envelope (for example the Rust `polymarket_client_sdk_v2`), submit that instead; anything longer than 65 bytes is forwarded verbatim. Kairos only wraps when `owner_address` is a deposit wallet derived from one of your registered signing wallets — the one it created for you — so it never rewrites a signature meant for some other contract wallet. The TypeScript SDK's `digestToSign(intent)` picks the right digest, and `polymarketDepositWalletDigest(order, negRisk)` reproduces it locally for the one-shot WebSocket path.

<Note>
  **The intent is single-use and short-lived.** Each `approvals/intent` is claimed on the first `submit`; if it expires, request a new one. `calls: []` means every approval is already in place — nothing to sign.
</Note>

```
POST /v2/partner/delegation/verify
```

A user can authorize a partner app to act on their behalf with a **stateless EIP-712 grant**: scopes plus an expiry, signed off-chain by the user's wallet. There is no server-side store; revocation is by short expiry. This is how a platform integrates on behalf of its users without holding their Kairos credentials.

The signed `Delegation` struct:

| Field | Type | Description |
| - | - | - |
| `user` | address | The wallet granting authority; must be registered to a Kairos account |
| `delegate` | address | The partner app's address |
| `scopes` | string | Comma-separated, from `trade:execute`, `trade:read`, `position:read` |
| `issued_at` | integer | Unix seconds |
| `expires_at` | integer | Unix seconds; the grant is rejected outside this window |
| `nonce` | string | Single-use nonce, `0x`-prefixed hex |

`POST /v2/partner/delegation/verify` takes `{ "delegation": { …the struct… }, "signature_hex": "0x…" }` and returns the resolved owner, the scopes and the expiry:

```json theme={null}
{ "user_id": "…", "delegate": "0x…", "scopes": ["trade:read"], "expires_at": 1750000000 }
```

### Authenticating with a grant

To use a grant directly as a credential, send it on the request instead of a JWT:

* `X-Kairos-Delegation`: the delegation JSON (`{ user, delegate, scopes, issued_at, expires_at, nonce }`)
* `X-Kairos-Delegation-Signature`: the `0x`-prefixed signature over that struct

Kairos verifies the grant, resolves the owning user, and synthesizes an identity carrying exactly the granted scopes. A grant cannot exceed `trade:execute`, `trade:read`, `position:read`, and the grant's `expires_at` is enforced on every request. Verify a grant up front with `/v2/partner/delegation/verify` before you wire it into a flow.

<Warning>
  **A delegation grant is a bearer credential for its lifetime.** It is not bound to the presenting party at the transport layer and there is no per-request revocation list — treat it like a short-lived API key and keep `expires_at` tight. Rotating the user's key does not invalidate an outstanding grant; only the expiry does.
</Warning>

## Readiness and balances

Two read-only endpoints let you self-diagnose your readiness:

**`GET /v2/onboarding/status`** (authenticated) returns, per provider, everything needed to place a first order and the single next step:

```json theme={null}
{
  "allowlisted": true,
  "providers": [
    {
      "id": "polymarket",
      "registered_signing_wallets": ["0xyoureoa"],
      "credentials_configured": true,
      "native_gas_balance_wei": "12000000000000000",
      "settlement_asset": "pUSD",
      "settlement_balance": "250000000",
      "approvals_ready": true,
      "next_action": null
    }
  ]
}
```

`next_action` is the ordered next thing to do — register a wallet, provision credentials, set approvals, fund gas — or `null` when the provider is ready. Balance fields are omitted when they cannot be read.

**`GET /v2/wallets/balances?provider=polymarket`** (authenticated) returns the same per-wallet view: native gas in wei, the settlement asset and balance, and whether the required approvals are in place.

`provider` is optional and defaults to `polymarket`; it is validated, so an unsupported value is a `400` rather than a silently-ignored parameter. Predict.fun has no external on-chain lane, so its wallets are reported with the settlement asset (`USDT`) but `null` gas, balance and approval fields instead of probing a chain the venue does not settle on.

Run these two reads after a change instead of discovering state from a failed order: `GET /v2/onboarding/status` tells you *what to do next*, `GET /v2/wallets/balances` tells you *whether the chain is ready*. (`allowlisted` reflects the optional kill-switch; the lane is open by default.)

## Capabilities, identity and health

Three more reads round out the surface:

* `GET /v2/providers` (authenticated) — per-venue capability descriptor: accepted `signature_schemes` (`eoa`, `poly1271`), `account_abstraction` flags, order entry points (two-round-trip REST, one-round-trip WSS, batch), supported `time_in_force`, `post_only`, cancel/amend capability, and whether your account is enabled. Read this instead of hard-coding venue behavior.
* `GET /v2/regions` (unauthenticated) — deployment identity: `role` (`cell`/`central`), `cell_id`, `region`, `exchanges` and whether the `/v2` lane is mounted.
* `GET /v2/health` (unauthenticated) — external-lane status: `status` (`ok`/`degraded`), the providers actually registered on this deployment, and feature flags (Polygon RPC, idempotency, admission mode).

## Attribution

If you resell or front access, tag your requests with `X-Kairos-Partner-Id` (REST) or a `partner_id` payload field (WebSocket) so Kairos can attribute volume, transactions and RPS to you. It is optional, never grants access, and is documented in full under [Partner attribution](/external-execution/signing#partner-attribution).

## See also

* **[Signing & Order Lifecycle](/external-execution/signing)** — the intent → sign → submit flow, Poly1271 smart-wallet orders, the WebSocket one-round-trip path, and cancels.
* **[Overview](/external-execution/overview)** — the access gate, the account model, and what's live today.
* **[Regional Execution Nodes](/external-execution/regional-execution)** — where to connect, and the live-position source-of-truth caveat.
* **[API Reference](/api-reference)** — full request/response schemas.


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