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:
{
"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
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"}'