# Authentication > Credential pairs, bearer tokens, the three token audiences, IP allowlisting and rotation. Source: https://casinoapipro.com/docs/authentication Every request outside `/v1/public` and the health probes carries a bearer token. Tokens are minted from a credential pair and are scoped to exactly one operator account. ## Credentials A credential is an `api_key` and an `api_secret`, created in the operator dashboard. We store only a keyed hash of the secret, so we cannot show it to you again and cannot recover it if it is lost — rotate instead. - Test keys are prefixed `ck_test_` and their secrets `cs_test_`. They can only ever touch play money. - Live keys are prefixed `ck_live_` and require a paid plan and an approved account. > **Never ship a secret to a browser** > > A credential authenticates your whole operator account. It belongs on your server. The launch URL is the only thing that is safe to hand to a player, and it carries no credential of yours. ## Exchanging for a token `POST /v1/auth/token` — auth: credential pair **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `api_key` | string | yes | The credential identifier from the dashboard. Begins `ck_test_` or `ck_live_`. | | `api_secret` | string | yes | The secret shown once at creation. | ```bash curl -X POST https://api.casinoapipro.com/v1/auth/token \\ -H "Content-Type: application/json" \\ -d '{ "api_key": "ck_test_7f3c9a21", "api_secret": "cs_test_9d41" }' ``` ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…", "token_type": "Bearer", "expires_in": 3600, "operator": { "id": "op_2f18c4", "name": "Example Casino", "mode": "TEST" } } ``` ## Using the token ```bash curl https://api.casinoapipro.com/v1/games \\ -H "Authorization: Bearer $ACCESS_TOKEN" ``` Cache the token until shortly before `expires_in` elapses. Minting a fresh token on every request works but counts against your rate limit for no benefit. ## Three audiences, three tokens A token is issued for one audience and is rejected everywhere else. This is why a launch token cannot be replayed against the operator API. | Name | Type | Meaning | | --- | --- | --- | | `operator-api` | server → us | Server-to-server. Minted from a credential pair. Everything under `/v1/sessions`, `/v1/transactions`, `/v1/games`. | | `game-launcher` | player browser | Embedded in the launch URL. Scoped to one session, and only valid for `/v1/game/*`. | | `dashboard` | human | Issued at dashboard login for a human user, not for machine traffic. Everything under `/v1/dashboard`. | > **Your API credentials cannot reach /v1/dashboard** > > The badge on each endpoint in these docs says which audience it accepts, and the split is enforced, not advisory. An `operator-api` token presented to a `/v1/dashboard` route is rejected — including the webhook management endpoints, which is where this most often surprises people. Those are configuration, done by a signed-in human in the dashboard, and there is deliberately no machine credential that can rewrite where your events are delivered. If you need webhook endpoints created as part of a provisioning script, tell us and we will set them up with you rather than issue a key that could be used to redirect your own event stream. ## IP allowlisting Live accounts may pin credentials to a set of source addresses. Requests from anywhere else are refused with `IP_NOT_ALLOWED` before the secret is even checked. If you need our egress addresses for your own allowlist, ask support — they are static. ## Permissions A credential can be restricted to what it actually needs. Scopes are `area:read` and `area:write`, across `transactions`, `sessions`, `games`, `history`, `engagement`, and read-only `reports` and `events`. - Every `GET` needs the area’s `read`; every other method needs its `write`. - A `write` includes its own `read` — you do not have to grant both. - A credential with no scopes at all is unrestricted. Every key issued before permissions existed is in that state and keeps working. A request a credential is not scoped for is refused with `INSUFFICIENT_SCOPE`, and the message names the scope it wanted. Set them when you create a key in the dashboard, or change them later without touching the secret. > **The one worth doing** > > Give anything that only reads — a BI job, a reconciliation script, a dashboard of your own — a read-only key. It is the difference between a leaked credential that can copy your figures and one that can empty your float. ## Rotating and revoking - Rotate issues a new secret for the same API key. The previous secret stops working immediately — there is no overlap window, so deploy the new one in the same change. - Revoke kills the credential immediately. In-flight tokens minted from it stop working within seconds. - Delete removes the key and the record of it, including the link from past API request logs. Revoke instead if you may need to answer questions about it later. > **Rotation has no grace period** > > This page used to say the old secret stayed valid for a short overlap. It does not, and never did — the stored hash is replaced in one write. Anything still sending the previous secret starts failing the moment you rotate. > **If a secret leaks** > > Revoke first, ask questions second, then check [the API request log](/docs/request-ids) in the dashboard for calls you do not recognise.