Management APIAuthentication

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.

Browser clients, such as the management console, can instead keep the whole session in HttpOnly cookies, so that no token is ever readable from JavaScript. See Browser sessions.

Login

POST /v1/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": "3f9c2a...",
  "expires_in": 900,
  "orgs": [{ "org_id": "…", "org_name": "Acme", "org_slug": "acme", "role": "org_admin", "is_default": true }],
  "must_change_password": false
}
FieldTypeDescription
access_tokenstringJWT access token for API requests. Omitted for cookie-only clients.
refresh_tokenstringOpaque refresh token for obtaining new access tokens. Omitted for cookie-only clients.
expires_inintegerAccess token lifetime in seconds (900 = 15 minutes)
orgsarrayOrganisations the user belongs to
must_change_passwordbooleanThe user must set a new password before continuing

If the account requires a second factor, the response is a challenge (mfa_required: true with an mfa_token) instead of tokens, and the session is issued by POST /v1/auth/mfa/verify.

The response also sets the session cookies described under Browser sessions.

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/auth/refresh

Exchange a valid refresh token for a new access token. Use this to maintain a session without re-authenticating.

Request:

{
  "refresh_token": "3f9c2a..."
}

The refresh token can come from the request body, as above, or from the df_refresh cookie; the cookie is used when both are present.

Response: the same shape as login. A cookie-only client gets no tokens in the body.

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

TokenLifetime
Access token15 minutes
Refresh token7 days

JWT Claims

Access tokens contain the following claims:

{
  "sub": "usr_abc123",
  "org_id": "org_xyz789",
  "role": "admin",
  "exp": 1740500400,
  "iat": 1740499500
}
ClaimTypeDescription
substringUser ID
org_idstringOrganisation ID the user belongs to
rolestringUser role: owner, admin, editor, or viewer
expintegerToken expiration time (Unix timestamp)
iatintegerToken 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."
  }
}

Browser sessions

Every response that issues a session (login, MFA verification, refresh, switching organisation, SSO sign-in) also sets two cookies:

CookieCarriesPathLifetime
df_accessAccess token/15 minutes
df_refreshRefresh token/v1/auth7 days

Both are HttpOnly, SameSite=Lax, and Secure when the request arrived over HTTPS. The API reads df_access on every request, before any Authorization header, so a browser needs nothing else to stay signed in.

A browser client that relies on the cookies alone adds ?auth_mode=cookie to the auth endpoints (/v1/auth/login, /v1/auth/mfa/verify, /v1/auth/refresh, /v1/auth/switch-org, /v1/auth/sso/start). The API then leaves access_token and refresh_token out of the response body, and an SSO sign-in returns to the console with no tokens in the URL. The management console works this way, so a script injected into the page has no token to read. Cross-origin clients must send requests with credentials (credentials: 'include'), and their origin must be listed in CORS_ALLOWED_ORIGINS.

Streaming endpoints such as GET /v1/admin/flow/stream authenticate the same way: a browser EventSource on the same site sends the cookie.

Token Storage Recommendations

  • Browser apps: Use the cookies and ?auth_mode=cookie, and keep no token in JavaScript.
  • Server apps: Store both tokens in a secure credential store or environment variable.
  • Never store tokens in localStorage or expose them in URLs.

Logout

POST /v1/auth/logout

Revokes the refresh token and clears both cookies. The refresh token comes from the df_refresh cookie or, for clients using bearer tokens, from the body:

{
  "refresh_token": "3f9c2a..."
}

Status codes: always 204, whether or not the token was still valid.

An access token already issued stays valid until it expires, at most 15 minutes. To revoke a user’s access everywhere at once, an admin can remove the user via the Users API, which invalidates all their tokens.