
# List users

`GET /v1/users` · permission `users:read`

Lists the users of the environment with cursor pagination, newest first by default. Only filters that carry no personal data are accepted here. To look users up by email, phone number or username use [Search users](/docs/api/management/users/search).

## Request

| Parameter | In | Required | Description |
|---|---|---|---|
| `limit` | query | no | Page size, 1 to 100. Default 20. |
| `after` | query | no | Opaque cursor from `list_metadata.after`. Exclusive with `before`. |
| `before` | query | no | Opaque cursor from `list_metadata.before`. Exclusive with `after`. |
| `order` | query | no | `asc` or `desc` by creation time. Default `desc`. |
| `status` | query | no | `active`, `blocked` or `pending_deletion`. |
| `external_id` | query | no | Exact match on your own identifier. |
| `created_after` | query | no | RFC 3339 time; users created after it. |
| `created_before` | query | no | RFC 3339 time; users created before it. |

A cursor is only valid with the same filters and order it was issued for; otherwise the request fails with `invalid_cursor`.

## Response

`200 OK`. `data` holds the users on the page; `list_metadata.before` and `list_metadata.after` are cursors for the previous and next page (`null` when there is none). No total count is returned.

Personal data is masked when the key lacks the `users:read_pii` permission: `email` becomes `a***@e***.com`, `phone_number` keeps only its last 2 digits (`+**********67`), `name`, `given_name`, `family_name`, `middle_name` and `nickname` become initials (`A. L.`), `username` keeps only its first character (`a***`), `birthdate` becomes `****-**-**` and `address` becomes `{"redacted": true}`. `user_metadata` and `app_metadata` are not masked.

```json
{
  "data": [ {
    "id": "usr_0a1b2c3d4e5f6g7h8j9k0m1n2p",
    "kind": "regular",
    "status": "active",
    "external_id": null,
    "username": null,
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "email_verified": true,
    "phone_number": null,
    "phone_verified": false,
    "user_metadata": {},
    "app_metadata": {},
    "created_via": "admin_api",
    "created_at": "2026-01-15T09:30:00Z",
    "updated_at": "2026-01-15T09:30:00Z",
    "last_sign_in_at": null,
    "deletion_requested_at": null,
    "deletion_due_at": null
  } ],
  "list_metadata": { "before": null, "after": "<opaque cursor>" }
}
```

## Errors

Every error is an `application/problem+json` body with a stable `code`.

| HTTP | `code` | When |
|---|---|---|
| 400 | `invalid_cursor` | The cursor is malformed, tampered with, or was issued for other filters. |
| 422 | `validation_failed` | A query parameter is invalid (`limit` out of range, bad timestamp, repeated parameter). |
| 401 | `unauthenticated` | The API key is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The key lacks the `users:read` permission. |
| 429 | `rate_limited` | The rate limit was exceeded; honor `Retry-After`. |
| 503 | `service_unavailable` | A dependency is temporarily unavailable; retry later. |

## Example

```bash
curl "https://api.eu.keenauth.com/v1/users?limit=20&status=active" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY"
```

## Personal data

This endpoint carries personal data: end-user profile. [Personal data in the KeenAuth APIs](/docs/policies/personal-data) describes what is stored, how long it is kept and how it is deleted. Do not send special categories of personal data (health, biometric data, religion, political opinions, sexual orientation) in any field.
