---
url: /sdk-api-user.md
description: >-
  Read user profiles, manage personal access tokens, resolve user identities,
  and search the user directory with the HubUserClient.
---

# HubUserClient

> Part of the [Kipu Quantum Hub API SDK reference](./sdk-api.md) - see the landing page for [installation](./sdk-api.md#installation) and [Python credential helpers](./sdk-api.md#python-credential-helpers).

`HubUserClient` talks to the user-service.
Use it to read the currently-authenticated user's profile, manage personal access tokens, resolve user identities, and search the user directory.

## Authentication

`HubUserClient` authenticates with a personal access token sent as the `X-Auth-Token` request header.
The same Python credential helpers documented under [Python credential helpers](./sdk-api.md#python-credential-helpers) work here - pass the resolved token via `api_key`.

The `users`, `user_registration_status`, and `authentication` namespaces are public/registration-oriented and do not require an authenticated principal; the remaining namespaces operate on the current user and do.

## Quickstart

Fetch the current user and list their personal access tokens.

::: tabs key:pythonTS

\== Python

```python
from qhub.api.user import HubUserClient

user = HubUserClient(api_key="YOUR_PERSONAL_ACCESS_TOKEN")

me = user.user_settings.get_current_user()
tokens = user.user_settings_personal_access_tokens.get_personal_access_tokens()

print(me.email, [t.name for t in tokens.access_tokens or []])
```

\== TypeScript

```ts
import { HubUserClient } from "@quantum-hub/qhub-api/user";

const user = new HubUserClient({
  apiKey: process.env.KQH_PERSONAL_ACCESS_TOKEN!,
});

const me = await user.userSettings.getCurrentUser();
const tokens = await user.userSettingsPersonalAccessTokens.getPersonalAccessTokens();

console.log(me.email, (tokens.accessTokens ?? []).map((t) => t.name));
```

:::

## Constructor

| Parameter                      | Required         | Description                                                                          |
| ------------------------------ | ---------------- | ------------------------------------------------------------------------------------ |
| `api_key` / `apiKey`           | yes              | Personal access token; sent as `X-Auth-Token`.                                       |
| `base_url` / `baseUrl`         | no               | Overrides both the default environment and `environment` if supplied.                |
| `environment`                  | no               | `HubUserClientEnvironment.DEFAULT` (Python) / `HubUserEnvironment.Default` (TypeScript). |
| `headers`                      | no               | Additional headers merged into every request.                                        |
| `timeout` / `timeoutInSeconds` | no               | Read timeout in seconds; defaults to 60 when no custom HTTP client is supplied.      |
| `max_retries` / `maxRetries`   | no               | Number of retries for transient failures; defaults to 2.                             |
| `follow_redirects`             | no (Python only) | Passed through to `httpx.Client`; defaults to `True`.                                |
| `httpx_client` / `fetch`       | no               | Inject a preconfigured HTTP client (Python) or `fetch` implementation (TypeScript).  |
| `logging`                      | no               | Logger instance or `{level, logger, silent}` config dict.                            |

Python also ships `AsyncHubUserClient` with the same shape plus an `httpx.AsyncClient` hook.

## Behaviour

* `base_url` / `baseUrl` always wins over `environment`; omitting both in Python raises `Exception("Please pass in either base_url or environment to construct the client")`.
* [check\_user\_exists](#check_user_exists) is a `HEAD` existence check: it returns the response headers (`Dict[str, str]` in Python, a `Headers` object in TypeScript) when the user exists and raises `NotFoundError` (404) otherwise - treat a thrown `NotFoundError`, not a falsy return, as "does not exist".
* [get\_user\_profile\_image](#get_user_profile_image) returns a nullable string (`Optional[str]` / `string | undefined`), not raw bytes; the value is absent when the user has no profile image.
* A personal access token's raw `value` is returned only once, at creation or default-token regeneration; subsequent reads omit it.

## Namespaces

| Namespace                              | Purpose                                                                            |
| -------------------------------------- | ---------------------------------------------------------------------------------- |
| `user_settings`                        | Current-user profile: get, update, delete, manage profile image.                  |
| `user_settings_personal_access_tokens` | List, create, delete, and regenerate personal access tokens for the current user. |
| `authentication`                       | Resolve the principal for a personal access token.                                 |
| `users`                                | Look up users by id, check existence, fetch profile images.                        |
| `user_registration_status`             | Read the registration status of a user by email.                                   |
| `search`                               | Search the user directory.                                                         |

TypeScript exposes the same namespaces in camelCase (`userSettings`, `userSettingsPersonalAccessTokens`, `authentication`, `users`, `userRegistrationStatus`, `search`).

## Current user profile

Operations on the currently-authenticated user, under the `user_settings` (Python) / `userSettings` (TypeScript) namespace.

| Method                                                       | Description                                  |
| ------------------------------------------------------------ | -------------------------------------------- |
| `user_settings.get_current_user()`                           | Full profile of the current user.            |
| `user_settings.update_current_user(current_position=?, homepage=?, about=?)` | Update editable profile fields.              |
| `user_settings.delete_current_user()`                        | Permanently delete the current user.         |
| `user_settings.update_current_user_profile_image(file=...)`  | Upload or replace the profile image.         |
| `user_settings.delete_current_user_profile_image()`          | Delete the profile image.                    |

### get\_current\_user

Return the full profile of the currently-authenticated user.
If the user does not yet exist in the user-service database, it is provisioned just-in-time from the identity provider and then returned.

This method takes no arguments beyond per-request options.

::: tabs key:pythonTS

\== Python

```python
me = user.user_settings.get_current_user()
print(me.id, me.email, me.firstname, me.lastname)
```

\== TypeScript

```ts
const me = await user.userSettings.getCurrentUser();
console.log(me.id, me.email, me.firstname, me.lastname);
```

:::

Returns `UserDto` - see [UserDto](#userdto).

### update\_current\_user

Update the editable profile fields.
Core identity fields (email, first name, last name) are owned by the identity provider and cannot be changed here.

| Parameter          | Required | Type               | Default | Description                                         |
| ------------------ | -------- | ------------------ | ------- | --------------------------------------------------- |
| `current_position` | no       | `str?` / `string?` | —       | Current professional position or role.              |
| `homepage`         | no       | `str?` / `string?` | —       | Personal or professional homepage (fully-qualified URL). |
| `about`            | no       | `str?` / `string?` | —       | Short biography shown on the profile.               |

::: tabs key:pythonTS

\== Python

```python
me = user.user_settings.update_current_user(
    current_position="Quantum Engineer",
    homepage="https://example.com",
    about="Working on variational algorithms.",
)
```

\== TypeScript

```ts
const me = await user.userSettings.updateCurrentUser({
  currentPosition: "Quantum Engineer",
  homepage: "https://example.com",
  about: "Working on variational algorithms.",
});
```

:::

Returns `UserDto` with the updated values applied.

### delete\_current\_user

Permanently delete the current user together with all associated resources (personal access tokens, profile image, profile metadata), and remove the user from the identity provider.
The operation cannot be undone.

This method takes no arguments beyond per-request options.

::: tabs key:pythonTS

\== Python

```python
user.user_settings.delete_current_user()
```

\== TypeScript

```ts
await user.userSettings.deleteCurrentUser();
```

:::

Responds with `204 No Content`; no body is returned.

### update\_current\_user\_profile\_image

Upload or replace the current user's profile image.
The image is sent as a multipart form part named `file`.

| Parameter | Required | Type                                  | Default | Description                |
| --------- | -------- | ------------------------------------- | ------- | -------------------------- |
| `file`    | yes      | `core.File` / `core.file.Uploadable`  | —       | The profile image to upload. |

::: tabs key:pythonTS

\== Python

```python
with open("avatar.png", "rb") as f:
    user.user_settings.update_current_user_profile_image(file=f)
```

\== TypeScript

```ts
import { createReadStream } from "node:fs";

await user.userSettings.updateCurrentUserProfileImage({
  file: createReadStream("avatar.png"),
});
```

:::

Responds with `204 No Content`; no body is returned.

### delete\_current\_user\_profile\_image

Remove the current user's profile image.
Idempotent - succeeds even when no image is set.

This method takes no arguments beyond per-request options.

::: tabs key:pythonTS

\== Python

```python
user.user_settings.delete_current_user_profile_image()
```

\== TypeScript

```ts
await user.userSettings.deleteCurrentUserProfileImage();
```

:::

Responds with `204 No Content`; no body is returned.

## Personal access tokens

Manage the current user's personal access tokens (PATs), under the `user_settings_personal_access_tokens` (Python) / `userSettingsPersonalAccessTokens` (TypeScript) namespace.

| Method                                                                | Description                                          |
| --------------------------------------------------------------------- | ---------------------------------------------------- |
| `user_settings_personal_access_tokens.get_personal_access_tokens()`   | List all PATs plus the default token value.          |
| `user_settings_personal_access_tokens.create_personal_access_token(name=..., expires_at=?)` | Create a new PAT; raw value returned once.           |
| `user_settings_personal_access_tokens.delete_personal_access_token(id)` | Delete a PAT by id.                                  |
| `user_settings_personal_access_tokens.regenerate_default_personal_access_token()` | Rotate the default PAT.                              |

### get\_personal\_access\_tokens

List all personal access tokens for the current user, together with the raw value of the current default token.
The raw `value` of individual tokens is omitted on reads.

This method takes no arguments beyond per-request options.

::: tabs key:pythonTS

\== Python

```python
tokens = user.user_settings_personal_access_tokens.get_personal_access_tokens()
for token in tokens.access_tokens or []:
    print(token.id, token.name, token.expires_at)
```

\== TypeScript

```ts
const tokens = await user.userSettingsPersonalAccessTokens.getPersonalAccessTokens();
for (const token of tokens.accessTokens ?? []) {
  console.log(token.id, token.name, token.expiresAt);
}
```

:::

Returns `AccessTokensDto` - see [AccessTokensDto](#accesstokensdto).

### create\_personal\_access\_token

Create a new personal access token.
The raw `value` is returned exactly once, on this response; store it immediately.

| Parameter    | Required | Type               | Default | Description                                      |
| ------------ | -------- | ------------------ | ------- | ------------------------------------------------ |
| `name`       | yes      | `str` / `string`   | —       | Human-readable token name.                       |
| `expires_at` | no       | `str?` / `string?` | —       | Expiry date as `yyyy-MM-dd`; omit for no expiry. |

::: tabs key:pythonTS

\== Python

```python
token = user.user_settings_personal_access_tokens.create_personal_access_token(
    name="ci-token",
    expires_at="2027-01-01",
)
print(token.value)  # shown only here
```

\== TypeScript

```ts
const token = await user.userSettingsPersonalAccessTokens.createPersonalAccessToken({
  name: "ci-token",
  expiresAt: "2027-01-01",
});
console.log(token.value); // shown only here
```

:::

Returns `AccessTokenDto` - see [AccessTokenDto](#accesstokendto).

### delete\_personal\_access\_token

Permanently delete a personal access token by id.
The token must belong to the calling user.

| Parameter | Required | Type             | Default | Description                       |
| --------- | -------- | ---------------- | ------- | --------------------------------- |
| `id`      | yes      | `str` / `string` | —       | Id of the token to delete.        |

::: tabs key:pythonTS

\== Python

```python
user.user_settings_personal_access_tokens.delete_personal_access_token(token_id)
```

\== TypeScript

```ts
await user.userSettingsPersonalAccessTokens.deletePersonalAccessToken(tokenId);
```

:::

Responds with `204 No Content`; no body is returned.

### regenerate\_default\_personal\_access\_token

Rotate the default personal access token and return its new raw value.
The previous default value is invalidated.

This method takes no arguments beyond per-request options.

::: tabs key:pythonTS

\== Python

```python
result = user.user_settings_personal_access_tokens.regenerate_default_personal_access_token()
print(result.default_token)  # shown only here
```

\== TypeScript

```ts
const result = await user.userSettingsPersonalAccessTokens.regenerateDefaultPersonalAccessToken();
console.log(result.defaultToken); // shown only here
```

:::

Returns `DefaultAccessTokenDto` - see [DefaultAccessTokenDto](#defaultaccesstokendto).

## Principal resolution

Resolve a personal access token into an authenticated principal, under the `authentication` namespace.
This is an internal/gateway-oriented endpoint: it validates the PAT carried in the `X-Auth-Token` header and returns the resolved user.

### authorize\_by\_personal\_access\_token

Validate the current token and return the authenticated principal (user id plus token metadata).

This method takes no arguments beyond per-request options.

::: tabs key:pythonTS

\== Python

```python
principal = user.authentication.authorize_by_personal_access_token()
print(principal.id, principal.access_token.name if principal.access_token else None)
```

\== TypeScript

```ts
const principal = await user.authentication.authorizeByPersonalAccessToken();
console.log(principal.id, principal.accessToken?.name);
```

:::

Returns `PersonalAccessTokenPrincipal` - see [PersonalAccessTokenPrincipal](#personalaccesstokenprincipal).

## User lookup

Public read access to user profiles and profile images, under the `users` namespace.

| Method                              | Description                                  |
| ----------------------------------- | -------------------------------------------- |
| `users.get_user_by_id(id)`          | Public overview of a user.                   |
| `users.check_user_exists(id)`       | `HEAD`-style existence check.                |
| `users.get_user_profile_image(id)`  | Fetch a user's profile image.                |

### get\_user\_by\_id

Return a public overview (id, first name, last name) of a user.

| Parameter | Required | Type             | Default | Description     |
| --------- | -------- | ---------------- | ------- | --------------- |
| `id`      | yes      | `str` / `string` | —       | User identifier. |

::: tabs key:pythonTS

\== Python

```python
overview = user.users.get_user_by_id(user_id)
print(overview.firstname, overview.lastname)
```

\== TypeScript

```ts
const overview = await user.users.getUserById(userId);
console.log(overview.firstname, overview.lastname);
```

:::

Returns `UserOverviewDto` - see [UserOverviewDto](#useroverviewdto).

### check\_user\_exists

`HEAD`-style existence check for a user.
Returns the response headers when the user exists and raises `NotFoundError` (404) otherwise - see [Behaviour](#behaviour).

| Parameter | Required | Type             | Default | Description     |
| --------- | -------- | ---------------- | ------- | --------------- |
| `id`      | yes      | `str` / `string` | —       | User identifier. |

::: tabs key:pythonTS

\== Python

```python
from qhub.api.user.errors import NotFoundError

try:
    user.users.check_user_exists(user_id)
    exists = True
except NotFoundError:
    exists = False
```

\== TypeScript

```ts
import { NotFoundError } from "@quantum-hub/qhub-api/user";

let exists = true;
try {
  await user.users.checkUserExists(userId);
} catch (err) {
  if (err instanceof NotFoundError) exists = false;
  else throw err;
}
```

:::

Returns the response headers (`Dict[str, str]` / `Headers`); no body is emitted.

### get\_user\_profile\_image

Fetch a user's profile image.
Returns `None` / `undefined` when the user has no image; raises `NotFoundError` (404) when the user does not exist.

| Parameter | Required | Type             | Default | Description     |
| --------- | -------- | ---------------- | ------- | --------------- |
| `id`      | yes      | `str` / `string` | —       | User identifier. |

::: tabs key:pythonTS

\== Python

```python
image = user.users.get_user_profile_image(user_id)
```

\== TypeScript

```ts
const image = await user.users.getUserProfileImage(userId);
```

:::

Returns `Optional[str]` / `string | undefined` - see [Behaviour](#behaviour) for the nullable-string contract.

## User registration status

Read the registration state of a user identified by email, under the `user_registration_status` (Python) / `userRegistrationStatus` (TypeScript) namespace.

### get\_user\_registration\_status

Return the registration status for an email address; drives the registration flow.

| Parameter | Required | Type             | Default | Description                           |
| --------- | -------- | ---------------- | ------- | ------------------------------------- |
| `email`   | yes      | `str` / `string` | —       | Email address to query (sent as a query parameter). |

::: tabs key:pythonTS

\== Python

```python
status = user.user_registration_status.get_user_registration_status(
    email="someone@example.com",
)
print(status.status, status.message)
```

\== TypeScript

```ts
const status = await user.userRegistrationStatus.getUserRegistrationStatus({
  email: "someone@example.com",
});
console.log(status.status, status.message);
```

:::

Returns `RegistrationResponse` - see [RegistrationResponse](#registrationresponse).

## User search

Full-text search across user profiles, under the `search` namespace.

### users

Case-insensitive substring search over first name, last name, username, and email.

| Parameter | Required | Type             | Default | Description                                |
| --------- | -------- | ---------------- | ------- | ------------------------------------------ |
| `q`       | yes      | `str` / `string` | —       | Search query (sent as a query parameter).  |

::: tabs key:pythonTS

\== Python

```python
results = user.search.users(q="alice")
for match in results:
    print(match.id, match.firstname, match.lastname)
```

\== TypeScript

```ts
const results = await user.search.users({ q: "alice" });
for (const match of results) {
  console.log(match.id, match.firstname, match.lastname);
}
```

:::

Returns a plain (possibly empty) `List[UserSearchDto]` / `UserSearchDto[]` - there is no pagination wrapper.
See [UserSearchDto](#usersearchdto).

## Endpoints

| Default base URL                                | Override                                                                                                                          |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `https://api.hub.kipu-quantum.com/user-service` | `base_url=...` / `baseUrl: ...`, or `environment=HubUserClientEnvironment.DEFAULT` (Python) / `environment: HubUserEnvironment.Default` (TypeScript). |

## Errors

All Python errors extend `ApiError` (in `qhub.api.user.core.api_error`); all TypeScript errors extend `HubUserError`.

| Status | Python class          | TypeScript class      | Meaning                              |
| ------ | --------------------- | --------------------- | ------------------------------------ |
| 400    | `BadRequestError`     | `BadRequestError`     | Malformed request.                   |
| 401    | `UnauthorizedError`   | `UnauthorizedError`   | Missing or invalid credentials.      |
| 403    | `ForbiddenError`      | `ForbiddenError`      | Authenticated but not permitted.     |
| 404    | `NotFoundError`       | `NotFoundError`       | Resource does not exist.             |

Other failure modes:

* **Auth misconfiguration (TypeScript)** - constructing `HubUserClient` without `apiKey` throws `HubUserError` with message `"Please provide 'apiKey' when initializing the client"`.
* **Timeouts** - hitting `timeoutInSeconds` raises `HubUserTimeoutError` in TypeScript; in Python, timeouts surface as the underlying `httpx.TimeoutException`.

All HTTP error instances expose `status_code`, `headers`, and `body` (Python) / `statusCode`, `rawResponse`, and `body` (TypeScript).

## Reference

### UserDto

Full profile of the currently-authenticated user.

| Field            | Python / wire name              | Type   | Description                                  |
| ---------------- | ------------------------------- | ------ | -------------------------------------------- |
| id               | `id`                            | `str?` | Unique id (identity-provider subject).       |
| username         | `username`                      | `str?` | Login name (typically the email).            |
| email            | `email`                         | `str?` | Email address (identity-provider managed).   |
| firstname        | `firstname`                     | `str?` | First (given) name.                          |
| lastname         | `lastname`                      | `str?` | Last (family) name.                          |
| current\_position | `current_position` / `currentPosition` | `str?` | Current professional position or role.       |
| homepage         | `homepage`                      | `str?` | Personal or professional homepage.           |
| about            | `about`                         | `str?` | Short biography.                             |

### AccessTokenDto

A personal access token belonging to a user.

| Field      | Python / wire name        | Type   | Description                                                |
| ---------- | ------------------------- | ------ | ---------------------------------------------------------- |
| id         | `id`                      | `str?` | Unique id of the token.                                    |
| name       | `name`                    | `str`  | Human-readable token name.                                 |
| created\_at | `created_at` / `createdAt` | `str?` | Creation timestamp (`yyyy-MM-dd HH:mm:ss`, UTC).           |
| used\_at    | `used_at` / `usedAt`      | `str?` | Last-used timestamp; `null` if never used.                 |
| expires\_at | `expires_at` / `expiresAt` | `str?` | Expiry (`yyyy-MM-dd`); `null` means never expires.         |
| value      | `value`                   | `str?` | Raw token value; returned only once at creation, `null` on reads. |

### AccessTokensDto

Container for a user's personal access tokens plus the raw default-token value.

| Field         | Python / wire name           | Type                | Description                          |
| ------------- | ---------------------------- | ------------------- | ------------------------------------ |
| default\_token | `default_token` / `defaultToken` | `str?`          | Raw value of the current default PAT. |
| access\_tokens | `access_tokens` / `accessTokens` | `List[AccessTokenDto]?` | All PATs for the user; may be empty. |

### DefaultAccessTokenDto

Response wrapping a newly-generated default personal access token value.

| Field         | Python / wire name           | Type   | Description                                        |
| ------------- | ---------------------------- | ------ | -------------------------------------------------- |
| default\_token | `default_token` / `defaultToken` | `str?` | Raw value of the regenerated default PAT (returned once). |

### PersonalAccessTokenPrincipal

Authenticated principal resolved from a personal access token.

| Field        | Python / wire name           | Type              | Description                                          |
| ------------ | ---------------------------- | ----------------- | ---------------------------------------------------- |
| id           | `id`                         | `str?`            | Unique id of the authenticated user.                 |
| access\_token | `access_token` / `accessToken` | `AccessTokenDto?` | Metadata of the PAT used to authenticate; raw `value` never included. |

### RegistrationResponse

Registration status of a user, used by the registration flow.

| Field   | Python / wire name | Type                         | Description                                  |
| ------- | ------------------ | ---------------------------- | -------------------------------------------- |
| status  | `status`           | `RegistrationResponseStatus?` | Current registration status (see [Enums](#enums)). |
| message | `message`          | `str?`                       | Human-readable message for the status.       |

### UserOverviewDto

Minimal public representation of a user.

| Field     | Python / wire name | Type   | Description           |
| --------- | ------------------ | ------ | --------------------- |
| id        | `id`               | `str?` | Unique id.            |
| firstname | `firstname`        | `str?` | First (given) name.   |
| lastname  | `lastname`         | `str?` | Last (family) name.   |

### UserSearchDto

Entry in the user-search result list (returned as a bare array).

| Field     | Python / wire name | Type   | Description           |
| --------- | ------------------ | ------ | --------------------- |
| id        | `id`               | `str?` | Unique id of the match. |
| firstname | `firstname`        | `str?` | First (given) name.   |
| lastname  | `lastname`         | `str?` | Last (family) name.   |

### Enums

* `RegistrationResponseStatus` - `PENDING | APPROVED | REJECTED | NOT_FOUND | EMAIL_NOT_VERIFIED`

### Field casing

User-API DTOs use snake\_case in Python and camelCase in TypeScript.
Single-word fields (`id`, `email`, `firstname`, `lastname`, `username`, `name`, `value`, `homepage`, `about`, `status`, `message`) are identical across both; the aliased fields are `currentPosition`, `createdAt`, `usedAt`, `expiresAt`, `defaultToken`, `accessTokens`, and `accessToken`.
