Skip to content

Authentication and API Keys

Koldan uses token-based authentication for all API requests. Every call to a protected endpoint must include a valid credential in the request headers. Two authentication methods are supported: OAuth2 Bearer tokens (JWT) and API Keys.


Authentication Methods

OAuth2 Bearer Token (JWT)

Interactive applications (web UIs, desktop clients) authenticate using OAuth2 / OpenID Connect. After a user signs in through the configured identity provider, the client receives a JWT access token.

Include the token in the Authorization header:

GET /api/v1/transcriptions HTTP/1.1
Host: koldan.dixilang.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Token lifecycle

  • Tokens are issued and refreshed through your identity provider (IdP).
  • Koldan validates the token signature against the tenant's OIDC JWK set - it never stores passwords.
  • When a token expires, use the refresh-token flow to obtain a new one.

API Key

For server-to-server integrations, scripts, and automated workflows, API Keys provide a simpler alternative that doesn't require an OAuth flow.

Include the key in the X-API-Key header:

POST /api/v1/transcriptions HTTP/1.1
Host: koldan.dixilang.com
X-API-Key: kk-a1b2c3d4e5f6g7h8i9j0...

API Key takes priority

If both Authorization and X-API-Key headers are present in the same request, the API Key is used and the Bearer token is ignored.


Authentication API Reference

Authenticate users with the Koldan platform via username/password credentials, OAuth 2.0 authorization codes (with optional PKCE), token refresh, and logout. All endpoints delegate to the configured OIDC identity provider and return standard OAuth 2.0 token responses.

Public - No authentication required

Base path: /api/v1/auth

Integrating without user interaction?

If you are building a service-to-service integration, a background worker, or any automation that runs without a user present, skip the authentication flows below and use an API Key instead. API Keys are long-lived, do not require token refresh, and can be scoped to specific permissions - making them the recommended approach for non-interactive access to the Koldan API.

Method Endpoint Description
POST /api/v1/auth/exchange-auth-code Exchange Authorization Code
POST /api/v1/auth/refresh-token Refresh Token
POST /api/v1/auth/logout Logout
POST /api/v1/auth/login Login

Login

POST /api/v1/auth/login

Deprecated

The direct login endpoint is deprecated and will be removed in a future release.

Use instead:

  • Authorization Code flow with PKCE - for browser-based and mobile applications where a user is present.
  • API Keys - for service-to-service integrations, background workers, and automations that run without user interaction.

Authenticate a user with username and password credentials. On success, the OIDC provider issues an OAuth 2.0 token set (access token, refresh token, and optional ID token). The access token should be used as a Bearer token in subsequent API requests.

LoginRequest
Field Type Required Description
username string Yes The user's username.
password string Yes The user's password.
LoginRequest
{
  "username": "john.doe",
  "password": "s3cur3P@ssw0rd"
}
curl -X POST https://koldan.dixilang.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john.doe",
    "password": "s3cur3P@ssw0rd"
  }'
import requests

resp = requests.post(
    "https://koldan.dixilang.com/api/v1/auth/login",
    json={
        "username": "john.doe",
        "password": "s3cur3P@ssw0rd"
    }
)
print(resp.json())
OAuthTokenResponse
Field Type Nullable Description
access_token string No The JWT access token used for authenticating API requests.
refresh_token string No The refresh token used to obtain a new access token when the current one expires.
token_type string No The token type. Always "Bearer".
expires_in integer No Number of seconds until the access token expires.
id_token string Yes The OIDC ID token containing user identity claims. May be null depending on the provider configuration.
scope string Yes Space-separated list of scopes granted to the token.
OAuthTokenResponse
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 300,
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid profile email"
}
Status Description
200 OK Authentication successful. The response body contains the OAuth 2.0 token set.
401 Unauthorized Invalid credentials. Check username and password.
409 Conflict User data conflict (e.g., duplicate username detected during post-login synchronization).
503 Service Unavailable The identity provider is unreachable. Retry later.

Logout

POST /api/v1/auth/logout

Log out a user by invalidating their refresh token with the OIDC provider. After logout, the refresh token can no longer be used to obtain new access tokens.

