Wallets
Create custodial wallets for a user, list them, and read each wallet's balances and ledger.
Create custodial wallets for an end user, list the ones they hold, and read each wallet's balances and ledger. A wallet groups one or more network/currency pairs and provisions a deposit address per network when it's created. The user must already exist under your customer before you open a wallet.
Authentication: every endpoint on this page accepts your Owner API Key (x-api-key) or a user token (Authorization: Bearer). A user token only reaches its own user and its own wallets.
x-api-key: YOUR_OWNER_API_KEYResponses use the standard envelope, amounts are decimal strings at each asset's scale, and lists use cursor pagination. See API conventions for the envelope, errors, idempotency, pagination and filters, money, and network codes.
A wallet you can't see answers exactly like a missing one: 404 WALLET_NOT_FOUND. That covers wallets of another customer and, for user tokens, wallets of another user.
The wallet object
| Field | Type | Description |
|---|---|---|
| id | string (UUID) | Wallet id. |
| wallet_id | string (UUID) | Same value as id, kept as an alias. |
| object | string | Always wallet. |
| user_id | string (UUID) | Owner of the wallet. |
| customer_id | string | Your customer id. |
| name | string | Wallet name, unique per user. |
| description | string | null | Free-text description. |
| wallet_type | string | VIRTUAL or REAL. |
| status | string | ACTIVE or RETIRED. FLAGGED and FROZEN are reserved for a later release; treat the enum as open. |
| networks | array | The {network, currency} pairs the wallet was opened with. network is a canonical code such as BITCOIN. |
| addresses | array | Deposit addresses already provisioned, as address objects. Empty while provisioning; create one later with POST /wallets/{wallet_id}/addresses. |
| created_at / updated_at | string | UTC timestamps without milliseconds. |
| created_by / updated_by | string | null | Actor that created or last changed the wallet. |
Create Wallet
Open a new wallet for the user. Each network/currency pair you list provisions a deposit address, returned in addresses[]. Send an x-idempotency-key header (UUID) so a retry never opens a second wallet.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| user_id | string (UUID) | Yes | User that owns the wallet. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Wallet name, unique per user (max 100). |
| networks | array | Yes | One or more network/currency pairs. |
| wallet_type | string | No | VIRTUAL or REAL, case-insensitive. Defaults to VIRTUAL. |
| description | string | No | Free-text description. |
networks[] object
| Field | Type | Required | Description |
|---|---|---|---|
| network | string | Yes | Canonical network code: BITCOIN, ETHEREUM, SOLANA, TRON, POLYGON, or BNB_SMART_CHAIN. |
| currency | string | Yes | Currency code, e.g. BTC, ETH, USDC. |
| name | string | No | Deprecated. The old network identifier (e.g. networks/bitcoin-testnet). Still accepted when network is missing; removed in the next release. |
{
"name": "Main Wallet",
"description": "Main wallet for all transactions",
"wallet_type": "VIRTUAL",
"networks": [
{ "network": "BITCOIN", "currency": "BTC" }
]
}Success Response (201 Created):
{
"success": true,
"data": {
"id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"object": "wallet",
"user_id": "3afd8834-33a7-52b0-aa47-8138c9af062a",
"customer_id": "corp-redundant-firewall",
"name": "Main Wallet",
"description": "Main wallet for all transactions",
"wallet_type": "VIRTUAL",
"status": "ACTIVE",
"networks": [
{ "network": "BITCOIN", "currency": "BTC" }
],
"addresses": [
{
"id": "ad1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"address_id": "ad1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"object": "address",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"currency": "BTC",
"network": "BITCOIN",
"address": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
"memo": null,
"created_at": "2026-10-12T14:03:11Z",
"created_by": "d5263d12-b5e8-5fc7-ba07-816c0c78a61a"
}
],
"created_at": "2026-10-12T14:03:11Z",
"updated_at": "2026-10-12T14:03:11Z",
"created_by": "d5263d12-b5e8-5fc7-ba07-816c0c78a61a",
"updated_by": "d5263d12-b5e8-5fc7-ba07-816c0c78a61a"
},
"meta": { "timestamp": "2026-10-12T14:03:11Z", "version": "v1", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" }
}curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/wallets" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_OWNER_API_KEY" \
-H "x-idempotency-key: 9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c" \
-d '{
"name": "Main Wallet",
"description": "Main wallet for all transactions",
"wallet_type": "VIRTUAL",
"networks": [{ "network": "BITCOIN", "currency": "BTC" }]
}'Errors: 400 WALLET_NAME_ALREADY_EXISTS the user already has a wallet with that name · 401 UNAUTHORIZED invalid key or token · 403 FORBIDDEN a user token for another user · 404 USER_NOT_FOUND the user doesn't exist under your customer · 422 VALIDATION_ERROR invalid field, with source.pointer (e.g. /networks/0/network for an unknown network) · 429 RATE_LIMIT_EXCEEDED.
List Wallets
List the user's wallets, newest first. A user with no wallets returns 200 with data: [].
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| filter[limit] | integer | No | Page size. Default 25, max 100. |
| cursor | string | No | Opaque cursor from meta.pagination.navigation. |
| filter[order] | string | No | created_at or updated_at, ASC or DESC. Default created_at DESC. |
| filter[where][status][eq|inq] | string | No | ACTIVE, RETIRED (FLAGGED and FROZEN are accepted and match nothing today). |
| filter[where][created_at][gte|lte|between] | string | No | UTC date-time ending in Z. |
GET https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/wallets?filter[where][status][eq]=ACTIVE&filter[limit]=25Success Response (200 OK):
{
"success": true,
"data": [
{
"id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"object": "wallet",
"user_id": "3afd8834-33a7-52b0-aa47-8138c9af062a",
"customer_id": "corp-redundant-firewall",
"name": "Main Wallet",
"description": "Main wallet for all transactions",
"wallet_type": "VIRTUAL",
"status": "ACTIVE",
"networks": [
{ "network": "BITCOIN", "currency": "BTC" }
],
"addresses": [
{
"id": "ad1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"address_id": "ad1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"object": "address",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"currency": "BTC",
"network": "BITCOIN",
"address": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
"memo": null,
"created_at": "2026-10-12T14:03:11Z",
"created_by": "d5263d12-b5e8-5fc7-ba07-816c0c78a61a"
}
],
"created_at": "2026-10-12T14:03:11Z",
"updated_at": "2026-10-12T14:03:11Z",
"created_by": "d5263d12-b5e8-5fc7-ba07-816c0c78a61a",
"updated_by": "d5263d12-b5e8-5fc7-ba07-816c0c78a61a"
}
],
"meta": {
"timestamp": "2026-10-12T14:05:02Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"pagination": {
"records": { "limit": 25, "has_next": false, "has_previous": false },
"navigation": { "next": null, "previous": null }
}
}
}curl -X GET "https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/wallets?filter[where][status][eq]=ACTIVE&filter[limit]=25" \
-H "x-api-key: YOUR_OWNER_API_KEY"Errors: 401 UNAUTHORIZED · 403 FORBIDDEN a user token for another user · 404 USER_NOT_FOUND · 422 VALIDATION_ERROR unknown filter, operator, or cursor, with source.parameter · 429 RATE_LIMIT_EXCEEDED.
Get Wallet Balance
Read the wallet's balance per currency. Every amount is a decimal string at the asset's scale (BTC 8, ETH 18, USDT 6…). held is total minus available, never negative.
Response Fields
| Field | Type | Description |
|---|---|---|
| object | string | Always wallet_balance. |
| wallet_id | string (UUID) | Wallet id. |
| user_id | string (UUID) | Owner of the wallet. |
| balances[] | array | One row per currency. |
| balances[].currency | string | Uppercase currency code. |
| balances[].total | string | Total balance. |
| balances[].available | string | Available to spend. |
| balances[].held | string | Reserved by pending movements. |
| as_of | string | UTC time the balance was read. |
GET https://api.sandbox.sovera.io/sovx/v1/wallets/{{walletID}}/balanceSuccess Response (200 OK):
{
"success": true,
"data": {
"object": "wallet_balance",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"user_id": "3afd8834-33a7-52b0-aa47-8138c9af062a",
"balances": [
{ "currency": "BTC", "total": "0.50000000", "available": "0.45000000", "held": "0.05000000" },
{ "currency": "ETH", "total": "1.250000000000000000", "available": "1.250000000000000000", "held": "0.000000000000000000" }
],
"as_of": "2026-10-12T14:03:11Z"
},
"meta": { "timestamp": "2026-10-12T14:03:11Z", "version": "v1", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736" }
}curl -X GET "https://api.sandbox.sovera.io/sovx/v1/wallets/{{walletID}}/balance" \
-H "x-api-key: YOUR_OWNER_API_KEY"Errors: 401 UNAUTHORIZED · 404 WALLET_NOT_FOUND · 429 RATE_LIMIT_EXCEEDED.
List Wallet Transactions
List the ledger movements recorded against this wallet, newest first. Only this wallet's movements come back; a wallet with none returns data: []. Each item is a transaction object.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| filter[limit] | integer | No | Page size. Default 25, max 100. |
| cursor | string | No | Opaque cursor from meta.pagination.navigation. |
| filter[order] | string | No | created_at or updated_at, ASC or DESC. Default created_at DESC. |
| filter[where][status][eq|inq] | string | No | PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED, EXPIRED, BLOCKED. |
| filter[where][currency][eq|inq] | string | No | Currency code, case-insensitive exact match. |
| filter[where][tx_hash][eq] | string | No | On-chain transaction hash. |
| filter[where][idempotency_key][eq] | string | No | x-idempotency-key of the request that created the movement. |
| filter[where][created_at][gte|lte|between] | string | No | UTC date-time ending in Z. |
GET https://api.sandbox.sovera.io/sovx/v1/wallets/{{walletID}}/transactions?filter[where][status][inq][]=COMPLETED&filter[limit]=25Success Response (200 OK):
{
"success": true,
"data": [
{
"id": "75ea771d-561e-5a7e-95b8-9420fe57aa55",
"object": "transaction",
"type": "DEPOSIT",
"status": "COMPLETED",
"status_reason": null,
"customer_id": "corp-redundant-firewall",
"user_id": "3afd8834-33a7-52b0-aa47-8138c9af062a",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"currency": "BTC",
"amount": "0.01000000",
"fee": null,
"net_amount": "0.01000000",
"price": null,
"pair": null,
"balance_after": "0.51000000",
"references": {
"order_id": null,
"withdrawal_id": null,
"transfer_id": null,
"deposit_id": "75ea771d-561e-5a7e-95b8-9420fe57aa55"
},
"address": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
"network": "BITCOIN",
"memo": null,
"tx_hash": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
"idempotency_key": null,
"description": "Deposit credited",
"created_at": "2026-10-11T09:12:40Z",
"updated_at": "2026-10-11T09:31:02Z",
"completed_at": "2026-10-11T09:31:02Z",
"created_by": "system:integration-layer",
"updated_by": "system:integration-layer"
}
],
"meta": {
"timestamp": "2026-10-12T14:03:11Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"pagination": {
"records": { "limit": 25, "has_next": true, "has_previous": false },
"navigation": {
"next": "/v1/wallets/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d/transactions?filter[where][status][inq][]=COMPLETED&filter[limit]=25&cursor=LGHoC3t43si9_yVEikVSoItl",
"previous": null
}
}
}
}curl -g -X GET "https://api.sandbox.sovera.io/sovx/v1/wallets/{{walletID}}/transactions?filter[where][status][inq][]=COMPLETED&filter[limit]=25" \
-H "x-api-key: YOUR_OWNER_API_KEY"Errors: 401 UNAUTHORIZED · 404 WALLET_NOT_FOUND · 422 VALIDATION_ERROR unknown filter, operator, or cursor, with source.parameter · 429 RATE_LIMIT_EXCEEDED.