# Run it from your own servers > Everything the console can do, driven by an API key: catalogue, tournaments, promotions, risk, chat, the operational record, currencies and support — and the four things that deliberately stay behind a person. Source: https://casinoapipro.com/docs/automation Everything your team does in the console, your servers can do with an API key. Not a parallel implementation — the same code, with your credential recorded as the actor instead of a person, so the two cannot validate different things or audit differently. Use the same bearer token as the rest of the API: see [Authentication](/docs/authentication). Everything on this page is scoped to the account the key belongs to, so no endpoint takes an operator id. ## Your catalogue `GET /v1/catalogue` — auth: api key Your games rather than the public list: what is enabled, the release each game is pinned to, and the return that release actually pays. `POST /v1/catalogue/{gameId}/rtp` retunes the return, and `PATCH /v1/catalogue/{gameId}` takes everything the console does: **PATCH body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `enabled` | boolean | no | Whether players can launch it at all. | | `min_bet / max_bet` | string | null | no | Your stake limits. Null clears the override and returns the game to the platform's own limit; absent leaves it alone. | | `max_win` | string | null | no | The most a single round may pay. Can only narrow the platform cap, never widen it — that ceiling is our risk budget rather than yours, so raising it stays an admin action. | | `release_id` | uuid | null | no | Which certified release your players get. Must belong to this game and not be retired. Null returns you to the platform default. | | `thumbnail_url` | string | null | no | Your own artwork. Must be `https://` — the tile renders on a TLS page, so a plain-http image is blocked as mixed content and shows blank. | | `chat_enabled` | boolean | no | Whether the room has chat. | | `demo_players` | integer | no | Ambient players on the shared-round feed. Presentation only; 0–400. | | `feedback_enabled` | boolean | no | Whether players can rate the game. | | `limits` | object | no | Per-currency limits, keyed by ISO 4217 — see below. A currency mapped to `null` clears its override. | ## Limits on a multi-currency account `min_bet`, `max_bet` and `max_win` above carry no currency: they are compared directly against the stake. On a single-currency account that is exactly right. On an account holding two currencies two orders of magnitude apart it is not — a floor of `"0.50"` means 0.50 in both, and no single value is sensible for either. So `limits` sets them per currency. Fields are independent: an entry with only `min_bet` leaves the game's own maximum in force for that currency. A currency with no entry behaves exactly as it always has. ```bash curl -X PATCH https://api.casinoapipro.com/v1/catalogue/$GAME_ID \\ -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -d '{ "limits": { "GHS": { "min_bet": "0.50", "max_bet": "5000" }, "NGN": { "min_bet": "500", "max_bet": "50000" }, "USD": null } }' ``` `GET /v1/catalogue` returns them under the same key, so you can read back what we hold rather than infer it from what you sent. > **These narrow, and a ceiling above ours is still a conversation** > > A per-currency limit is bounded by the same platform figures as the flat one, and that ceiling does not itself vary by currency yet. So you can raise a floor in a large-denomination currency freely — which is the half that was missing — but a maximum above the platform's own is refused, because it spends our exposure rather than yours. Ask us and we will look at it. > **Nothing is converted** > > We do not hold a rate and never will: an exchange rate in the bet path is a number that goes stale and then silently moves every limit on your account. These are the figures you set, in the currency you set them in. > **Limits are checked against the release you are moving to** > > If you change `release_id` and the stake limits in one request, the limits are validated against the _incoming_ release, not the one you are leaving. A minimum above the maximum, or a maximum win below the maximum bet, is refused rather than clamped — silently clamping means the number you typed is not the number you got. > **The rtp you get back is the one that pays** > > Asking for 94% builds a configuration and the response tells you what that configuration actually returns, which can differ once multipliers are floored. Advertise the number in `rtp`, not the one in `asked`. It is `/v1/catalogue` and not `/v1/games` because the launcher already owns `/v1/games` with a player token. The name is better anyway: this is _ your_ catalogue, and `/v1/public/games` is the platform's. ## Promotions on a schedule `POST /v1/promotions/cashback/{ruleId}/run` — auth: api key A weekly cashback period is a cron job, not a button. Create the rule once with `POST /v1/promotions/cashback`, then run it from your own scheduler. > **Safe to call twice** > > The run claims its window before computing anything, so a retry over the same period answers `already_run: true` and pays nobody. This matters more from a machine than from a button, because a machine retries on a timeout. Referrals are a single settings row: `POST /v1/promotions/referrals`. Rain drops free bets into a game's chat for anyone to claim — `POST /v1/rain` — and claims are race-safe, one per player with the total capped in the same transaction. > **Both pay in free bets, never cash** > > We do not hold your float, so a cash credit issued here would be money promised on your behalf with nothing behind it. Read the computed amounts and pay cash through your own wallet if that is what you want. ## Tournaments `POST /v1/tournaments` — auth: api key **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Shown to players on the leaderboard. | | `scoring` | enum | no | One of `TOTAL_WAGERED`, `NET_WIN`, `BIGGEST_MULTIPLIER`, `WIN_COUNT`. | | `starts_at / ends_at` | timestamp | yes | Must end after it starts, and must not already have ended. | | `game_ids` | uuid[] | no | Empty means every game in your catalogue. Each must be in it. | | `prizes` | object[] | yes | One entry per rank. Ranks must be distinct — see below. | > **Two prizes at rank 1 is not a tie** > > It is a table where settlement pays whichever row it finds first, and you have no idea which until it happens. Duplicate ranks are refused. Prizes are frozen at creation: they cannot change once players are competing for them. Cancel with `POST /v1/tournaments/{id}/cancel`, which works only while the tournament is unsettled — a finished tournament has already paid, and un-happening that would leave grants in players' accounts for a competition we claim never ran. `GET /v1/tournaments` returns yours plus the network tournaments your players can enter; `editable` tells you which are yours. `GET /v1/tournaments/{tournamentId}/leaderboard` — auth: api key Standings, ranked, with `prize` and `awarded_at` filled in once a tournament has settled. On a network leaderboard only your own players are named — `is_yours` marks them, and another operator's player ids and turnover are not ours to hand over. ## The risk queue `GET /v1/risk/events` — auth: api key Signals, sorted worst-first, so the thing most worth looking at is never on page three. Mark one reviewed or dismissed with `PATCH /v1/risk/events/{eventId}`. > **A signal is a prompt to look, not a finding** > > These are deterministic rules, not machine learning, and nothing on this surface blocks a player or moves money. Dismissals are audited against the credential that made them: “who decided this was fine, and when” is the question an investigation opens with. ## Chat moderation `POST /v1/chat/mutes` — auth: api key `GET /v1/chat/messages?flagged=true` is the review queue — the messages the filter acted on, with the reason it acted. Mute a player for a number of minutes, or pass `minutes: 0` to unmute. `player_id` accepts your own id for the player, which is what `GET /v1/players` returns. `POST /v1/chat/messages/{messageId}/remove` — auth: api key Takes one message out of the room. It is scoped to your account in the lookup itself, so a guessed id from another operator answers `404` rather than removing anything. Removal is recorded, not destructive — the message stays in the record marked removed. ## The operational record | Name | Type | Meaning | | --- | --- | --- | | `GET /v1/api-logs` | endpoint | What we saw you send — the first thing to check against what you believe you sent. | | `GET /v1/rounds/{roundId}/wallet-calls` | endpoint | The other direction: every call we made to _your_ wallet on a round, with your own response body verbatim. See below. | | `GET /v1/webhook-deliveries` | endpoint | Every delivery, its attempt count, response status and error. | | `POST /v1/webhook-deliveries/{id}/retry` | endpoint | Delivers now and returns the result, so a script draining a backlog after an outage knows whether it worked. | | `GET /v1/sla` | endpoint | Uptime and response times against your plan, with the platform-wide figure beside yours. | | `GET /v1/feedback` | endpoint | Player ratings and comments on your games. | | `GET /v1/notifications` | endpoint | Account-wide notices. A key has no inbox of its own, so anything addressed to one person stays theirs. | | `POST /v1/notifications/read` | endpoint | Marks them read. Send `ids` to mark specific ones, or no body at all to mark everything. | ## Is the shared flight in the air `GET /v1/catalogue/{gameId}/flight` — auth: api key For Goal Jet and anything else where one round is shared across your players. The flight sleeps after about two minutes with nobody playing and wakes on the next session, so `idle` is normal rather than a fault — `idle_seconds` says how long, and `never_flown` means no round has ever run on your account. > **Reading this does not wake it** > > Which is the point. The only way to see a flight used to be to mint a session and look through it, which both spends a session you may not use and starts the thing you were trying to measure — so a stopped flight could not be observed without ending the outage you were trying to confirm. ## Reconciling a round `GET /v1/rounds/{roundId}/wallet-calls` — auth: api key `/v1/api-logs` is what you sent us. This is the reverse, and it is the one you want when a round is disputed: every movement we raised on it, the request as we put it to your wallet, and your wallet's response body exactly as it came back — including `wallet_reference` and the `balance` it reported. Addressed by your own round id or by our reference, so you can start from whichever you are holding. > **attempts above 1 is a retry, not a second movement** > > We retry a call that gave us no usable answer — a timeout, a 502 — always with the same `transaction_id`. That is what makes the retry safe, and why your wallet should answer the second one with `duplicate: true` and move no money. A round showing `attempts: 3` and one movement is that working. ## Currencies `PATCH /v1/business` — auth: api key A session in a currency your account does not hold is refused with `CURRENCY_NOT_SUPPORTED`, and this is how you add one without asking us. ```bash curl -X PATCH https://api.casinoapipro.com/v1/business \\ -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -d '{"currencies":["USD","NGN","GHS"]}' ``` > **Removing a currency players are registered in is refused** > > It would strand them: their sessions would start failing and there is no way back. Refused rather than cascaded — these are your players, and we do not get to decide which of them to break. ## Sandbox wallets `POST /v1/sandbox/players` — auth: api key Play money, with no relationship to real funds. This is what makes an automated integration test possible: set a known balance, play, assert on the result. ## Support `POST /v1/tickets` — auth: api key Useful from a monitor: the alert that noticed the problem can open the ticket about it. The ticket is labelled with the key that raised it, and we reply to your account owner — a credential has no address of its own. `GET /v1/tickets/{ticketId}` — auth: api key One ticket and its conversation in order. Our internal notes are never included: those are support talking to support, and they do not belong on your side of the thread. `POST /v1/tickets/{ticketId}/reply` — auth: api key Adds a message and moves the ticket back to `AWAITING_SUPPORT`. A closed ticket is refused — open a new one rather than reviving a thread we have finished with. ## What a key deliberately cannot do Four things stay behind a person who has signed in. The test is not whether something is tedious in a browser — it is what an attacker gets if the key escapes. - **Issuing, rotating or revoking credentials.** A key that can mint keys turns one leak into permanent access, and one that can revoke them can lock you out of your own account. - **Creating or repointing webhook endpoints.** Where your events go is worth stealing. Reading the delivery log and retrying a delivery is on this side of the line; changing the destination is not. - **Configuring your wallet.** This is the outbound money path. A key that can change the base URL can redirect every debit and credit on the account. - **Team and billing.** Who may act on your account is a people decision, and a key that can promote a member could promote itself to owner. > **This list is enforced by a test, not by a promise** > > A test reads both surfaces out of the source and fails if a console capability has no API counterpart and no stated reason. A new console feature that forgets the API is a broken build rather than a support ticket six months later.