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

# Local SDK User Management

## Overview

Hand model and tactile calibration data belongs to the current SDK user. When several people share one glove on the same computer, create a separate user for each person to keep calibration data separate.

User information is stored locally and is not uploaded. The current user is written to a configuration file shared by Python and C, so a switch takes effect in other processes immediately.

Confirm the current user before calibration. This page covers user creation, switching, deletion, and data transfer.

Matching examples: `examples/python/wuji_glove/4.user.py` and `examples/c/wuji_glove/3_user_management.c`.

For the concept and basic usage, see [Device Parameters](/en/docs/wuji-sdk/device-params#sdk-user-management).

## Prerequisites

### Hardware

User management reads and writes local data and doesn't need a glove.

### Software

| Item | Requirement |
| - | - |
| Operating system | Ubuntu 22.04 LTS or later |
| Wuji SDK | v2026.8.31 or later, the version used for this page |
| Python | 3.10 or later |
| C toolchain | CMake 3.16 or later, plus the extracted SDK package |

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install wuji-sdk
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    cmake -S examples/c/wuji_glove -B build/glove \
      -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
      -DWUJI_SDK_LIB="${SDK}/lib/libwuji_sdk_c.so"
    cmake --build build/glove --target 3_user_management
    ```
  </Tab>
</Tabs>

## Local Storage Layout

The SDK uses `~/.wuji/sdk/` for the following data:

```text theme={null}
~/.wuji/sdk/
├── storage.json                    # SDK-level state
├── users/
│   ├── registry.toml               # user registry plus the current-user pointer
│   └── <user_id>/                  # each user's private directory
├── params/
│   └── <serial>.toml               # parameters filed per glove
└── models/
    └── <serial>_hand.urdf          # the hand model calibration produces
```

Example `registry.toml`:

```toml theme={null}
current_user_id = "u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

[[users]]
user_id = ""
display_name = "Default"
is_default = true
created_at = "2026-07-14T13:05:53.428368166+00:00"
updated_at = "2026-07-14T13:05:53.428368166+00:00"

[[users]]
user_id = "u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
display_name = "alice"
is_default = false
created_at = "2026-07-14T13:05:53.428376205+00:00"
updated_at = "2026-07-14T13:05:53.428376205+00:00"
```

The default user's `user_id` is an empty string, not `"default"` or `None`. `current_user_id` is stored in this file, so user switching persists.

## Data Flow

```mermaid theme={null}
flowchart TD
    PY["Python process<br/>SdkManager"]
    C["C process<br/>wuji_user_*"]
    REG["~/.wuji/sdk/users/registry.toml<br/>user table plus current-user pointer"]
    CAL["calibration"]
    MOD["~/.wuji/sdk/models/<br/>hand models by serial"]
    PAR["~/.wuji/sdk/params/<br/>parameters by serial"]

    PY -->|"read and write"| REG
    C -->|"read and write"| REG
    REG -->|"the current user decides where writes land"| CAL
    CAL --> MOD
    CAL --> PAR
```

## API Reference

The user management interfaces are:

| Purpose | Python | C |
| - | - | - |
| Read the current user | `current_user()` | `wuji_current_user(&out)` |
| List them all | `list_users()` | `wuji_list_users(&arr, &len)` |
| Create one | `create_user(display_name, description=None)` | `wuji_create_user(name, desc, external_id, &out)` |
| Update details | `update_user(user_id, description=...)` | `wuji_update_user(user_id, &update, &out)` |
| Switch | `switch_user(user_id)` | `wuji_switch_user(user_id, &out)` |
| Switch back to default | `switch_to_default_user()` | `wuji_switch_to_default_user(&out)` |
| Delete | `delete_user(user_id)` | `wuji_delete_user(user_id)` |
| Export a calibration bundle | `export_user_data(user_id, out_path)` | `wuji_user_data_export(user_id, out_zip_path)` |
| Preview a bundle | `preview_user_data(bundle_path)` | `wuji_user_data_preview(zip_path)` |
| Import a bundle | `import_user_data(...)` | `wuji_user_data_import(zip_path)` |

Python and C provide equivalent user management capabilities. Use the export, preview, and import interfaces to move calibration data between computers.

On the Python side, the user dict holds `user_id`, `display_name`, `description`, `is_default`, `created_at`, `updated_at`, and `external_id`. The C `WujiUserInfo` struct uses the same field names.

The SDK allocates strings in `WujiUserInfo` output values. Free a single structure with `wuji_user_info_free(&user)` and an array with `wuji_user_info_array_free(users, len)`. Do not mix these functions.

Besides `display_name`, `description`, and `external_id`, `WujiUserUpdate` carries the booleans `clear_description` and `clear_external_id` for setting a field to empty. Passing NULL means "leave it alone," not "clear it."

## Behavior and Error Handling

The following behavior applies to the user management interfaces.

### User Names Must Be Unique

```text theme={null}
WujiException: Internal error: SDK user display_name 'PracticeDoc' already exists
```

The SDK guarantees unique `display_name` values. The duplicate-match branch in the example is not reached:

```python theme={null}
matches = [u for u in manager.list_users() if u.get("display_name") == display_name]
if len(matches) > 1:
    raise RuntimeError(f"Multiple SDK users named {display_name!r}. ...")
```

Callers don't need an additional branch for duplicate results.

Callers still need to handle a name collision during creation. Concurrent creates can race, so catch `WujiException`.

### Deleting the Current User

After deletion, the SDK switches to the default user:

| Action | `current_user()` afterward |
| - | - |
| Switch to `PracticeDoc`, then delete `PracticeDoc` | `display_name='Default'`, `user_id=''`, `is_default=True` |

The operation doesn't leave a dangling current-user pointer. It also deletes that user's calibration data, so confirm before deleting.

### The Default User Can't Be Deleted

```text theme={null}
WujiException: Internal error: default SDK user cannot be deleted
```

### A Nonexistent user\_id Raises an Error

```text theme={null}
WujiException: Internal error: SDK user 'u_does_not_exist' not found
```

The SDK doesn't switch to the default user automatically.

### Switching Takes Effect Across Processes and Languages

For example:

1. The C program runs `./3_user_management --default-user`, switching to Default
2. A brand-new Python process reads `current_user()`

The result is `Default` with a `user_id` of `''`. Going the other way, a user created by the C program's `--user-name CDocTest` shows up in Python's `list_users()` immediately, with non-ASCII text in `description` intact:

```text theme={null}
user list: [('Default', None), ('alice', None), ('CDocTest', 'verified from C')]
```

Both interfaces read and write the same `registry.toml` and don't maintain separate caches.

### Export Requires Calibration Data

Calling `export_user_data` for a user without calibration data:

```text theme={null}
WujiException: Internal error: no calibration data found for user alice
```

Both arguments in `export_user_data(user_id, out_path)` are required.

## Error Handling Quick Reference

| Branch point | Behavior | What to do |
| - | - | - |
| display\_name collides at creation | Raises `WujiException` | Check with `list_users()` first, or catch the exception and switch instead |
| Deleting the current user | Allowed, falls back to the default user | Confirm you no longer need the calibration data |
| Deleting the default user | Raises `WujiException` | The default user is the fallback and can't be removed |
| Switching to a nonexistent user\_id | Raises `WujiException` | Verify against `list_users()` |
| Exporting an uncalibrated user | Raises `WujiException` | Calibrate first |
| Several processes changing users at once | Last write wins | Switching users is infrequent, so avoid concurrency |

Command-line tools can catch `WujiException` and exit with a non-zero status. Display the exception message without printing the full traceback.

## Basic Operations

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from wuji_sdk import SdkManager, WujiException, set_log_level

    set_log_level("warn")
    manager = SdkManager.instance()

    # 1. See who's current. Always do this before calibrating
    current = manager.current_user()
    print(f"current user: {current['display_name']} ({current['user_id']!r})")

    # 2. Look up by name, create if missing
    name = "Alice"
    matches = [u for u in manager.list_users() if u["display_name"] == name]
    if matches:
        user = matches[0]
    else:
        user = manager.create_user(name, description="right hand")

    # 3. Switch. This writes registry.toml and applies across processes
    manager.switch_user(user["user_id"])

    # 4. Update the description
    manager.update_user(user["user_id"], description="right hand, autumn 2026")

    # 5. Switch back to the default user
    manager.switch_to_default_user()

    # 6. Delete. The default user can't be deleted, and deleting the
    #    current user falls back to default
    manager.delete_user(user["user_id"])
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    #include "wuji_sdk.h"

    /* 1. Initialize. User operations need no device connection */
    WujiInitOptions opts = { .log_level = 2 };
    wuji_init(&opts);

    /* 2. See who's current. Free the strings inside every WujiUserInfo */
    WujiUserInfo current = {0};
    if (wuji_current_user(&current) == WUJI_STATUS_OK) {
        printf("current user: %s (user_id='%s', is_default=%d)\n",
               current.display_name, current.user_id, current.is_default);
        wuji_user_info_free(&current);
    }

    /* 3. List every user. Arrays use their own free */
    WujiUserInfo *users = NULL;
    size_t n_users = 0;
    if (wuji_list_users(&users, &n_users) == WUJI_STATUS_OK) {
        for (size_t i = 0; i < n_users; i++) {
            printf("  %s user_id='%s' desc='%s'\n", users[i].display_name,
                   users[i].user_id,
                   users[i].description ? users[i].description : "None");
        }
        wuji_user_info_array_free(users, n_users);
    }

    /* 4. Create a user. The third argument is an optional external_id */
    WujiUserInfo created = {0};
    if (wuji_create_user("Alice", "right hand", NULL, &created) != WUJI_STATUS_OK) {
        fprintf(stderr, "create failed: %s\n", wuji_last_error());
    }

    /* 5. Switch. Switch calls return the resulting user by out parameter */
    WujiUserInfo switched = {0};
    wuji_switch_user(created.user_id, &switched);
    wuji_user_info_free(&switched);

    /* 6. Update the description. Set clear_description to true to empty it */
    WujiUserUpdate update = { .description = "right hand, autumn 2026" };
    WujiUserInfo updated = {0};
    if (wuji_update_user(created.user_id, &update, &updated) == WUJI_STATUS_OK) {
        wuji_user_info_free(&updated);
    }

    /* 7. Switch back to the default user */
    WujiUserInfo dflt = {0};
    wuji_switch_to_default_user(&dflt);
    wuji_user_info_free(&dflt);

    /* 8. Delete */
    wuji_delete_user(created.user_id);
    wuji_user_info_free(&created);

    wuji_shutdown();
    ```
  </Tab>
</Tabs>

User operations need no device connection. Call them right after `wuji_init()`.

## Run the Examples

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/wuji_glove/4.user.py --list
    python examples/python/wuji_glove/4.user.py --user-name Alice --user-description "right hand"
    python examples/python/wuji_glove/4.user.py --default-user
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    ./build/glove/3_user_management --list
    ./build/glove/3_user_management --user-name Alice --user-description "right hand"
    ./build/glove/3_user_management --default-user
    ```
  </Tab>
</Tabs>

Measured output from the C side's `--list`:

```text theme={null}
previous current user:
  user_id:      'u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  display_name: alice
  description:  None
  is_default:   False
current SDK user:
  user_id:      'u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  display_name: alice
  description:  None
  is_default:   False

SDK users:
  Default user_id='' description=None
* alice user_id='u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' description=None
```

When you create a user, the `previous` and `current` blocks differ:

```text theme={null}
previous current user:
  display_name: alice
current SDK user:
  user_id:      'u_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
  display_name: CDocTest
  description:  verified from C
  is_default:   False
```

Both sides print identical formats, which the examples align deliberately. The `*` marks the current user.

These are real results from SDK v2026.8.31 with no device connected. Every user created during verification has been deleted, and the current user is back to what it was.

## Usage Recommendations

### Confirm the User Before Calibrating

This is the whole reason for this page. Calibration output lands under the current user, so the wrong user overwrites someone else's data, silently. Add one line at the top of your calibration script:

```python theme={null}
print(f"calibration will write to user: {SdkManager.instance().current_user()['display_name']}")
```

### Move Calibration Results to Another Machine

`export_user_data` packs the bundle, `preview_user_data` shows you what's inside on the target machine, and `import_user_data` brings it in once you're satisfied. Importing recreates the user as needed, so you don't have to create a matching user by hand first.

### Use external\_id to Link an External System

The user dict carries an `external_id` field that neither example uses. When you connect your own account system, store your external primary key there instead of matching on `display_name`.

### Keep Scripts Non-Interactive

Both examples are one-shot command-line tools, which suits dropping them into the shell script around your calibration flow. `--list` exits 0 on success and 1 on failure, so you can branch on that.

## Related

* [Wuji Glove Hand Model Calibration](/en/docs/wuji-glove/sdk-practice/hand-model-calibration/): produces the hand model filed under the current user
* [Wuji Glove Tactile Calibration](/en/docs/wuji-glove/sdk-practice/tactile-calibration/): also filed by user and serial number
* [Wuji Glove Custom Hand Model](/en/docs/wuji-glove/sdk-practice/custom-hand-model/): name a hand model directly, bypassing the user system
* [Wuji Glove Offline Processing](/en/docs/wuji-glove/sdk-practice/offline-replay/): loads the hand model by serial number during replay


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