- Scope:
enroll:writeon both stages. - Idempotency anchor:
flowIdreturned by stage 1. Valid for 24 hours. - Stage 2 creates one CREATE2-deterministic vault per requested chain inside a single database transaction.
Sequence
Stage 1 — Request the signable message
POST /v2/enroll/signature
Validates that the strategy belongs to your tenant, peeks the user’s next
available account index, resolves or allocates a pooled KMS agent, and
returns the session-key message.
Peek, not reserve
accountIndex is peeked, not reserved. Repeated stage-1 calls for the
same user return the same candidate index, and the index only advances when
stage 2 commits. Retries, abandoned flows, and tests do not burn indices.
Unsupported chain ids
chainIds is validated against the deployment’s configured RPC URLs. Unknown
chains return 400 with the exact allowed list in details, so you can
surface a helpful error without a second round trip:
Stage 2 — Have the user sign, then submit
The user signsmessage.raw in their wallet (ECDSA personal_sign). The
signed bytes are the only cryptographic input. Then POST /v2/enroll with
every round-trip field from stage 1.
Round-trip contract
These fields must be sent back to stage 2 byte-for-byte identical to what stage 1 returned. The server recomputes the session-key message from them; any mismatch fails signature recovery.| Field | Stage 1 returns | Stage 2 requires |
|---|---|---|
flowId | Yes | Yes — also the idempotency anchor |
accountIndex | Yes (string) | Yes — as a string, not a number |
agentAccountId | Yes | Yes |
ownerAccountId | (input) | Same CAIP-10 as stage 1 |
strategyId | (input) | Same as stage 1 |
chainIds | (input) | Same set, same order |
Error Handling
| Code | HTTP | Cause | Retry safe? |
|---|---|---|---|
API_400 | 400 | Unknown strategy, expired flowId (past 24h), invalid signature, or malformed CAIP-10 | No — re-run stage 1 |
API_007 | 409 | A previous stage-2 call with this flowId is still executing | Yes — back off and retry |
API_008 | 409 | Same flowId, different body — replay conflict | No — do not retry with modified body |
API_202 | 409 | User is already enrolled in this strategy | No — terminal |
Common Pitfalls
- Modifying
accountIndexbetween stages."7"and7are different values. The schema requires the decimal string form. Do not parse it to an integer on your side unless you also re-serialize it before stage 2. - Generating a fresh
flowIdon stage-2 retry. This creates a fresh session-key slot and burns the previous one. Always retry with the originalflowIdunless you have received a terminal error. - Sending
ownerAccountIdin chain-bound form. The enroll surface only accepts chain-agnosticeip155:0:<addr>owners. Use CAIP identifiers to pick the right form. - Calling stage 2 more than 24 hours after stage 1. The
flowIdreservation has expired. Re-run stage 1.