
# Create an API

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

Creates an API in the environment. `key`, `identifier` and `name` are required; omitted optional fields take their defaults. The identifier is unique in the environment (a deleted API keeps its identifier reserved for 90 days). The response carries an `ETag` with the current version. Send an `Idempotency-Key` header to make retries safe.

## Request

Body `application/json`. Unknown fields are rejected.

| Field | Type | Required | Description |
|---|---|---|---|
| `key` | string | yes | Stable configuration key, `^[a-z0-9_-]{1,64}$`. Immutable. |
| `identifier` | string | yes | Token audience: an https URL (no query or fragment) or `urn:<nid>:<nss>`; stored in canonical form. Immutable. The `urn:idp:` namespace is reserved. |
| `name` | string | yes | 1-128 characters. |
| `access_token_lifetime_s` | integer | no | 60-86400. Default 3600 (600 with `mcp_profile`). |
| `token_format` | string | no | `jwt` (default) or `opaque`. |
| `rbac_mode` | string | no | How roles and permissions reach the token: `claims` (default) adds `permissions` and `org_permissions`; `off` adds neither (`org_roles` is still added); `enforce` adds them and also limits the granted scopes to the user's effective permissions. |
| `allow_offline_access` | boolean | no | Default `true`. |
| `require_resource_indicator` | boolean | no | Default `false` (`true` with `mcp_profile`). |
| `dpop_required` | boolean | no | Default `false`. |
| `mcp_profile` | boolean | no | Requires an https `identifier` and `require_resource_indicator: true`. |
| `first_party_access` | string | no | `all` (default) or `granted_only`. |
| `introspection_applications` | array | no | Applications (`app_…`) that may introspect this API's tokens, at most 50, no duplicates. Only `web` and `m2m` applications of the same environment. Empty by default. |

### Introspection applications

The introspection endpoint (`POST <issuer>/oauth2/introspect`, RFC 7662) answers `active: true` for an access token only to two callers: the application the token was issued to, or an application in the `introspection_applications` list of the API named in the token's `aud`. Every other caller gets `active: false`, so APIs cannot read each other's tokens. Only confidential applications (`web`, `m2m`) of the same environment can be listed; the list is stored as a set and returned in id order. A suspended application stays on the list but cannot introspect. The system API's list cannot be written.

## Response

`201 Created` with an `ETag` header and the API:

```json
{
  "id": "api_01h455vb4pex5vsknk084sn02q",
  "key": "orders",
  "identifier": "https://api.example.com/orders",
  "name": "Orders API",
  "is_system": false,
  "access_token_lifetime_s": 3600,
  "token_format": "jwt",
  "rbac_mode": "claims",
  "allow_offline_access": true,
  "require_resource_indicator": false,
  "dpop_required": false,
  "mcp_profile": false,
  "first_party_access": "all",
  "introspection_applications": ["app_01h455vb4pex5vsknk084sn02r"],
  "created_at": "2026-10-05T12:00:00Z",
  "updated_at": "2026-10-05T12:00:00Z"
}
```

## Errors

| HTTP | code | When |
|---|---|---|
| 400 | `unknown_field` | The body contains a field that is not listed above (for example `id` or `is_system`). |
| 409 | `conflict` | An API with this `key` or `identifier` already exists. |
| 415 | `unsupported_media_type` | The content type is not `application/json`. |
| 422 | `validation_failed` | A field is missing or invalid (`errors[].pointer`, for example `/identifier`). |
| 422 | `validation_failed` | An `introspection_applications` item does not exist in this environment (`unknown_application`), is not confidential (`not_confidential`), is a duplicate (`duplicate`) or is not an `app_…` id (`invalid_value`); the pointer names the index in the request. |
| 403 | `quota_exceeded` | The environment reached its technical API limit. |
| 403 | `insufficient_permissions` | The credential lacks `apis:write`. |

## Example

```bash
curl -X POST https://api.eu.keenauth.com/v1/apis \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"key":"orders","identifier":"https://api.example.com/orders","name":"Orders API"}'
```
