V2 Integrator API
Enroll User (Stage 2)
Stage 2 of the two-stage enrollment flow — commits the user’s signed session-key and creates the portfolio.
POST
Stage 2 of the two-stage enrollment flow. Takes the
flowId, accountIndex,
and agentAccountId returned by POST /v2/enroll/signature, plus the user’s
signature, and provisions the portfolio: one smart account per requested
chain, plus the rebalance schedule derived from your tenant’s execution
config. The whole operation succeeds atomically — a failure anywhere rolls
back cleanly.
- Auth:
x-api-keyheader (required) - Scope:
enroll:write
flowId (scoped to your API key) with a
24-hour TTL:
- Replaying the same
flowIdwith the same body returns the cached response. - Replaying with a different body returns
409 IDEMPOTENCY_KEY_CONFLICT. - Replaying while the original is still in-flight returns
409 IDEMPOTENCY_IN_PROGRESS. - Replaying after a typed error (e.g., signature mismatch) returns the same typed error so retries don’t get stuck.
signature is computed by signing message.raw from the stage-1
response with the user’s wallet, using viem’s signMessage({ message: { raw } })
or the equivalent wallet primitive.
For EVM smart-contract wallets, pass accountType: "ERC1271" in both stages
and have the user sign the stage-1 message.typedData EIP-712 payload. The
server verifies the signature with ERC-1271 on the requested chain and configures
the Kernel vault with the SmartAccount sudo validator. ERC-1271 enrollment
currently supports exactly one EVM chainId per signature.
Wallet addresses are exchanged as CAIP-10
account identifiers. EVM ownerAccountId / agentAccountId use the
chain-agnostic form eip155:0:<address>, and each element of the response
smartAccounts array uses the chain-bound form eip155:<chainId>:<address>.
Solana (SVM) enrollment
For a Solana-rooted user, omitsignature and instead send
signedSolanaTransaction — the base64 solanaTransaction from stage 1 after the
user signs it with their wallet. The server re-derives the expected
instructions, verifies the user signed, co-signs with the pooled agent, and
submits it (the paymaster pays).
For an EVM-rooted user (Model A subaccount, client-driven single submit),
send userSecp256k1PublicKey (the user’s secp256k1 key — its derived address
must equal ownerAccountId’s), signature (the user’s slot-bound secp256k1
authorization over the Swig add-authority payload), currentSlot (the slot it
was bound to), and optionally ownerSignatureScheme (eip191 default / raw).
The server replays the signature to add the agent + create the sub-account in
one paymaster-sponsored transaction. Capture the slot and submit promptly — Swig
enforces a slot window.
The response smartAccounts[] entry’s accountId is the sub-account the
user funds; it is also surfaced as depositAccountId, with swigRoleId
identifying the owning Swig role. The parent Swig PDA is internal. A second
enroll for the same (tenant, user) reuses the same Swig and returns a new
sub-account at a higher role.
portfolioName is an optional display name for the portfolio. It is trimmed
server-side, returned by portfolio read endpoints, and mutable via
PATCH /v2/portfolios/{portfolioId}. Names may be up to 64 characters and may
include Unicode letters, digits, spaces, and - _ . ' : ( ) & /.
Common error responses:
400for an invalid request body, expired flow, mismatched signature, or invalid strategy ownership401whenx-api-keyheader is missing or the key is invalid403when the API key lacks theenroll:writescope409IDEMPOTENCY_IN_PROGRESSwhen an earlier request with the sameflowIdis still running409IDEMPOTENCY_KEY_CONFLICTwhen the sameflowIdis reused with a different request body409PORTFOLIO_ALREADY_EXISTSwhen the user is already enrolled at the givenaccountIndex500on unexpected server errors