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. |
{
"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. |
{
"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.
ApiKeyResponse[]
Returns an array of ApiKeyResponse objects (see field table in Create API Key). The key field is always null in list responses.
[
{
"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) |
ApiKeyResponse
Returns a single ApiKeyResponse object (see field table in Create API Key). The key field is always null.
{
"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. |
# 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:
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.