> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cabalspy.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Create or update a user

> POST /v1/platform-users: add one of your users by your own id with their existing wallets, or update an existing one. POST /v1/platform-users/update changes the display name or avatar.

Adds one of your users as a [platform user](https://docs.cabalspy.xyz/cabalspy-id/white-label.md), by your own id, with the wallets they already have. If a user with this `external_id` exists, it is updated instead: profile fields you pass are changed and new wallets are added. Wallets are never removed by this call.

Call it from your sign-up flow, or whenever a user adds a wallet on your side. It is safe to call repeatedly with the same data.

> [!TIP]
>
> A new user needs at least one wallet. Wallets you send are unverified and count straight away unless your instance sets `wallet_trust` to `verified_only`. See [Unverified and verified wallets](https://docs.cabalspy.xyz/cabalspy-id/white-label.md#unverified-and-verified-wallets).

## Body

**external\_id** `string` *required*

Your id of the user. 1 to 128 printable ASCII characters, no spaces.

**wallets** `array`

The user's wallets, up to 10. Required to create a user. Each item is an object or just the address as a string.

**wallet**

**address** `string` *required*

A Solana or EVM address.

**chains** `array`

Chains to track it on. Default: `solana` for a Solana address, every supported EVM chain (bnb, base, eth, rh) for an EVM address. A single chain can be passed as a string.

**source** `string`

Where the wallet comes from, for example `privy`. 1 to 40 characters a-z 0-9 \_ . -

**display\_name** `string`

Display name, 1 to 60 characters. null clears it.

**avatar\_url** `string`

Avatar as an https URL, at most 500 characters. null clears it.

## Usage Example

```bash cURL theme={null}
curl -X POST "https://api-id.cabalspy.xyz/v1/platform-users" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "user_8412",
    "display_name": "Maze",
    "avatar_url": "https://cdn.example.com/avatars/8412.png",
    "wallets": [
      {"address": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW", "source": "privy"},
      {"address": "0x4b1e9c0d2a7f3e5b8c6d1a9f0e2b7c4d5a6e8f01", "chains": ["base"]}
    ]
  }'
```

```python Python theme={null}
import requests

r = requests.post("https://api-id.cabalspy.xyz/v1/platform-users", json={
    "external_id": "user_8412",
    "display_name": "Maze",
    "wallets": [{"address": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW", "source": "privy"}],
}, headers={"Authorization": "Bearer YOUR_KEY"})
d = r.json()["data"]
print(r.status_code, d["created"], d["csid_id"], d["wallets_added"])
```

```javascript JavaScript theme={null}
const res = await fetch("https://api-id.cabalspy.xyz/v1/platform-users", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({
    external_id: "user_8412",
    wallets: [{ address: "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW", source: "privy" }],
  }),
});
const { data } = await res.json();
console.log(res.status, data.created, data.csid_id);
```

## Response

**201** when the user was created, **200** when an existing user was updated.

**data** `object`

The [user object](https://docs.cabalspy.xyz/cabalspy-id/platform-users-get.md#response), plus:

**extra fields**

**created** `boolean`

True when this call created the user.

**wallets\_added** `array`

The wallets and chains that are new with this call: address, family, chains and source. Empty when nothing new was added.

```json Response theme={null}
{
  "success": true,
  "data": {
    "csid_id": "3f9a1c2e-5b7d-4e8f-a1b2-c3d4e5f60718",
    "external_id": "user_8412",
    "claimed": false,
    "claimed_at": null,
    "managed": true,
    "display_name": "Maze",
    "avatar_url": "https://cdn.example.com/avatars/8412.png",
    "limited": false,
    "x": null,
    "telegram": null,
    "kol": { "badge": false, "since": null },
    "wallets": [
      {
        "address": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
        "family": "solana",
        "chains": ["solana"],
        "verified": false,
        "verified_chains": [],
        "status": "active",
        "counts": true,
        "source": "privy",
        "verified_at": null,
        "added_at": "2026-10-24T09:12:40Z"
      }
    ],
    "pending_verifications": [],
    "created_at": "2026-10-24T09:12:40Z",
    "created": true,
    "wallets_added": [
      {
        "address": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
        "family": "solana",
        "chains": ["solana"],
        "source": "privy"
      }
    ]
  },
  "meta": {
    "request_id": "req_5162c7cf7f87",
    "cached": false,
    "cache_age_seconds": 0,
    "version": "v1",
    "timestamp": "2026-10-24T09:12:40Z"
  }
}
```

A new user triggers the `platform_user.created` and `wallet.added` [webhooks](https://docs.cabalspy.xyz/cabalspy-id/webhooks.md#white-label-events).

## Update a user

`POST /v1/platform-users/update` or `PATCH /v1/platform-users`

Changes the display name and/or avatar of an existing user without touching the wallets. Returns the [user object](https://docs.cabalspy.xyz/cabalspy-id/platform-users-get.md#response).

**external\_id** `string` *required*

Your id of the user. Alternatively pass csid\_id.

**display\_name** `string`

Display name, 1 to 60 characters. null clears it.

**avatar\_url** `string`

Avatar as an https URL, at most 500 characters. null clears it.

Pass at least one of display\_name and avatar\_url.

```bash cURL theme={null}
curl -X PATCH "https://api-id.cabalspy.xyz/v1/platform-users" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "user_8412", "display_name": "Maze 2"}'
```

```python Python theme={null}
import requests

r = requests.post("https://api-id.cabalspy.xyz/v1/platform-users/update",
                  json={"external_id": "user_8412", "avatar_url": None},
                  headers={"Authorization": "Bearer YOUR_KEY"})
print(r.json()["data"]["avatar_url"])
```

```javascript JavaScript theme={null}
const res = await fetch("https://api-id.cabalspy.xyz/v1/platform-users/update", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ external_id: "user_8412", display_name: "Maze 2" }),
});
const { data } = await res.json();
console.log(data.display_name);
```

## Errors

| Status | Code | Meaning |
| - | - | - |
| 400 | missing\_parameter | external\_id missing, or nothing to update. |
| 400 | invalid\_parameter | external\_id, display\_name, avatar\_url or a wallet's chains or source invalid, or more than 10 wallets. |
| 400 | wallet\_required | A new user needs at least one wallet. |
| 400 | invalid\_address | A wallet is not a valid Solana or EVM address. |
| 401 | missing\_api\_key | API key missing. |
| 403 | invalid\_api\_key | API key invalid or not enabled for CabalSpy ID. |
| 403 | plan\_user\_limit | Your plan's user limit is reached, or 25 users on a sandbox or development instance. |
| 404 | platform\_user\_not\_found | Update: no user with this id in your instance. |
| 409 | account\_claimed | The person claimed the account and manages it themselves. |
| 409 | too\_many\_wallets | The user would have more than 5 Solana or 5 EVM wallets. |
| 429 | rate\_limited | More than 2,000 new platform users per minute. |
| 429 | rate\_limit\_exceeded | Rate limit exceeded. |

See [Errors](https://docs.cabalspy.xyz/cabalspy-id/errors.md) for the full list.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.