LogoutRequest
Field Type Required Description
refreshToken string Yes The refresh token to invalidate.
LogoutRequest
{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
curl -X POST https://koldan.dixilang.com/api/v1/auth/logout \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'
import requests

resp = requests.post(
    "https://koldan.dixilang.com/api/v1/auth/logout",
    json={
        "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
)
print(resp.text)
Response
Status Description
200 OK User successfully logged out. The refresh token has been invalidated.
400 Bad Request Logout failed. The refresh token may be invalid, already expired, or the identity provider rejected the request.

Exchange Authorization Code

POST /api/v1/auth/exchange-auth-code

Exchange an OAuth 2.0 authorization code for an access token using the Authorization Code with PKCE flow. The codeVerifier is required to complete the token exchange securely.

When to use this endpoint

Use this endpoint after completing an OAuth 2.0 / OIDC authorization flow in a browser or mobile application. The authorization server redirects the user back to your application with a code parameter - send that code here to obtain tokens.

UnifiedLoginRequest
Field Type Required Description
code string Yes The authorization code received from the OIDC provider's authorization endpoint.
redirectUri string Yes The redirect URI that was used in the original authorization request. Must match exactly.
codeVerifier string Yes The PKCE code verifier. Must match the code_challenge sent in the original authorization request.
UnifiedLoginRequest
{
  "code": "abc123-authorization-code",
  "redirectUri": "https://app.example.com/callback",
  "codeVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
curl -X POST https://koldan.dixilang.com/api/v1/auth/exchange-auth-code \
  -H "Content-Type: application/json" \
  -d '{
    "code": "abc123-authorization-code",
    "redirectUri": "https://app.example.com/callback",
    "codeVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  }'
import requests

resp = requests.post(
    "https://koldan.dixilang.com/api/v1/auth/exchange-auth-code",
    json={
        "code": "abc123-authorization-code",
        "redirectUri": "https://app.example.com/callback",
        "codeVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
    }
)
print(resp.json())
OAuthTokenResponse

Returns an OAuthTokenResponse object (see field table in Login).

OAuthTokenResponse
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 300,
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid profile email"
}
Status Description
200 OK Token exchange successful. The response body contains the OAuth 2.0 token set.
401 Unauthorized Token exchange failed. The authorization code may be invalid, expired, or the redirect URI does not match.
409 Conflict User data conflict during post-login synchronization.
503 Service Unavailable The identity provider is unreachable. Retry later.

Refresh Token

POST /api/v1/auth/refresh-token

Refresh an expired access token using a valid refresh token. Returns a new OAuth 2.0 token set with a fresh access token.

Token Lifecycle

Access tokens are short-lived (typically 5 minutes). Use this endpoint to seamlessly obtain a new access token without requiring the user to re-authenticate. The refresh token itself may also be rotated - always store the latest refresh_token from the response.

RefreshTokenRequest
Field Type Required Description
refreshToken string Yes The refresh token obtained from a previous login or token refresh response.
RefreshTokenRequest
{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
curl -X POST https://koldan.dixilang.com/api/v1/auth/refresh-token \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'
import requests

resp = requests.post(
    "https://koldan.dixilang.com/api/v1/auth/refresh-token",
    json={
        "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
)
print(resp.json())
OAuthTokenResponse

Returns an OAuthTokenResponse object (see field table in Login).

OAuthTokenResponse
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 300,
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid profile email"
}
Status Description
200 OK Access token successfully refreshed. The response body contains the new OAuth 2.0 token set.
400 Bad Request Failed to refresh access token. The refresh token may be invalid, expired, or already revoked.

Authentication Flows

Koldan supports multiple authentication flows depending on your application type:

Direct Login (Resource Owner Password)

The simplest flow for server-side applications and scripts. Send the user's credentials directly to the Login endpoint.

sequenceDiagram
    participant C as Client
    participant K as Koldan API
    participant O as OIDC Provider

    C->>K: POST /api/v1/auth/login<br>{ username, password }
    K->>O: Token Request
    O-->>K: OAuthTokenResponse
    K-->>C: 200 OK { access_token, … }

Authorization Code Flow with PKCE

Recommended for browser-based and mobile applications. The user authenticates directly with the OIDC provider and your application exchanges the resulting authorization code for tokens using PKCE.

sequenceDiagram
    participant U as User
    participant A as Client App
    participant O as OIDC Provider
    participant K as Koldan API

    U->>A: Login
    A->>A: Generate code_verifier & code_challenge
    A->>O: Redirect to /authorize<br>with code_challenge
    U->>O: Authenticate with provider
    O-->>U: Authenticated
    O-->>A: Redirect with ?code=abc123
    A->>K: POST /api/v1/auth/exchange-auth-code<br>{ code, redirectUri, codeVerifier }
    K-->>A: 200 OK { access_token, … }

PKCE is Required

All clients must use PKCE when using the Authorization Code flow. Generate a code_verifier and code_challenge before the authorization request, then include the codeVerifier when calling the Exchange Authorization Code endpoint.

Using Tokens

Once authenticated, include the access token in the Authorization header of all API requests:

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

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

For API key authentication as an alternative to Bearer tokens, see API Keys.


How API Keys Work

Key Format

All API keys share a recognizable format:

Part Example Description
Prefix kk- Fixed prefix that identifies the string as a Koldan API key
Secret a1b2c3d4e5... (64 characters) Randomly generated secret, hashed before storage

Full key example: kk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2

Store your key immediately

The plain-text key is returned only once - at creation time. Koldan stores a one-way hash and cannot retrieve the original key. If you lose it, revoke the old key and create a new one.

Key Lifecycle

When creating an API key, you provide a name and optionally a description, expiration date, and a set of scopes. The full API key is shown once in the response - store it securely.

An API key can be in one of three states:

State Can Authenticate? Description
Active Key is valid and has not expired or been revoked
Expired Current time is past the key's expiration date
Revoked Key was explicitly revoked by the user or an administrator

Revocation is permanent - once revoked, the key can never be used again.

For the full API reference on creating, listing, and revoking API keys, see API Keys API.


Scoped Permissions

How Scopes Work

Scopes control what an API key is allowed to do. There are two types of API keys:

  • Scoped key - created with an explicit list of scopes. The requested scopes must be a subset of the creator's own role permissions. At runtime, the key's effective permissions are the intersection of its scopes and the user's current role permissions.
  • Identity-only key - created without specifying scopes. The key provides authentication only (identifies the caller) but carries no authorization scopes. Requests to endpoints that require a specific scope will be rejected with 403 Forbidden.

For the complete list of available scopes and how they map to roles, see Roles and Permissions.

Identity-only keys have no permissions

If you create an API key without specifying any scopes, it will not inherit your role's permissions. It will only authenticate your identity. Always specify the scopes your integration needs.


Koldan Authentication Best Practices

  • Use scoped keys - always limit API keys to the minimum scopes required for the task.
  • Set expiration dates - prefer short-lived keys for CI/CD pipelines and automated scripts.
  • Rotate regularly - create a new key, update your integration, then revoke the old one.
  • Never commit keys - store API keys in environment variables or a secrets manager, not in source code.
  • One key per integration - if a key is compromised, you can revoke it without affecting other systems.
  • Use Bearer tokens for interactive apps - API Keys are designed for non-interactive, server-side use cases.
  • Never store tokens in local storage. Use secure, HTTP-only cookies or in-memory storage to protect tokens from XSS attacks.
  • Refresh proactively. Refresh the access token before it expires to avoid request failures. Use the expires_in field to schedule refreshes.
  • Handle token rotation. Always store the latest refresh_token from each response - the OIDC provider may rotate refresh tokens.
  • Always use PKCE. The Authorization Code flow requires PKCE - generate a code_verifier and code_challenge for every authorization request.
  • Invalidate tokens on logout. Always call the Logout endpoint to revoke refresh tokens when a user signs out.
  • Protect credentials in transit. All authentication requests must be made over HTTPS.
  • Prefer authorization code flow. When possible, use the Exchange Authorization Code flow instead of direct login to avoid handling user passwords.