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