
# Search users

`POST /v1/users:search` · permission `users:read`

Searches users with filters that contain personal data. These filters are accepted only in the request body, never in the query string, because query strings end up in proxy and client logs. Filters combine with AND and at least one identity filter is required. Matches are exact; prefix and substring search are not offered. A search that matches too many users fails with `search_too_broad`; narrow the filters.

## Request

Pagination parameters go in the query string; they carry no personal data.

| 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`. |

Body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `email` | string | no | Exact email address. |
| `phone` | string | no | Exact phone number in E.164 format. |
| `username` | string | no | Exact username. |
| `email_domain` | string | no | Exact email domain. |
| `app_metadata`, `user_metadata` | object | no | Up to 3 top-level keys; each key must equal a scalar value. |
| `status` | string | no | `active`, `blocked` or `pending_deletion`. |

A cursor is valid only with the same set of filters and order it was issued for.

## 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. A key without `users:read_pii` can still search with exact personal-data filters; the results are 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 | `unknown_field`, `invalid_cursor` | The body has an unsupported field (for example a prefix filter), or the cursor does not match the request. |
| 415 | `unsupported_media_type` | `Content-Type` is not `application/json`. |
| 422 | `validation_failed`, `search_too_broad` | A filter is invalid, no identity filter was given, or the search matches too many users. |
| 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 -X POST "https://api.eu.keenauth.com/v1/users:search?limit=20" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "ada@example.com"}'
```

## 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.
