Skip to content

API Keys

Manage API keys for authenticating with the Koldan platform. API keys provide a convenient alternative to OAuth 2.0 bearer tokens for server-to-server integrations and can be created, listed, retrieved, and revoked through this API.

Base path: /api/v1/api-keys

Method Endpoint Description
POST /api/v1/api-keys Create API Key
GET /api/v1/api-keys List API Keys
GET /api/v1/api-keys/{keyId} Get API Key
DELETE /api/v1/api-keys/{keyId} Revoke API Key

Create API Key

POST /api/v1/api-keys

Requires Authentication - Scopes: user:apikeys:write

Create a new API key for the authenticated user. The plain-text key is only returned once in the creation response - store it securely.

CreateApiKeyRequest
Field Type Required Description
name string Yes A human-readable name for the API key.
description string No Optional description of the API key's purpose.
scopes string[] No Permission scopes in service:resource:action format (e.g., speech:files:read). If omitted, the key is created as an identity-only key with no scopes. Requested scopes cannot exceed the creator's own role permissions.
expirationDate string (ISO 8601) No Expiration date for the key. If omitted, the key does not expire.
CreateApiKeyRequest
{
  "name": "Dashboard Read-Only",
  "description": "Used by monitoring dashboard",
  "scopes": ["speech:files:read", "speech:transcriptions:read"],
  "expirationDate": "2027-12-31T23:59:59Z"
}
curl -X POST https://koldan.dixilang.com/api/v1/api-keys \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dashboard Read-Only",
    "description": "Used by monitoring dashboard",
    "scopes": ["speech:files:read", "speech:transcriptions:read"],
    "expirationDate": "2027-12-31T23:59:59Z"
  }'
import requests

resp = requests.post(
    "https://koldan.dixilang.com/api/v1/api-keys",
    headers={
        "Authorization": f"Bearer {JWT}",
        "Content-Type": "application/json"
    },
    json={
        "name": "Dashboard Read-Only",
        "description": "Used by monitoring dashboard",
        "scopes": ["speech:files:read", "speech:transcriptions:read"],
        "expirationDate": "2027-12-31T23:59:59Z"
    }
)
print(resp.json())
ApiKeyResponse
Field Type Nullable Description
id string (UUID) No Unique identifier of the API key.
name string No Human-readable name of the API key.
description string Yes Description of the API key's purpose.
scopes string[] No Permission scopes assigned to the key. Empty list indicates an identity-only key.
expirationDate string (ISO 8601) Yes Expiration date, or null if the key does not expire.
revocationDate string (ISO 8601) Yes Date the key was revoked, or null if not revoked.
revocationReason string Yes Reason provided when the key was revoked, or null.
createdAt string (ISO 8601) No Timestamp when the API key was created.
lastUsedAt string (ISO 8601) Yes Timestamp when the key was last used, or null if never used.
expired boolean No Whether the API key has passed its expiration date.
revoked boolean No Whether the API key has been revoked.
key string Yes The plain-text API key. Only present in the Create response. Store it immediately - it cannot be retrieved again.
ApiKeyResponse
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Dashboard Read-Only",
  "scopes": ["speech:files:read", "speech:transcriptions:read"],
  "expirationDate": "2027-12-31T23:59:59Z",
  "createdAt": "2026-04-01T12:00:00Z",
  "expired": false,
  "revoked": false,
  "key": "kk-a1b2c3d4e5f6...d0e1f2"
}

Active Key Limit

Each user can have at most 15 active (non-revoked, non-expired) API keys at a time (configurable via koldan.api.api-keys.max-active-keys-per-user). If the limit is reached, revoke or let an existing key expire before creating a new one.

Status Description
201 Created API key created successfully. The response includes the plain-text key.
400 Bad Request The name field is missing or blank, or the requested scopes exceed the creator's role permissions.
401 Unauthorized Authentication required or token invalid.
429 Too Many Requests The active API key limit has been reached. Revoke an existing key before creating a new one.

List API Keys

GET /api/v1/api-keys

Requires Authentication - Scopes: user:apikeys:read

Retrieve all API keys belonging to the authenticated user.

Note

The key field is not included when listing API keys. The plain-text key is only returned at the time of creation.

curl -X GET https://koldan.dixilang.com/api/v1/api-keys \
  -H "Authorization: Bearer $JWT"
import requests

resp = requests.get(
    "https://koldan.dixilang.com/api/v1/api-keys",
    headers={"Authorization": f"Bearer {JWT}"}
)
print(resp.json())
ApiKeyResponse[]

Returns an array of ApiKeyResponse objects (see field table in Create API Key). The key field is always null in list responses.

ApiKeyResponse[]
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Dashboard Read-Only",
    "createdAt": "2026-04-01T12:00:00Z",
    "expired": false,
    "revoked": false,
    ...
  },
  ...
]
Status Description
200 OK List of API keys returned successfully.
401 Unauthorized Authentication required or token invalid.

