Skip to main content
Enrolling a user into a strategy is a two-stage flow. Stage 1 returns a message for the user to sign with their wallet; stage 2 submits the signature and creates the portfolio. The two stages are linked by three round-trip fields that must be echoed back byte-for-byte unchanged.
  • Scope: enroll:write on both stages.
  • Idempotency anchor: flowId returned by stage 1. Valid for 24 hours.
  • Stage 2 creates one CREATE2-deterministic vault per requested chain inside a single database transaction.

Sequence

Integrator          Glider API                 End-user Wallet
    |                    |                           |
    |--1. POST /v2/enroll/signature---------------->|
    |                    |                           |
    |<--200 { message, flowId, accountIndex, agentAccountId }
    |                    |                           |
    |--2. signMessage(message.raw)----------------->|
    |                    |                           |
    |<--signature--------|---------------------------|
    |                    |                           |
    |--3. POST /v2/enroll (echo all round-trip fields + signature)
    |                    |                           |
    |<--201 { portfolioId, strategyId, vaults }------|

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.
curl -X POST https://api.glider.fi/v2/enroll/signature \
  -H 'x-api-key: gldr_sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "ownerAccountId": "eip155:0:0xabcdef0000000000000000000000000000000001",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "chainIds": [1, 8453]
  }'
Response:
{
  "success": true,
  "data": {
    "message": {
      "kind": "ecdsa",
      "raw": "0xdeadbeef...deadbeef"
    },
    "agentAccountId": "eip155:0:0x1111111111111111111111111111111111111111",
    "accountIndex": "7",
    "flowId": "flow_01JWZEE2MF30KVRMRX53N88VA4"
  }
}

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:
{
  "success": false,
  "error": {
    "code": "API_400",
    "message": "Request validation failed",
    "details": [
      "chainIds: Unsupported chainId: [999]. This deployment has RPCs configured for: [1, 8453, 42161]"
    ]
  }
}

Stage 2 — Have the user sign, then submit

The user signs message.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.
FieldStage 1 returnsStage 2 requires
flowIdYesYes — also the idempotency anchor
accountIndexYes (string)Yes — as a string, not a number
agentAccountIdYesYes
ownerAccountId(input)Same CAIP-10 as stage 1
strategyId(input)Same as stage 1
chainIds(input)Same set, same order
curl -X POST https://api.glider.fi/v2/enroll \
  -H 'x-api-key: gldr_sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "ownerAccountId": "eip155:0:0xabcdef0000000000000000000000000000000001",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "chainIds": [1, 8453],
    "accountIndex": "7",
    "agentAccountId": "eip155:0:0x1111111111111111111111111111111111111111",
    "signature": "0x9412d70d...39e01b",
    "flowId": "flow_01JWZEE2MF30KVRMRX53N88VA4",
    "portfolioName": "Alice Balanced"
  }'
Response:
{
  "success": true,
  "data": {
    "portfolioId": "pf_01JWZG3KH9P4N5QXJVNK7M3WTV",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "vaults": [
      { "accountId": "eip155:1:0x2222222222222222222222222222222222222222" },
      { "accountId": "eip155:8453:0xe3a2d1f49aee887e42655b56371d4d76bbf58058" }
    ]
  }
}

Error Handling

CodeHTTPCauseRetry safe?
API_400400Unknown strategy, expired flowId (past 24h), invalid signature, or malformed CAIP-10No — re-run stage 1
API_007409A previous stage-2 call with this flowId is still executingYes — back off and retry
API_008409Same flowId, different body — replay conflictNo — do not retry with modified body
API_202409User is already enrolled in this strategyNo — terminal
See Idempotency for the full 409 model.

Common Pitfalls

  • Modifying accountIndex between stages. "7" and 7 are 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 flowId on stage-2 retry. This creates a fresh session-key slot and burns the previous one. Always retry with the original flowId unless you have received a terminal error.
  • Sending ownerAccountId in chain-bound form. The enroll surface only accepts chain-agnostic eip155:0:<addr> owners. Use CAIP identifiers to pick the right form.
  • Calling stage 2 more than 24 hours after stage 1. The flowId reservation has expired. Re-run stage 1.