Skip to main content
At the end of this page, you will have created a Sumvin user, added a wallet, and made a real call to: GET /v0/wallets/{id}/assets — Returns a JSON body listing the wallet’s balances with _links for navigation. Nothing here is a toy. The call you make in five minutes is the call you run in production.

Prerequisites

  • A Platform API JWT from your configured auth provider (Dynamic Labs, Privy, or ). See Authentication.
  • Your platform’s base URL — https://api.sumvin.com/v0.
  • A wallet address for the user () on Sei (chain_id: 1329).
  • Your x-juno-orgid if your credentials span multiple Sumvin organisations (see Authentication). Single-org integrations can omit this header.
The Platform API uses the x-juno-jwt header (no Bearer prefix). Reserve Authorization: Bearer for SIS API calls. Multi-tenant callers also pass x-juno-orgid: <your-org-id> to scope the request to a specific organisation.
The first call you make is POST, not GET. GET /v0/user/me returns 404 USR-404-001 until the user is created — it does not auto-provision. If you see a 404 on the first request, follow it with POST /v0/user/ rather than retrying the GET.
1

Create the user

Register the user with their primary EOA address. Sumvin queues a Safe smart account for deployment on the specified chain in the background — you do not wait for it here.
Background Safe deployment is the default ai_agent cohort behaviour. Environments configured for user-signed Safe deploy (recommended for partners who want user-controlled wallets) or BYO Safe (existing on-chain Safe) follow a different onboarding step instead. See Onboarding cohorts.

Supported chain_id values

chain_id follows EIP-155 and must be one of Sumvin’s supported EVM networks:
chain_idNetworkType
1Ethereum Mainnetmainnet
10Optimismmainnet
56BNB Smart Chainmainnet
137Polygonmainnet
8453Basemainnet
42161Arbitrum Onemainnet
43114Avalanche C-Chainmainnet
1329Seimainnet
1328Sei Testnettestnet
The example below uses 1329 (Sei). Pass any value from the table — Sumvin deploys the user’s Safe on the chain you specify.
curl -X POST https://api.sumvin.com/v0/user/ \
  -H "x-juno-jwt: <your-jwt-token>" \
  -H "x-juno-orgid: <your-org-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "primary_eoa_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
    "chain_id": 1329
  }'
Response: 201 Created
{
  "user": {
    "id": "usr_abc123",
    "username": null,
    "first_name": null,
    "last_names": null,
    "auth_provider": "dynamic",
    "auth_provider_id": "dyn_xxxxxxxxxx",
    "primary_eoa_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
    "primary_smart_wallet_address": null,
    "safe_creation_status": "processing",
    "email": "user@example.com",
    "phone": null,
    "phone_verified_at": null,
    "created_at": 1740000000000,
    "updated_at": 1740000000000,
    "deleted_at": null,
    "profile_picture_url": null
  },
  "_links": {
    "self": { "href": "/v0/user/me" },
    "wallets": { "href": "/v0/wallets" },
    "kyc-status": { "href": "/v0/kyc/status" }
  }
}

safe_creation_status values

The Safe smart wallet is deployed by a background worker after the user record is created. Poll GET /v0/user/me to watch this field transition.
ValueMeaningWhat the UI should show
pendingInitial state — Safe creation has been queued but not yet picked up by the worker.Brief; treat the same as processing.
processingWorker is deploying the Safe contract on-chain.”Setting up your wallet…” with a spinner. Continue polling.
completedSafe deployed. primary_smart_wallet_address is now populated.Reveal the smart-wallet-dependent surfaces (transactions, cards).
failedDeployment failed and will not retry automatically.Surface a contact-support path. Do not block onboarding on this field — phone_verification and kyc_verification proceed independently.
Treat pending and processing as the same non-terminal state. completed and failed are terminal — once you observe one, stop polling this field.

When the user already exists — 208 Already Reported

If the authenticated identity already maps to a Sumvin user, POST /v0/user/ returns 208 Already Reported with the existing record. The body is the same UserResponse shape as the 201 Created response above — same user object, same _links, no slim variant. This means you can call POST /v0/user/ idempotently from your onboarding entry point without checking for duplicates first.

Error responses

All errors follow the RFC 7807 Problem Details format. The three you should design UI for:
{
  "type": "https://api.sumvin.com/errors/usr-401-001",
  "title": "User Authentication Failed",
  "status": 401,
  "detail": "Missing or invalid x-juno-jwt header.",
  "instance": "/v0/user",
  "error_code": "USR-401-001",
  "trace_id": "abc-123-def"
}
The x-juno-jwt header is missing, malformed, or expired. Refresh the token from your auth provider and retry.
Returned when FastAPI rejects the request body before it reaches the handler — for example, a non-integer chain_id, a missing required field, or an invalid email shape.
{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["body", "chain_id"],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "sei"
    }
  ]
}
Fix the field at loc and retry. This response uses FastAPI’s default validation error envelope, not RFC 7807 — it is the one place in the user-creation flow where the shape differs.
Not strictly an error — Sumvin returns 208 to signal an idempotent replay rather than 409 Conflict. The body is the existing user record (see above).
{
  "user": {
    "id": "usr_abc123",
    "primary_eoa_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
    "safe_creation_status": "completed",
    "email": "user@example.com",
    "created_at": 1740000000000,
    "updated_at": 1740000050000
  },
  "_links": {
    "self": { "href": "/v0/user/me" },
    "wallets": { "href": "/v0/wallets" },
    "kyc-status": { "href": "/v0/kyc/status" }
  }
}
Treat 201 and 208 as the same success path — the record is yours either way.
Other status codes worth handling defensively: 400 WAL-400-001 if primary_eoa_address is not a valid EVM address, and 403 WAL-403-003 if the address cannot be verified against your auth provider’s connected identity. Both follow the RFC 7807 shape shown above.
2

