# Users

Every account you create through the Partner API belongs to you. It is tagged
with your partner ID, and that tag scopes every read and write: you see your
users and nothing else.

Requires `users:read` to read and `users:write` to create, update or close -
granted by name, or through the
[`all:read` / `all:write` wildcards](/authentication#wildcard-scopes).

## Creating a user

```bash
curl -X POST https://stg-s2s.restake.gg/api/v1/s2s/users \
  -H "authorization: Bearer $ACCESS_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "email": "jane@acme.com",
    "walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
    "userName": "jane.doe",
    "language": "en",
    "countryCode": "BR"
  }'
```

```json title="Response"
{
  "data": {
    "userId": "8f2b1c4e-…",
    "email": "jane@acme.com",
    "walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
    "userName": "jane.doe",
    "language": "en",
    "avatar": null,
    "status": "active",
    "countryCode": "BR",
    "kycLevel": null,
    "createdAt": "2026-08-11T09:14:03.221Z",
    "updatedAt": "2026-08-11T09:14:03.221Z"
  }
}
```

`email` is required; `walletAddress` is optional and can be linked later. Both
must be unique across the **whole platform** - not just across your own users. A
collision answers `409`, which in practice means the person already has a
Restake account.

`userId` is the handle for everything else: reads, updates, and the `userId` the
transaction endpoints take.

## Wallet addresses are set once

A wallet address can be attached to an account that has none, but an existing
one is **never replaced**. Sending a different `walletAddress` in a `PATCH` on a
user who already has one is rejected rather than silently ignored.

This is the same rule the Restake app enforces on wallet linking, and it exists
because the address is what funds move to and from - a "correction" would
retarget a user's entire position history.

## Reading and listing

```bash
# One user
curl "https://stg-s2s.restake.gg/api/v1/s2s/users/$USER_ID" \
  -H "authorization: Bearer $ACCESS_TOKEN"

# Your users, newest first
curl "https://stg-s2s.restake.gg/api/v1/s2s/users?page=1&pageSize=20" \
  -H "authorization: Bearer $ACCESS_TOKEN"
```

`pageSize` defaults to 20 and is capped at 100.

<Callout type="info" title="404, not 403">
  A `userId` that belongs to another partner - or to somebody who signed up
  through the Restake app - answers `404`, exactly as an ID that does not exist
  does. The API will not confirm the existence of an account it will not show
  you, so a `404` is not evidence that the ID is wrong.
</Callout>

## Updating

`PATCH` is a partial update: omitted fields are left untouched. Sending `null`
is not a way to clear a field - the endpoint takes values, and clearing an
identifier a user signs in with is not something an integration should be able
to do by accident.

```bash
curl -X PATCH "https://stg-s2s.restake.gg/api/v1/s2s/users/$USER_ID" \
  -H "authorization: Bearer $ACCESS_TOKEN" \
  -H 'content-type: application/json' \
  -d '{ "userName": "jane.d", "countryCode": "PT" }'
```

## Closing

```bash
curl -X DELETE "https://stg-s2s.restake.gg/api/v1/s2s/users/$USER_ID" \
  -H "authorization: Bearer $ACCESS_TOKEN"
```

```json title="Response"
{ "data": { "deleted": true } }
```

This moves the account to `closed`; it does not delete the row. Sessions,
portfolio history and referrals all point at a user, and those records have to
survive the account being closed. The call is idempotent - closing an already
closed user succeeds.
