Authentication
The Management API uses JWT (JSON Web Tokens) for authentication. Clients authenticate with email and password to receive an access token and a refresh token. The access token is included in all subsequent requests.
Login
POST /v1/admin/auth/login
Authenticate with email and password to receive a token pair.
Request:
{
"email": "admin@example.com",
"password": "your-secure-password"
}Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900
}| Field | Type | Description |
|---|---|---|
access_token | string | JWT access token for API requests |
refresh_token | string | JWT refresh token for obtaining new access tokens |
token_type | string | Always "Bearer" |
expires_in | integer | Access token TTL in seconds (900 = 15 minutes) |
Status codes: 200 on success, 401 for invalid credentials, 429 if rate-limited.
The login endpoint is rate-limited to 10 attempts per minute per email address to prevent brute-force attacks. After 5 consecutive failed attempts, the account is temporarily locked for 15 minutes.
Refresh Token
POST /v1/admin/auth/refresh
Exchange a valid refresh token for a new access token. Use this to maintain a session without re-authenticating.
Request:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900
}A new refresh token is issued with each refresh request. The previous refresh token is invalidated (rotation).
Status codes: 200 on success, 401 if the refresh token is invalid or expired.
Refresh token rotation ensures that a compromised refresh token can only be used once. If a rotated-out token is reused, all tokens for that user are revoked as a security precaution.
Token Lifetimes
| Token | TTL | Configurable Via |
|---|---|---|
| Access token | 15 minutes | ACCESS_TOKEN_TTL environment variable |
| Refresh token | 7 days | REFRESH_TOKEN_TTL environment variable |
JWT Claims
Access tokens contain the following claims:
{
"sub": "usr_abc123",
"org_id": "org_xyz789",
"role": "admin",
"exp": 1740500400,
"iat": 1740499500
}| Claim | Type | Description |
|---|---|---|
sub | string | User ID |
org_id | string | Organisation ID the user belongs to |
role | string | User role: owner, admin, editor, or viewer |
exp | integer | Token expiration time (Unix timestamp) |
iat | integer | Token issued-at time (Unix timestamp) |
Using Tokens in Requests
Include the access token in the Authorization header of every API request:
curl -X GET http://localhost:8084/v1/admin/sources \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."If the token is missing, malformed, or expired, the API returns a 401 Unauthorized response:
{
"error": {
"code": "EXPIRED_TOKEN",
"message": "The access token has expired. Use the refresh endpoint to obtain a new one."
}
}Token Storage Recommendations
- Browser apps: Store the access token in memory (JavaScript variable). Store the refresh token in an
HttpOnlycookie or secure storage. - Server apps: Store both tokens in a secure credential store or environment variable.
- Never store tokens in
localStorageor expose them in URLs.
Logout
To end a session, discard the tokens on the client side. There is no explicit logout endpoint — tokens are stateless and will expire on their own. If you need to immediately revoke a user’s access, an admin can remove the user via the Users API, which invalidates all their tokens.