Get API Key

GET /api/v1/api-keys/{keyId}

Requires Authentication - Scopes: user:apikeys:read

Retrieve a specific API key by its unique identifier.

Note

The key field is not included when retrieving an API key. The plain-text key is only returned at the time of creation.

Path Parameters
Field Type Required Description
keyId string (UUID) Yes Unique identifier of the API key. (path parameter)
curl -X GET https://koldan.dixilang.com/api/v1/api-keys/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $JWT"
import requests

key_id = "550e8400-e29b-41d4-a716-446655440000"
resp = requests.get(
    f"https://koldan.dixilang.com/api/v1/api-keys/{key_id}",
    headers={"Authorization": f"Bearer {JWT}"}
)
print(resp.json())
ApiKeyResponse

Returns a single ApiKeyResponse object (see field table in Create API Key). The key field is always null.

ApiKeyResponse
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Dashboard Read-Only",
  "scopes": ["speech:files:read", "speech:transcriptions:read"],
  "expirationDate": "2027-12-31T23:59:59Z",
  "createdAt": "2026-04-01T12:00:00Z",
  "expired": false,
  "revoked": false,
  "key": null
}
Status Description
200 OK API key returned successfully.
401 Unauthorized Authentication required or token invalid.
404 Not Found API key not found or does not belong to the current user.

Revoke API Key

DELETE /api/v1/api-keys/{keyId}

Requires Authentication - Scopes: user:apikeys:delete

Permanently revoke an API key. Once revoked, the key can no longer be used for authentication. This action is irreversible.

Path Parameters
Field Type Required Description
keyId string (UUID) Yes Unique identifier of the API key to revoke. (path parameter)
RevokeApiKeyRequest (Optional)

The request body is optional. If provided, it can include a reason for the revocation.

Field Type Required Description
reason string No A human-readable reason for revoking the API key.
RevokeApiKeyRequest
{
  "reason": "Key compromised - rotating credentials"
}
# Revoke with reason
curl -X DELETE https://koldan.dixilang.com/api/v1/api-keys/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Key compromised - rotating credentials" }'

# Revoke without reason
curl -X DELETE https://koldan.dixilang.com/api/v1/api-keys/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $JWT"
import requests

key_id = "550e8400-e29b-41d4-a716-446655440000"

# Revoke with reason
resp = requests.delete(
    f"https://koldan.dixilang.com/api/v1/api-keys/{key_id}",
    headers={
        "Authorization": f"Bearer {JWT}",
        "Content-Type": "application/json"
    },
    json={"reason": "Key compromised - rotating credentials"}
)

# Revoke without reason
resp = requests.delete(
    f"https://koldan.dixilang.com/api/v1/api-keys/{key_id}",
    headers={"Authorization": f"Bearer {JWT}"}
)
Response

This endpoint returns no response body on success (204 No Content).

Status Description
204 No Content API key revoked successfully.
401 Unauthorized Authentication required or token invalid.
404 Not Found API key not found, does not belong to the current user, or is already revoked.

Using an API Key

Once created, use the API key to authenticate requests by including it in the X-API-Key header:

X-API-Key: kk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
curl -X GET https://koldan.dixilang.com/api/v1/speech-services/files \
  -H "X-API-Key: kk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"
import requests

resp = requests.get(
    "https://koldan.dixilang.com/api/v1/speech-services/files",
    headers={"X-API-Key": "kk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"}
)
print(resp.json())

For more details on all supported authentication methods, see Authentication.


Scopes

API keys can be assigned scopes that restrict the permissions granted to the key. Scopes follow the service:resource:action format (e.g., speech:files:read).

When creating a scoped API key, each requested scope is validated against your current role permissions. Then, the key's effective permissions are always the intersection of the key's scopes and the user's role permissions.

Scope Validation

If any requested scope exceeds your permissions, the request will be rejected with 400 Bad Request.


Best Practices

Key Management Recommendations

  • Store keys securely. The plain-text key is only shown once during creation. Store it in a secrets manager or encrypted vault.
  • Use descriptive names. Give each key a meaningful name and description to easily identify its purpose.
  • Follow least privilege. Create scoped keys with only the permissions required for the specific use case.
  • Set expiration dates. Use expiration dates for keys with a known lifecycle (e.g., contractor access, short-lived integrations).
  • Rotate keys regularly. Periodically create new keys and revoke old ones.
  • Revoke unused keys. Promptly revoke keys that are no longer needed.
  • One key per integration. Avoid sharing a single key across multiple services - this simplifies auditing and limits blast radius.
  • Never expose keys in code. Do not hard-code API keys in source code or public repositories. Use environment variables or secret management tools.