
# Create a user

`POST /v1/users` · permission `users:write`

Creates a user in the environment of the API key. The user belongs to that environment only; the same email in two environments is two separate users. Unique values (`email`, `phone_number`, `username`, `external_id`) are checked per environment. Send an `Idempotency-Key` header to make retries safe.

## Request

The body is a JSON object. Unknown fields are rejected with `unknown_field`.

| Field | Type | Required | Description |
|---|---|---|---|
| `email` | string | no* | Primary email address. |
| `email_verified` | boolean | no | Marks the email as verified. Default `false`. |
| `phone_number` | string | no* | Primary phone number in E.164 format. |
| `phone_verified` | boolean | no | Marks the phone number as verified. Default `false`. |
| `phone_country_code` | string | no | ISO 3166-1 alpha-2 code. Derived from the phone prefix when empty. |
| `username` | string | no* | 3 to 64 characters. Unique in the environment. |
| `external_id` | string | no | Your own identifier, 1 to 255 characters. Unique in the environment. |
| `name`, `given_name`, `family_name`, `middle_name`, `nickname` | string | no | Profile names. |
| `picture`, `website`, `profile_url` | string | no | `https://` URLs, up to 2048 characters. |
| `locale`, `zoneinfo`, `gender`, `birthdate` | string | no | Profile attributes. `birthdate` is `YYYY-MM-DD`. |
| `address` | object | no | OIDC `address` object, up to 2048 bytes. |
| `user_metadata`, `app_metadata` | object | no | Free-form JSON objects, up to 16 KiB, depth 5, 100 top-level keys. |

\* At least one of `email`, `phone_number` or `username` is required.

## Response

`201 Created`. The body is the user object. The `ETag` header carries the user version.

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
{
  "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
}
```

## Errors

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

| HTTP | `code` | When |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | The body is not valid JSON or has a field that is not in the schema. |
| 409 | `conflict` | The value is already in use. `errors[]` names the field and the reason (`email_taken`, `phone_taken`, `username_taken`, `external_id_taken`). |
| 415 | `unsupported_media_type` | `Content-Type` is not `application/json`. |
| 422 | `validation_failed` | A value is invalid; `errors[].pointer` names the field. |
| 401 | `unauthenticated` | The API key is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The key lacks the `users:write` 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 \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"email": "ada@example.com", "email_verified": true, "name": "Ada Lovelace"}'
```

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