# Users & Tokens

## Creating users

When creating users, there are a few important things to keep in mind:

- The `id` field is mandatory, in most cases you want this to be the same ID you use on your database.
- The `role` field is optional, by default it is set to `user` but you can specify any existing role.
- Custom data can be added to users in the `custom` field.
- `name` and `image` are optional and handled by all SDKs automatically to render users.

<tabs groupId="examples">

<tabs-item value="js" label="JavaScript">

```js
const newUser: UserRequest = {
  id: 'user-id',
  role: 'user',
  custom: {
    color: 'red',
  },
  name: 'This is a test user',
  image: 'link/to/profile/image',
};
await client.upsertUsers([newUser]);
```

</tabs-item>

<tabs-item value="py" label="Python">

```py
from getstream.models import UserRequest

client.upsert_users(
    UserRequest(
        id="user_id",
        role="user",
        custom={"color": "red"},
        name="This is a test user",
        image="link/to/profile/image",
    )
)
```

</tabs-item>

<tabs-item value="go" label="Golang">

```go
response, err := client.UpdateUsers(ctx, &getstream.UpdateUsersRequest{
    Users: map[string]getstream.UserRequest{
        "user_id": {
            ID:   "user_id",
            Role: getstream.PtrTo("admin"),
            Custom: map[string]interface{}{
                "color": "red",
            },
            Name:  getstream.PtrTo("This is a test user"),
            Image: getstream.PtrTo("link/to/profile/image"),
        },
    },
})
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
var response = client.updateUsers(
  UpdateUsersRequest.builder()
    .users(Map.of(
      "john",
      UserRequest.builder()
        .id("john")
        .name("john")
        .custom(Map.of("country", "NL"))
        .build()
    ))
    .build())
    .execute();
```

</tabs-item>

<tabs-item value="curl" label="cURL">

```bash
curl -X POST https://video.stream-io-api.com/api/v2/users?api_key=${API_KEY} \
-H "Authorization: ${TOKEN}" \
-H "stream-auth-type: jwt" \
-H "Content-Type: application/json" \
-d '{
  "users": {
    "john": {
      "id": "john",
      "role": "user",
      "custom": {
        "color": "red"
      },
      "name": "John",
      "image": "link/to/profile/image"
    }
  }
}'
```

</tabs-item>

</tabs>

## Updating users

You can update users in two ways:

- Replace updates: replace the entire user object with the one provided to the API call
- Partial update: choose which fields you want to change

<tabs groupId="examples">

<tabs-item value="js" label="JavaScript">

```js
const user: UserRequest = {
  id: 'userid',
  role: 'user',
  custom: {
    color: 'blue',
  },
  name: 'This is a test user',
  image: 'link/to/profile/image',
};
client.upsertUsers([user]);

// or
client.updateUsersPartial({
  users: [
    {
      id: user.id,
      set: {
        'new-field': 'value',
      },
      unset: ['name'],
    },
  ],
});
```

</tabs-item>

<tabs-item value="py" label="Python">

```py
client.upsert_users(UserRequest(
  id= 'userid',
  role= 'user',
  custom= {
    "color": 'blue',
  },
  name= 'This is a test user',
  image= 'link/to/profile/image',
))

# or
client.update_users_partial(
    users=[
        UpdateUserPartialRequest(
            id="userid",
            set={
                "new-field": "value",
            },
            unset=["name"],
        )
    ],
)
```

</tabs-item>

<tabs-item value="go" label="Golang">

