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.
API keys — for your software
Section titled “API keys — for your software”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_8Kd2mQvXp1nR7wLzT4bY9cHfAuthorization: Bearer rdk_8Kd2mQvXp1nR7wLzT4bY9cHfBoth 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.
Creating a key
Section titled “Creating a key”From the admin UI, under App Keys for your tenant or customer. Programmatically:
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.
Revoking
Section titled “Revoking”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.
JWTs — for signed-in people
Section titled “JWTs — for signed-in people”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.
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.
Scopes
Section titled “Scopes”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.
What never to do
Section titled “What never to do”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.
Cross-origin requests
Section titled “Cross-origin requests”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.