Check onboarding status

The onboarding state machine tells you which step the user is on. is_complete: true gates the next step.
curl "https://api.sumvin.com/v0/user/me/onboarding/steps" \
  -H "x-juno-jwt: <your-jwt-token>" \
  -H "x-juno-orgid: <your-org-id>"
Response: 200 OK
{
  "onboarding": {
    "current_step": "phone_verification",
    "is_complete": false,
    "steps": [
      { "step": "phone_verification", "status": "current", "gated": false, "meta": null },
      { "step": "kyc_verification", "status": "pending", "gated": false, "meta": null },
      { "step": "open_banking", "status": "pending", "gated": false, "meta": null },
      { "step": "card_setup", "status": "pending", "gated": false, "meta": null },
      { "step": "feature_selection", "status": "pending", "gated": false, "meta": null }
    ]
  },
  "_links": {
    "self": { "href": "/v0/user/me/onboarding/steps" },
    "onboarding-events": { "href": "/v0/user/me/onboarding/events" }
  }
}
The server drives the transitions — you submit the prompted data for each step until is_complete is true. See the onboarding guide for the full flow, including phone and KYC.
3

Add a wallet

Once onboarding completes, add the user’s wallet. If you use Dynamic Labs, pass the credential_id from the user’s verified credential — Sumvin resolves the address and chain from Dynamic’s API.
curl -X POST https://api.sumvin.com/v0/wallets/ \
  -H "x-juno-jwt: <your-jwt-token>" \
  -H "x-juno-orgid: <your-org-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_id": "cred_abc123"
  }'
Response: 201 Created
{
  "id": "wal-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
  "chain_id": 1329,
  "is_primary": true,
  "is_eoa": true,
  "nickname": null,
  "logo_uri": null,
  "created_at": 1740000001000,
  "deleted_at": null,
  "_links": {
    "self": { "href": "/v0/wallets/wal-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" },
    "user": { "href": "/v0/user/me" }
  }
}
For auth providers that do not issue credential IDs, use the SIWE challenge/verify flow instead — see the wallets guide for all three ownership verification methods.
4

Query wallet balances

This is the working artefact. The response lists token balances with USD valuations, plus HAL _links to navigate to the wallet itself and set-primary action.
curl https://api.sumvin.com/v0/wallets/wal-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/assets \
  -H "x-juno-jwt: <your-jwt-token>" \
  -H "x-juno-orgid: <your-org-id>"
Response: 200 OK
{
  "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
  "wallet_chain_id": 1329,
  "assets": [
    {
      "symbol": "ETH",
      "name": "Ether",
      "chain_id": 1329,
      "balance": "0.05423100",
      "balance_usd": "173.54",
      "balance_updated_at": 1740000010000,
      "transactions_href": "/v0/wallets/wal-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/assets/eth-1329/transactions",
      "_links": {
        "self": { "href": "/v0/wallets/wal-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/assets" }
      }
    }
  ],
  "total_assets": 1,
  "_links": {
    "self": { "href": "/v0/wallets/wal-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/assets" },
    "wallet": { "href": "/v0/wallets/wal-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" }
  }
}
You have a live Sumvin integration. The _links field is how every response advertises its next actions — use them instead of hardcoding URLs.

Minting a public RPC key

If your integration calls the RPC endpoint from a browser or mobile bundle, mint a public-partition key with rpc.invoke. Public-partition keys are scoped to a single namespace (rpc.*) and are safe to ship in client-side code.
curl -X POST https://api.sumvin.com/v0/organisation/<org_id>/keys \
  -H "x-juno-jwt: <your-jwt-token>" \
  -H "x-juno-orgid: <org-id>:<env-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "browser-rpc-key",
    "env_id": "<env-id>",
    "scopes": ["rpc.invoke"]
  }'
The response carries partition: "public", scopes: ["rpc.invoke"], and a _links.rpc_docs HAL link pointing at the RPC method reference. All requests authenticate with the returned key via Authorization: Bearer:
curl <rpc-endpoint>/<method> \
  -H "Authorization: Bearer <returned-key>"
The RPC method catalogue is documented separately. Use _links.rpc_docs from the create response to find the latest method list.
For the server-side flow (full sis.* scope set) and the partition / scope reference, see SIS scopes & partitions.

What’s next

NextWhereWhen
Full account flowStand up an accountBefore putting real users through KYC and Safe deployment
Auth referenceAuthenticationWhen you’re wiring up your JWT provider or SIS key
API conventionsAPI conventionsWhen a response shape surprises you — HAL, 7807, expand, 202 patterns
Wallet depthWallets guideWhen you need multi-chain or manual Safe flows
SIS RPC keysAuthentication → Scopes & partitionsWhen you need a public-safe key for a browser or mobile bundle