Create wallet
Provisions a new wallet under exactly one provider account: the project's Managed config for provider, or the Custody Connection named by connectionId.
POST/Run in dashboard
A body naming both, or neither, is rejected with 400. With an Idempotency-Key, SDP never records a second wallet for one key: a retry returns the wallet an earlier attempt recorded, including after the 24-hour key window. Privy and Coinbase CDP also receive a request id derived from the key, so they dedupe at the provider. A provider failure is stored as a 503, and a new key is a new provider request. A 503 can mean the provider created the wallet before the error, and with a provider other than Privy or Coinbase CDP, a failure that left no SDP wallet row makes even the same key a new provider request after 24 hours: before any retry after an ambiguous failure, whatever the key, check GET /v1/wallets and, if the wallet is not there, the provider account. The provider request id and the recorded-wallet lookup both include the credential, so after an API key rotation, once the key's 24-hour record has expired, the same key under the new credential is a new provider request and can create a second wallet: after a rotation, list wallets with GET /v1/wallets and check the provider account before retrying a create.
Requires an API key with the wallets.manage action.
Headers
-
x-project-id·string· min length 1 · exampleprj_exampleRequired for dashboard sessions. API keys use their assigned project.
-
Idempotency-Key·stringUse a unique 1–255 printable ASCII key per operation. Reuse it with the same request on retries. An in-flight attempt returns
409; a changed request returns422. Retry and recovery rules.
Request body
application/json · required
Create wallet request body. Names exactly one owner: provider (Managed config) or connectionId (Custody Connection); a body naming both, or neither, is rejected with 400.
Any of:
-
Create a wallet under the project's Managed config for provider. · must not include
connectionId-
provider·string· required · one oflocal,fireblocks,privy,coinbase_cdp,para,turnkey,dfns,ibm_haven,anchorage,utila· exampleprivyProvider whose Managed config owns the new wallet. A project holds one active config per provider.
-
label·string· max length 100 · exampleMint authority walletOptional label for the new wallet.
-
purpose·string· one oftransfer,fee_payer· defaulttransferWhat the wallet is for. Defaults to
transfer, the only purpose a payment, transfer batch or recurring payment can send from.
-
-
Create a wallet under a Custody Connection. · must not include
provider-
connectionId·string· required · min length 1 · examplecconn_123Custody Connection (BYOK) that owns the new wallet.
-
label·string· max length 100 · exampleMint authority walletOptional label for the new wallet.
-
purpose·string· one oftransfer,fee_payer· defaulttransferWhat the wallet is for. Defaults to
transfer, the only purpose a payment, transfer batch or recurring payment can send from.
-
Responses
-
201Wallet created-
data·object· requiredCreated wallet response payload.
-
wallet·object· requiredWallet details.
-
id·string· required · examplecw_exampleWallet record ID.
-
custodyConfigId·string· examplecfg_exampleOwning custody configuration ID.
-
custodyConnectionId·string· examplecconn_exampleOwning Custody Connection ID.
-
provider·string· one oflocal,fireblocks,privy,coinbase_cdp,para,turnkey,dfns,ibm_haven,anchorage,utila· exampleprivyWallet signing provider.
-
isRuntimeExecutionAllowed·boolean· required · exampletrueWhether SDP currently permits attempting runtime execution through this wallet's owner. This is request-time admission, not Provider health or a success guarantee.
-
walletId·string· required · min length 1 · exampleprivy_wallet_123Provider wallet ID.
-
publicKey·string· required · min length 32 · max length 44 · exampleSo11111111111111111111111111111111111111112Base58-encoded Solana address.
-
label·string | null· required · exampleRoot Signing WalletOptional wallet label.
-
purpose·string | null· required · examplerootOptional wallet purpose.
-
status·string· required · one ofactive,inactive· exampleactiveWallet status.
-
createdAt·string· required · formatdate-time· example2025-01-01T00:00:00.000ZISO 8601 timestamp.
-
balances·object[]Tracked token balances when requested and successfully observed. Omitted when the balance lookup is unavailable; an empty array is a successful zero-token result.
-
token·string· required · exampleUSDCTracked token symbol.
-
mint·string· required · min length 32 · max length 44 · example4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDUTracked token mint address.
-
amount·string· required · example1250000Raw token amount as a string.
-
uiAmount·string· required · example1.25Human-readable token balance.
-
decimals·integer· required · min 0 · example6Token decimals.
-
usdPrice·number· example1Resolved USD price per token when available.
-
usdValue·number· example125.5Resolved USD value of this balance when pricing is available.
-
One of:
-
Owned by a Managed config. · must include
custodyConfigId· must not includecustodyConnectionId -
Owned by a Custody Connection. · must include
custodyConnectionId· must not includecustodyConfigId
-
-
-
meta·object· requiredResponse metadata.
-
requestId·string· required · min length 1 · examplereq_exampleRequest identifier for tracing.
-
timestamp·string· required · formatdate-time· example2025-01-01T00:00:00.000ZISO 8601 timestamp.
-
-
-
400Bad Request-
error·object· requiredStandard error payload.
-
code·string· requiredMachine-readable error code.
-
message·string· requiredHuman-readable error message.
-
details·objectOptional error details for debugging.
*·any
-
-
meta·object-
requestId·string· min length 1 · examplereq_exampleRequest identifier for tracing.
-
-
-
401UnauthorizedSame fields as
400. -
403Forbidden. Requires thewallets.manageaction: an API key must hold it (a module'smanageorexecutealso covers itsread), and a signed-in member's organization role must include it. Without it the error is INSUFFICIENT_PERMISSIONS anderror.details.actionnames the action. Any other 403 names its own reason. Forbidden: the caller lacks the route's action or the API key is wallet-scoped, or the provider account was refused. A refusal's details.reason names the failed check: custody_mode_not_allowed when provider names a Managed config in a Production project (Production is BYOK only: create the wallet in a Custody Connection with connectionId), custody_provider_not_in_release_channel, or provider_not_entitled.Same fields as
400. -
404Not FoundSame fields as
400. -
409ConflictSame fields as
400. -
422Unprocessable EntitySame fields as
400. -
500Internal Server ErrorSame fields as
400. -
503Service UnavailableSame fields as
400.
Example request
curl -X POST 'https://api.solana.com/v1/wallets' \
-H "Authorization: Bearer $SDP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"provider": "privy",
"label": "Treasury wallet",
"purpose": "transfer"
}'Example responses
{
"data": {
"wallet": {
"id": "cw_example",
"custodyConfigId": "cfg_example",
"provider": "privy",
"isRuntimeExecutionAllowed": true,
"walletId": "privy_wallet_123",
"publicKey": "So11111111111111111111111111111111111111112",
"label": "Root Signing Wallet",
"purpose": "root",
"status": "active",
"createdAt": "2025-01-01T00:00:00.000Z"
}
},
"meta": {
"requestId": "req_example",
"timestamp": "2025-01-01T00:00:00.000Z"
}
}Error responses use the standard error shape.