```go
response, err := client.UpdateUsers(ctx, &getstream.UpdateUsersRequest{
    Users: map[string]getstream.UserRequest{
        "user_id": {
            ID:   "user_id",
            Role: getstream.PtrTo("user"),
            Custom: map[string]any{
                "color": "blue",
            },
            Name:  getstream.PtrTo("This is a test user"),
            Image: getstream.PtrTo("link/to/profile/image"),
        },
    },
})

// or
response, err = client.UpdateUsersPartial(ctx, &getstream.UpdateUsersPartialRequest{
    Users: []getstream.UpdateUserPartialRequest{
        {
            ID: "user_id",
            Set: map[string]interface{}{
                "new-field": "value",
            },
            Unset: []string{"name"},
        },
    },
})
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
var userRequest = UserRequest.builder()
        .id("john")
        .name("john")
        .custom(Map.of("country", "NL"))
        .build();

// Upserting a user, all existing user data will be overridden
client.updateUsers(
  UpdateUsersRequest.builder()
    .users(Map.of(
      userRequest.getId(),
      userRequest
    ))
    .build()
).execute();

// or using partial update
client.updateUsersPartial(
  UpdateUsersPartialRequest.builder()
    .users(List.of(
      UpdateUserPartialRequest.builder()
        .id(userRequest.getId())
        .set(Map.of("country", "US"))
        .build()
    ))
    .build()
).execute();
```

</tabs-item>

<tabs-item value="curl" label="cURL">

```bash
# Upserting a user, all existing user data will be overridden
curl -X POST https://video.stream-io-api.com/api/v2/users?api_key=${API_KEY} \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt" \
  -H "Content-Type: application/json" \
  -d '{
    "users": {
      "john": {
        "id": "john",
        "role": "user",
        "custom": {
          "color": "red"
        },
        "name": "John",
        "image": "link/to/profile/image"
      }
    }
  }'

# Partial update
curl -X PATCH https://video.stream-io-api.com/api/v2/users?api_key=${API_KEY} \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt" \
  -H "Content-Type: application/json" \
  -d '{
    "users": [
      {
        "id": "john",
        "set": {
          "color": "blue"
        },
        "unset": ["name"]
      }
    ]
  }'
```

</tabs-item>

</tabs>

## Anonymous users

Anonymous users are users that are not authenticated. It's common to use this for watching a livestream or similar where you aren't authenticated. Anonymous users can be connected using client-side SDKs. Anonymous users are not counted toward your MAU.

## Guest users

Guest users are temporary user accounts. You can use it to temporarily give someone a name and image when joining a call. Guest users can be created client-side. Guest users are counted towards your MAU usage.

## Deactivating and deleting users

Depending on your use-case, you can choose to delete users or de-activating them. There are some differences between these two approach.

Deactivating users:

- the user will not be allowed to perform API requests / connect
- user data is retained on Stream's side and returned from API
- deactivated users can be re-activated

Deleting users:

- the user will no longer be able to perform API requests / connect
- the user is deleted and by default not returned from API
- all data from the user is marked as deleted
- by default the data is retained and "soft" deleted, you can optionally request hard deletion
- deletion is not reversible

Note: Both deletion and deactivation are performed asynchronously by Stream API. A task ID is returned and you can use that to check the status of its processing.

### Deactivating users

<tabs groupId="examples">

<tabs-item value="js" label="JavaScript">

```js
client.deactivateUser({
  user_id: '<id>',
});

// reactivate
client.reactivateUsers({
  user_ids: ['<id>'],
});

// deactivating users in bulk is performed asynchronously
const deactivateResponse = client.deactivateUsers({
  user_ids: ['<id1>', '<id2>'...],
});
```

</tabs-item>

<tabs-item value="py" label="Python">

```py
# deactivate one user
client.deactivate_user(user_id=alice.id)

# reactivates the user
client.reactivate_user(user_id=alice.id)

# deactivating users in bulk is performed asynchronously
response = client.deactivate_users(user_ids=[alice.id, bob.id])
```

</tabs-item>

<tabs-item value="go" label="Golang">

```go
// deactivate one user
response, err := client.DeactivateUser(ctx, "alice", &getstream.DeactivateUserRequest{})

// reactivates the user
_, err = client.ReactivateUser(ctx, "alice", &getstream.ReactivateUserRequest{})

// deactivates users in bulk, this is an async operation
_, err = client.DeactivateUsers(ctx, &getstream.DeactivateUsersRequest{
    UserIds: []string{"alice", "bob"},
})
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
// deactivate one user
client.deactivateUser("john", DeactivateUserRequest.builder().build()).execute();

// reactivates the user
client.reactivateUser("john", ReactivateUserRequest.builder().build()).execute();

// deactivates users in bulk, this is an async operation
client.deactivateUsers(DeactivateUsersRequest.builder().userIds(List.of("<id1>", "<id2>")).build()).execute();
```

