Skip to content

Authentication

The platform accepts two kinds of credential. Which one you want depends on whether your software acts as itself or on behalf of a signed-in person.

An API key identifies your integration. It belongs to a tenant (or to a customer within a tenant), never expires unless you give it an expiry, and needs no login round-trip. This is what almost every third-party integration should use.

Keys are prefixed rdk_ followed by random base64 characters — there is no environment or region segment, so do not pattern-match on anything beyond the rdk_ prefix. They can be sent either way:

X-API-Key: rdk_8Kd2mQvXp1nR7wLzT4bY9cHf
Authorization: Bearer rdk_8Kd2mQvXp1nR7wLzT4bY9cHf

Both are equivalent. The Gateway validates the key, resolves its tenant, and forwards a short-lived internal token to whichever service handles the request — your key itself never leaves the Gateway.

From the admin UI, under App Keys for your tenant or customer. Programmatically:

Terminal window
curl -X POST https://riddlerai.io:4443/api/auth/tenants/{tenantId}/api-keys \
-H "Authorization: Bearer <admin-jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "billing-sync (production)",
"scopes": ["llm:read", "llm:write"],
"expiresAt": "2027-01-01T00:00:00Z"
}'

Only name is required. The response contains the plaintext key once, as key:

{
"id": "3f9a...",
"name": "billing-sync (production)",
"key": "rdk_8Kd2mQvXp1nR7wLzT4bY9cHf",
"maskedKey": "rdk_8Kd2...T4bY9cHf",
"status": "active",
"scopes": "[\"llm:read\",\"llm:write\"]",
"createdAt": "2026-07-28T09:12:44Z",
"expiresAt": "2027-01-01T00:00:00Z"
}

Watch the type of scopes. You send an array, and you get back a JSON-encoded string. Parse it before use — a client that assumes the field round-trips as an array will break on the first read.

The key is stored only as a hash, so there is no way to retrieve it afterwards. If you lose it, revoke the key and create another.

Give each environment and each integration its own key. Revoking one then costs you nothing elsewhere, and the usage metrics per key stay meaningful.

Terminal window
curl -X POST https://riddlerai.io:4443/api/auth/tenants/{tenantId}/api-keys/{keyId}/revoke \
-H "Authorization: Bearer <admin-jwt>"

Revocation is reversible — a matching /activate endpoint restores the key. If a key has genuinely leaked, delete it (DELETE .../api-keys/{keyId}) rather than revoking it.

Revocation is also not instantaneous: the Gateway caches successful key validations for up to five minutes, so a revoked key can continue to work for that long. Plan for it when responding to a suspected leak, and rotate the underlying secrets too.

If your software has its own users who each have a RiddlerAI account, authenticate them individually so that actions are attributed to the right person and their own permissions apply.

Terminal window
curl -X POST https://riddlerai.io:4443/api/auth/login \
-H "Content-Type: application/json" \
-d '{ "username": "someone@example.com", "password": "..." }'

The field is username, not email. Its value may be either a username or an email address — both are accepted — but sending an email field fails validation with a 400.

A successful response carries token, refreshToken, expiresAt and user. Send the access token as Authorization: Bearer <token>.

A 200 does not guarantee a token. When the account has two-factor authentication enabled, login returns 200 with requiresTwoFactor: true, a userId and no token; complete the flow at /api/auth/verify-2fa. Check for the token’s presence rather than the status code alone.

Access-token lifetime is configurable and is 120 minutes in the standard deployment. Do not hardcode it — read expiresAt from the login response. When a call returns 401, exchange your refresh token at POST /api/auth/refresh and retry. Refresh tokens rotate on each use, so always store the new one.

Browser clients get the refresh token a second way: it is also set as an httpOnly cookie riddler_refresh_token, scoped to /api/auth. The refresh endpoint reads that cookie when the request body omits a token, which lets a browser app refresh without ever handling the value in JavaScript.

Keys can carry scopes such as llm:read and llm:write, and you should set them to record what an integration is meant to do.

Scopes are not enforced today. They are stored on the key and forwarded to services as claims, but no service currently rejects a request for being out of scope — a valid key for the tenant reaches every endpoint that key's tenant can reach. Enforcement is planned. Set scopes correctly now so that your integration keeps working when it arrives, but do not rely on them as a security boundary, and do not treat a narrowly-scoped key as safe to hand out.

Do not put a key in client-side code. Browser and mobile applications ship their contents to users; a key there is a public key. Call the platform from your own backend, or authenticate each person with their own JWT.

Do not share one key across environments. When staging misbehaves you want to revoke staging.

The Gateway only permits browser requests from origins it has been configured to allow. A web application on an arbitrary origin will be blocked by CORS — which is the intended outcome, because that is not a safe place for a credential. Server-to-server is the supported model. If you have a genuine browser use case, ask your administrator to allow-list the origin.