> Solana Developer Platform docs, served as Markdown. The [docs index](https://platform-beta.solana.com/docs.md) lists every page and [llms.txt](https://platform-beta.solana.com/docs/ai/llms.txt) the key ones; [SKILL.md](https://platform-beta.solana.com/SKILL.md) has the rules for building on SDP.

# 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 /v1/wallets`

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, even after the 24-hour key window. A provider that accepts a request id also receives one derived from the key.

On a provider that dedupes on the request id, a `5xx` is not stored: a same-key retry runs again and gets back the wallet the first attempt made, even when SDP failed to record it, for as long as the provider keeps the request id (permanently, or for 24 hours; the Idempotency guide lists each provider). On any other provider a 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. With a provider that accepts no request id, 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. 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: list wallets with `GET /v1/wallets` and check the provider account before retrying a create.

Requires an [API key](/docs/developing-with-sdp/authentication) with the `wallets.manage` action.

## Headers

- **`x-project-id`** · `string` · min length 1 · example `prj_example`

  Required for dashboard sessions. API keys use their assigned project.

- **`Idempotency-Key`** · `string`

  Use 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 returns `422`. [Retry and recovery rules](/docs/developing-with-sdp/idempotency).

## 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 of `fireblocks`, `privy`, `coinbase_cdp`, `para`, `turnkey`, `dfns`, `ibm_haven`, `anchorage`, `utila` · example `privy`

    Provider whose Managed config owns the new wallet. A project holds one active config per provider.

  - **`label`** · `string` · max length 100 · example `Mint authority wallet`

    Optional label for the new wallet.

  - **`purpose`** · `string` · one of `transfer`, `fee_payer` · default `transfer`

    What 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 · example `cconn_123`

    Custody Connection (BYOK) that owns the new wallet.

  - **`label`** · `string` · max length 100 · example `Mint authority wallet`

    Optional label for the new wallet.

  - **`purpose`** · `string` · one of `transfer`, `fee_payer` · default `transfer`

    What the wallet is for. Defaults to `transfer`, the only purpose a payment, transfer batch or recurring payment can send from.

## Responses

- **`201`** Wallet created

  - **`data`** · `object` · required

    Created wallet response payload.

    - **`wallet`** · `object` · required

      Wallet details.

      - **`id`** · `string` · required · example `cwlt_example`

        Wallet record ID.

      - **`custodyConfigId`** · `string` · example `cfg_example`

        Owning custody configuration ID.

      - **`custodyConnectionId`** · `string` · example `cconn_example`

        Owning Custody Connection ID.

      - **`provider`** · `string` · example `privy`

        Wallet signing provider.

      - **`isRuntimeExecutionAllowed`** · `boolean` · required · example `true`

        Whether 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 · example `privy_wallet_123`

        Provider wallet ID.

      - **`publicKey`** · `string` · required · min length 32 · max length 44 · example `So11111111111111111111111111111111111111112`

        Base58-encoded Solana address.

      - **`label`** · `string | null` · required · example `Treasury wallet`

        Optional wallet label.

      - **`purpose`** · `string | null` · required · example `transfer`

        Optional wallet purpose.

      - **`status`** · `string` · required · one of `active`, `inactive` · example `active`

        Wallet status.

      - **`createdAt`** · `string` · required · format `date-time` · example `2025-01-01T00:00:00.000Z`

        ISO 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 · example `USDC`

          Tracked token symbol.

        - **`mint`** · `string` · required · min length 32 · max length 44 · example `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU`

          Tracked token mint address.

        - **`amount`** · `string` · required · example `1250000`

          Raw token amount as a string.

        - **`uiAmount`** · `string` · required · example `1.25`

          Human-readable token balance.

        - **`decimals`** · `integer` · required · min 0 · example `6`

          Token decimals.

        - **`usdPrice`** · `number` · example `1`

          Resolved USD price per token when available.

        - **`usdValue`** · `number` · example `125.5`

          Resolved USD value of this balance when pricing is available.

      One of:

      - **Owned by a Managed config.** · must include `custodyConfigId` · must not include `custodyConnectionId`

      - **Owned by a Custody Connection.** · must include `custodyConnectionId` · must not include `custodyConfigId`

  - **`meta`** · `object` · required

    Response metadata.

    - **`requestId`** · `string` · required · min length 1 · example `req_example`

      Request identifier for tracing.

    - **`timestamp`** · `string` · required · format `date-time` · example `2025-01-01T00:00:00.000Z`

      ISO 8601 timestamp.

- **`400`** Bad Request

  - **`error`** · `object` · required

    Standard error payload.

    - **`code`** · `string` · required

      Machine-readable error code.

    - **`message`** · `string` · required

      Human-readable error message.

    - **`details`** · `object`

      Optional error details for debugging.

      - **`*`** · `any`

  - **`meta`** · `object`

    - **`requestId`** · `string` · min length 1 · example `req_example`

      Request identifier for tracing.

- **`401`** Unauthorized

  Same fields as `400`.

- **`403`** Forbidden. Requires the `wallets.manage` action: an API key must hold it (a module's `manage` or `execute` also covers its `read`), and a signed-in member's organization role must include it. Without it the error is INSUFFICIENT_PERMISSIONS and `error.details.action` names 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`.

- **`404`** Not Found

  Same fields as `400`.

- **`409`** Conflict

  Same fields as `400`.

- **`422`** Unprocessable Entity

  Same fields as `400`.

- **`500`** Internal Server Error

  Same fields as `400`.

- **`503`** Service Unavailable

  Same fields as `400`.

## Example request

```bash tab="cURL"
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST 'https://api.solana.com/v1/wallets' \
  -H "Authorization: Bearer $SDP_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "privy",
    "label": "Treasury wallet",
    "purpose": "transfer"
  }'
```

```ts tab="TypeScript"
// Reuse this key when you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://api.solana.com/v1/wallets", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SDP_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "provider": "privy",
    "label": "Treasury wallet",
    "purpose": "transfer"
  })
});
```

```python tab="Python"
import os, requests, uuid

# Reuse this key when you retry this request.
idempotency_key = str(uuid.uuid4())
r = requests.post("https://api.solana.com/v1/wallets",
  headers={"Authorization": f"Bearer {os.environ['SDP_API_KEY']}", "Idempotency-Key": idempotency_key},
  json={
    "provider": "privy",
    "label": "Treasury wallet",
    "purpose": "transfer"
  })
```

## Example responses

```json tab="201"
{
  "data": {
    "wallet": {
      "id": "cwlt_example",
      "custodyConfigId": "cfg_example",
      "provider": "privy",
      "isRuntimeExecutionAllowed": true,
      "walletId": "privy_wallet_123",
      "publicKey": "So11111111111111111111111111111111111111112",
      "label": "Treasury wallet",
      "purpose": "transfer",
      "status": "active",
      "createdAt": "2025-01-01T00:00:00.000Z"
    }
  },
  "meta": {
    "requestId": "req_example",
    "timestamp": "2025-01-01T00:00:00.000Z"
  }
}
```

```json tab="403"
{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "Missing action: wallets.manage",
    "details": {
      "action": "wallets.manage"
    }
  },
  "meta": {
    "requestId": "req_example"
  }
}
```

Error responses use the [standard error shape](/docs/reference/conventions/errors).
