# client.identity.users

> client.identity.users — the identity.users operations.

`client.identity.users` — the `identity.users` operations.

On `AsyncKindgi` every method is the same, awaited.

## `client.identity.users.list()`

```python signature
list(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    query: str | None = None,
    include_unregistered: bool | None = None,
    pending: bool | None = None,
    include: Literal['grants'] | None = None,
    timeout: float | None = None,
) -> UserCollectionPage
```

List users in the tenant. `GET /v1/identity/users`

Cursor-paginated list of tenant users (sort order is binding-defined), for tenant admins only. Optional `?query=` is a prefix match on `displayName` — the natural filter shape for a "search users" surface. `primaryEmail` may be redacted per tenant policy. Anyone else adds a person to a project by their email or id (`POST /v1/projects/{projectId}/memberships`).

## `client.identity.users.create()`

```python signature
create(
    body: CreateUserBody | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> UserCreated
```

Add a person. `POST /v1/identity/users`

Adds a person to the tenant as a tenant member, written before it answers: they can read the tenant's settings (providers, policies, adapters, signing keys, deployments), not its projects. Give them a role to work (project or team membership, or tenant admin), then mint their first API key at `POST /v1/tokens` with `for`. A person added with an email is invited (`invite`): they sign in within 7 days, or a tenant admin renews it (`POST /v1/identity/users/{userId}/invite`); the invitation is emailed where the deployment sends email, and `inviteEmail` says whether it was. Tenant admins only. Mounted when the identity directory can add people.

## `client.identity.users.get()`

```python signature
get(user_id: str | UUID, /, *, timeout: float | None = None) -> UserRecord
```

Get a user by id. `GET /v1/identity/users/{userId}`

A tenant admin, or the person themselves.

## `client.identity.users.list_sessions()`

```python signature
list_sessions(
    user_id: str | UUID,
    /,
    *,
    timeout: float | None = None,
) -> IdentitySessionCollectionPage
```

List active sessions for a user. `GET /v1/identity/users/{userId}/sessions`

A tenant admin, or the person themselves. Returns the wire-safe `IdentitySessionSummary` shape — provider access-token + refresh-token never cross the wire, even to admins. Unknown user id returns an empty list (call `GET /v1/identity/users/:userId` first to distinguish "no sessions" from "no user").

## `client.identity.users.unregister()`

```python signature
unregister(
    user_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> UnregisterUserResult
```

Remove a person. `POST /v1/identity/users/{userId}/unregister`

Removes a person from the tenant, in one step: they're marked removed (`unregisteredAt`; their record stays, so their history still says who they were), every API key and session of theirs is revoked, and every grant and membership they hold is taken away, all before it answers. Their keys get `401` at once. Their email is free again: adding it makes a new person. Removing someone already removed changes nothing. Refused for yourself and the deployment's seed user (`identity-user-unregister-refused`), and for the only tenant admin (`last-tenant-admin`). Tenant admins only. Mounted when the identity directory can remove people.

## `client.identity.users.resend_invite()`

```python signature
resend_invite(
    user_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> InviteRenewed
```

Renew a person's invitation. `POST /v1/identity/users/{userId}/invite`

For a person who hasn't signed in yet (`invite` pending or expired): a new expiry, 7 days on, and the invitation emailed again where the deployment sends email (`inviteEmail` says whether it was). A person who has signed in, was added before invitations, or has no email gets `409 identity-user-not-invited`. Renewing one person again within a minute, more than 5 times in 24 hours, or past the invitation emails the workspace, or the whole deployment, may send in 24 hours, gets `429 invite-capped` with `Retry-After`, and changes nothing. To revoke an invitation, remove the person (`POST /v1/identity/users/{userId}/unregister`). Tenant admins only. Mounted when the identity directory invites people (from 0.1.9).

## `client.identity.users.second_factor()`

```python signature
second_factor(
    user_id: str | UUID,
    /,
    *,
    timeout: float | None = None,
) -> SecondFactorStatus
```

A person's second factor. `GET /v1/identity/users/{userId}/second-factor`

Whether they have a second factor set up, since when, how many recovery codes are left, and whether it can still be used. Never a key or a code. Tenant admins only. Mounted when the deployment keeps second factors (from 0.1.9).

## `client.identity.users.remove_second_factor()`

```python signature
remove_second_factor(
    user_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> SecondFactorRemoved
```

Remove a person's second factor. `POST /v1/identity/users/{userId}/second-factor/remove`

For a lost phone, or lost recovery codes: their key is deleted, their recovery codes stop working, they're told by email where the deployment sends it, and it's audited. Their sessions stay: removing one gives no one access. Where the workspace requires one, they set one up at their next sign-in. `removed: false` when they had none. Tenant admins only, for anyone but themselves: an admin's own is taken off at `POST /v1/identity/me/second-factor/remove`, with a code from it (`403 permission-denied` here). Mounted when the deployment keeps second factors (from 0.1.9).

## `client.identity.users.revoke_sessions()`

```python signature
revoke_sessions(
    user_id: str | UUID,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
) -> RevokeSessionsResult
```

Revoke every active session for a user. `POST /v1/identity/users/{userId}/revoke-sessions`

A tenant admin revokes anyone's sessions; anyone else only their own. Idempotent. Under the hood, deployments typically delegate to `SessionStoreBinding.revokeAllForUser`. Returns `{ revokedCount: 0 }` when the user was already fully signed out.

## `client.identity.users.grants()`

```python signature
grants(user_id: str | UUID, /, *, timeout: float | None = None) -> PersonGrants
```

Read a person's grants. `GET /v1/identity/users/{userId}/grants`

What the person may do, as granted directly: tenant admin, project and team roles, the reviewer roster. A tenant admin reads anyone's; anyone else only their own.

## `client.identity.users.grant()`

```python signature
grant(
    user_id: str | UUID,
    body: PersonGrantBody | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> PersonGrants
```

Make a person a tenant admin. `POST /v1/identity/users/{userId}/grant`

Written before the call answers, so the person's next request holds it. A no-op when held. Tenant admins only.

## `client.identity.users.ungrant()`

```python signature
ungrant(
    user_id: str | UUID,
    body: PersonGrantBody | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> PersonGrants
```

Remove tenant admin from a person. `POST /v1/identity/users/{userId}/ungrant`

A no-op when not held. Refused for the only person who is a tenant admin (`last-tenant-admin`: make someone else one first), and for the seed user, whom the runtime makes tenant admin at every boot (`seed-user-admin`: unset `KINDGI_SEED_USER_ID` and restart it first). Tenant admins only.