</tabs-item>

<tabs-item value="curl" label="cURL">

```bash
# Deactivate users
curl -X POST https://video.stream-io-api.com/api/v2/users/deactivate?api_key=${API_KEY} \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["sara"]
  }'

# Reactivate users
curl -X POST https://video.stream-io-api.com/api/v2/users/reactivate?api_key=${API_KEY} \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["sara"]
  }'
```

</tabs-item>

</tabs>

Deactivating users in bulk can take some time, this is how you can check the progress:

<tabs groupId="examples">

<tabs-item value="js" label="JavaScript">

```js
// Example of monitoring the status of an async task
// The logic is same for all async tasks
const response = await client.exportUsers({
  user_ids: ["<user id1>", "<user id1>"],
});

// you need to poll this endpoint
const taskResponse = await client.getTask({ id: response.task_id });

console.log(taskResponse.status === "completed");
```

</tabs-item>

<tabs-item value="py" label="Python">

```py
# Example of monitoring the status of an async task
# The logic is same for all async tasks
response = client.export_users(user_ids=["<user id1>", "<user id1>"])
task_id = response.data.task_id

# get information about the task
task_status = client.get_task(task_id)

# just an example, in reality it can take a few seconds for a task to be processed
if task_status.data.status == "completed":
    print(task_status.data.result)
```

</tabs-item>

<tabs-item value="go" label="Golang">

```go
// Example of monitoring the status of an async task
// The logic is same for all async tasks
response, err := client.ExportUsers(ctx, &getstream.ExportUsersRequest{
    UserIds: []string{"<user id1>", "<user id1>"},
})
taskID := response.Data.TaskID

// get information about the task
taskStatus, err := client.GetTask(ctx, taskID, &getstream.GetTaskRequest{})

// just an example, in reality it can take a few seconds for a task to be processed
if taskStatus.Data.Status == "completed" {
    println("Export is completed")
}
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
var response = client.exportUsers(ExportUsersRequest.builder().userIds(List.of("user-id1", "user-id2")).build()).execute();
var taskID = response.getData().getTaskID();

// get information about the task
var taskStatus = client.getTask(taskID).execute();

// just an example, in reality it can take a few seconds for a task to be processed
if (taskStatus.getData().getStatus().equals("completed")) {
    System.out.println("Export is completed");
}
```

</tabs-item>

<tabs-item value="curl" label="cURL">

```bash
# When an operation is async, a task_id will be included in the API response
# That task_id can be used to monitor the status of the task
# When finished, task status will be completed
curl -X GET https://video.stream-io-api.com/api/v2/tasks/${TASK_ID}?api_key=${API_KEY} \
    -H "Authorization: ${TOKEN}" \
    -H "stream-auth-type: jwt"
```

</tabs-item>

</tabs>


For more information, please refer to the [async operations guide](/video/docs/api/misc/async/)


### Deleting users

<tabs groupId="examples">

<tabs-item value="js" label="JavaScript">

```js
client.deleteUsers({ user_ids: ["<id>"] });

//restore
client.restoreUsers({ user_ids: ["<id>"] });
```

</tabs-item>

<tabs-item value="py" label="Python">

```py
client.delete_users(user_ids=["<id>"])

# restore
client.restore_users(user_ids=["<id>"])
```

</tabs-item>

<tabs-item value="go" label="Golang">

```go
response, err := client.DeleteUsers(ctx, &getstream.DeleteUsersRequest{UserIds: []string{"<id>"}})

// restore a soft-deleted user
_, err = client.RestoreUsers(ctx, &getstream.RestoreUsersRequest{UserIds: []string{"<id>"}})
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
client.deleteUsers(DeleteUsersRequest.builder().userIds(List.of("user-id1", "user-id2")).build()).execute();

// restore a soft-deleted users
client.restoreUsers(RestoreUsersRequest.builder().userIds(List.of("user-id1", "user-id2")).build()).execute();
```

