
# Create an API key

`POST /v1/api-keys` · permission `api-keys:write` · required `Idempotency-Key`

Creates a management API key for the environment. A `secret` key reaches the whole management API; a `restricted` key carries only the listed permissions (`<resource>:<action>`).

- **Raw key.** The response carries `key` once (`idp_sk_live_…`, `idp_sk_test_…`, `idp_rk_live_…` or `idp_rk_test_…`). It cannot be retrieved again; store it immediately. The response has `Cache-Control: no-store`.
- **No escalation.** A key cannot create a key whose permissions exceed its own, and a `restricted` key cannot create a `secret` key. The new key cannot expire after the calling key, and a calling key with `allowed_cidrs` can only create keys limited to networks inside its own; if `allowed_cidrs` is omitted, the new key gets the calling key's list (`permission_escalation`).
- **Limits.** At most 50 active keys per environment, at most 20 CIDR blocks per key, expiry at most 730 days ahead. A key with `allowed_cidrs` is accepted only from those networks.
- **Idempotent replay.** Repeating the request with the same `Idempotency-Key` replays the stored response without the key: the body has `secret_redacted: true` and no `key`. Create a new key if the key was lost.

## Request

Body:

| Field | Type | Description |
|---|---|---|
| `name` | string | Required. 1–100 characters. |
| `type` | string | Required. `secret` or `restricted`. |
| `permissions` | string[] | Required and non-empty for `restricted` (at most 200); absent or empty for `secret`. Cannot exceed the caller's own permissions. |
| `expires_at` | string (date-time) | Required, in the future, at most 730 days ahead. |
| `allowed_cidrs` | string[] | Optional, at most 20 CIDR blocks. |

## Response

`201 Created` with `Cache-Control: no-store`. The key object as in the list response, plus `key`.

```json
{
  "id": "key_0000000000000000000000001z",
  "name": "ci",
  "type": "restricted",
  "permissions": ["users:read"],
  "allowed_cidrs": [],
  "last4": "xxxx",
  "created_by": "key_…",
  "created_at": "2026-10-01T10:00:00Z",
  "expires_at": "2027-10-01T10:00:00Z",
  "last_used_at": null,
  "last_used_ip": null,
  "rotated_from": null,
  "revoked_at": null,
  "revoke_reason": null,
  "key": "<shown once>"
}
```

## Errors

| HTTP | `code` | When |
|---|---|---|
| 401 | `unauthenticated` | The key is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The key lacks the required permission. |
| 403 | `permission_escalation` | The request would give a key more permissions, a wider network range or a later expiry than the calling key has, a `restricted` key tries to create, rotate or revoke beyond its own permissions, or another key tries to rotate or revoke a key created in the dashboard. |
| 409 | `api_key_limit_reached` | The environment already has the maximum of 50 active keys. |
| 422 | `validation_failed` | The body is invalid: missing or too long `name`, unknown `type`, `restricted` without `permissions`, `secret` with `permissions`, malformed permission or CIDR, `expires_at` in the past or more than 730 days ahead. |

## Example

```bash
curl -X POST https://api.eu.keenauth.com/v1/api-keys \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"ci","type":"restricted","permissions":["users:read"],"expires_at":"2027-10-01T10:00:00Z"}'
```