</tabs-item>

<tabs-item value="curl" label="cURL">

```bash
# Delete users
curl -X POST https://video.stream-io-api.com/api/v2/users/delete?api_key=${API_KEY} \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["sara"]
  }'

# Restore users
curl -X POST https://video.stream-io-api.com/api/v2/users/restore?api_key=${API_KEY} \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt" \
  -H "Content-Type: application/json" \
  -d '{
    "user_ids": ["sara"]
  }'
```

</tabs-item>

</tabs>

The delete users endpoints supports the following parameters to control which data needs to be deleted and how. By default users and their data are soft-deleted.

| Name                   | Type                       | Description                                                                                                                                                                                                                                                                                                   | Optional |
| ---------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `user`                 | Enum (soft, pruning, hard) | - Soft: marks user as deleted and retains all user data. <br /> - Pruning: marks user as deleted and nullifies user information. <br /> - Hard: deletes user completely - this requires hard option for messages and conversation as well.                                                                    | Yes      |
| `conversations`        | Enum (soft, hard)          | - Soft: marks all conversation channels as deleted (same effect as Delete Channels with 'hard' option disabled). <br /> - Hard: deletes channel and all its data completely including messages (same effect as Delete Channels with 'hard' option enabled).                                                   | Yes      |
| `messages`             | Enum (soft, pruning, hard) | - Soft: marks all user messages as deleted without removing any related message data. <br /> - Pruning: marks all user messages as deleted, nullifies message information and removes some message data such as reactions and flags. <br /> - Hard: deletes messages completely with all related information. | Yes      |
| `new_channel_owner_id` | string                     | Channels owned by hard-deleted users will be transferred to this userID. If you doesn't provide a value, the channel owner will have a system generated ID like `delete-user-8219f6578a7395g`                                                                                                                 | Yes      |
| `calls`                | Enum (soft, hard)          | - Soft: marks calls and related data as deleted. <br /> - Hard: deletes calls and related data completely <br /> Note that this applies only to 1:1 calls, not group calls                                                                                                                                    | Yes      |

Deleting users is done asynchronously and and can take some time to complete. You can find more information on how to work with API endpoints performing async work in the [async operations guide](/video/docs/api/misc/async/).


## User tokens

Stream uses JWT (JSON Web Tokens) to authenticate users, enabling them to log in. Knowing whether a user is authorized to perform certain actions is managed separately via a role-based permissions system. Tokens need to be generated server-side.

You can optionally provide an expiration time.

<tabs groupId="examples">

<tabs-item value="js" label="JavaScript">

```js
const userId = "john";
// validity is optional, in this case we set it to 1 day
const validity = 24 * 60 * 60;
client.generateUserToken({ user_id: userId, validity_in_seconds: validity });
```

</tabs-item>

<tabs-item value="py" label="Python">

```py
# in this example we use Django
# but you can use any framework you like
import time
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse

# The user token endpoint is protected with the login_required decorator.
@login_required
def create_user_token(request):
    # The 'user_id' is retrieved from the request's user instance.
    user_id = request.user.id

    # the token will be valid for 1 day
    exp = 24 * 3600

    # Here client is Stream client and it's called with the 'user_id' and the expiration time.
    token = client.create_token(user_id, expiration=exp)

    # The token is then returned in the response.
    return JsonResponse({"token": token})

```

</tabs-item>

<tabs-item value="go" label="Golang">

```go
// the token will be valid for 1 day
token, err := client.CreateToken("user-id", getstream.WithExpiration(24*time.Hour))
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
// the token will be valid for 1 day
var userToken = client.tokenBuilder().createToken("john", 24*60*60);
```

</tabs-item>

<tabs-item value="bash" label="Bash">

```bash
HEADER=$(echo -n '{"alg": "HS256", "typ": "JWT"}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=');
USER_ID='<user id>';
CURRENT_TIMESTAMP=$(date +%s);
HOUR_FROM_NOW=$((CURRENT_TIMESTAMP + 3600))
PAYLOAD=$(echo -n '{"user_id": "'${USER_ID}'", "iat": '${CURRENT_TIMESTAMP}', "exp": '${HOUR_FROM_NOW}'}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=');
SECRET='<API secret>';
SIGNATURE=$(echo -n ${HEADER}.${PAYLOAD} | openssl dgst -sha256 -hmac ${SECRET} -binary | openssl base64 -e -A | tr '+/' '-_' | tr -d '=');

echo "${HEADER}.${PAYLOAD}.${SIGNATURE}"
```

</tabs-item>

</tabs>

You need to provide the generated tokens to the client SDKs. Stream SDKs accept a token provider, that can be called to retrieve and renew tokens. You need to implement the token provider in your own application, this is usually an HTTP endpoint.

## Call tokens

Call tokens contain a list of call IDs. When a user utilizes a call token, they will automatically be assigned the membership role for all the calls specified in the token’s claims. Additionally, the token may optionally include alternative roles, such as admin or moderator.

<tabs groupId="examples">

<tabs-item value="js" label="JavaScript">

```js
const user_id = "john";
// validity is optional (by default the token is valid for an hour)
const validity_in_seconds = 60 * 60;

const call_cids = ["default:call1", "livestream:call2"];

client.generateCallToken({ user_id, call_cids, validity_in_seconds });

// Optionally provide a role for the call(s)
client.generateCallToken({ user_id, call_cids, role: "admin" });
```

</tabs-item>

<tabs-item value="py" label="Python">

```py
user_id = "john"

# exp and iat are optional, token will be valid for 1 hour
exp = 3600

call_cids = ["default:call1", "livestream:call2"]

client.create_call_token(user_id=user_id, expiration=exp, call_cids=call_cids)
```

</tabs-item>

<tabs-item value="go" label="Golang">

```go
// the list of call IDs this token applies to
tokenClaims := getstream.Claims{CallCIDs: []string{"default:call1", "livestream:call2"}}

token, err := client.CreateToken("john",
    getstream.WithClaims(tokenClaims),
    getstream.WithExpiration(24*time.Hour),
)
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
// call tokens
var callToken = client.tokenBuilder().createCallToken(CallTokenClaims.builder()
  .userID("user-id").callCIDs(List.of("default:call1", "livestream:call2"))
  // expiration is optional
  .expiresAt(Instant.now().plus(24, ChronoUnit.HOURS))
  .build());
```

</tabs-item>

<tabs-item value="bash" label="Bash">

```bash
HEADER=$(echo -n '{"alg": "HS256", "typ": "JWT"}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=');
USER_ID='<user id>';
CURRENT_TIMESTAMP=$(date +%s);
HOUR_FROM_NOW=$((CURRENT_TIMESTAMP + 3600))
CALL_CID1='livestream:1';
CALL_CID2='livestream:2';
# Optionally provide a role for the call(s)
ROLE='admin'
PAYLOAD=$(echo -n '{"user_id": "'${USER_ID}'", "role": "'${ROLE}'", "call_cids": ["'${CALL_CID1}'", "'${CALL_CID2}'"], "iat": '${CURRENT_TIMESTAMP}', "exp": '${HOUR_FROM_NOW}'}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=');
SECRET='<API secret>';
SIGNATURE=$(echo -n ${HEADER}.${PAYLOAD} | openssl dgst -sha256 -hmac ${SECRET} -binary | openssl base64 -e -A | tr '+/' '-_' | tr -d '=');

echo "${HEADER}.${PAYLOAD}.${SIGNATURE}"
```

</tabs-item>

</tabs>

> **Note:** Call tokens are designed to grant additional access, not restrict it. Most call types let regular users join calls. If all users can access any call, call tokens won't change this. Remove call access from the user role and grant it to specific members instead.


---

This page was last updated at 2026-07-17T15:25:41.262Z.

For the most recent version of this documentation, visit [https://getstream.io/video/docs/api/authentication/](https://getstream.io/video/docs/api/authentication/).