openapi: 3.0.3

info:
  title: hizi.io Game Studio API (RGS / Game Studio API V2)
  version: 1.21.0
  description: |
    The interface game engines and third-party RGS use to integrate with the
    hizi.io RGS — game launch, round lifecycle, certified RNG and freeplays.

    Machine-readable description of the **RGS / Game Studio API V2** — the
    interface a game engine or a third-party RGS uses to integrate *with* the
    hizi.io Remote Gaming Server. It is generated from, and kept faithful to,
    the hand-written documentation under `/game-studio-api/`.

    This is **not** the Operator API. The Operator API describes the
    operator ⇄ hizi.io wallet/session protocol (`doTransactions`,
    `getGameURL`, …) and is specified separately. The two protocols share the
    `X-H-AUTH-*` signing header family and the response/error-code catalogue
    (see [Response Codes & Error Codes](/response-codes)), but
    their endpoints and payloads are different.

    ## Who this API is for

    | | Classic RGS-to-RGS (typical) | hizi.io-hosted engine |
    | --- | --- | --- |
    | Where game logic runs | your own infrastructure | inside the hizi.io RGS |
    | Transport | HTTP/2 | RabbitMQ or HTTP/2 |
    | RNG | your own certified RNG, passed in `rngResult` | hizi.io certified RNG via `POST /getRandomResult` |
    | Certification | entirely yours; expose hashes via `GET /certifiedfiles` | built and certified inside the hizi.io stack |

    ## Two transports, one payload set

    The API can be consumed in two ways:

    1. **RabbitMQ** — subscribe to the hizi.io message broker and send RPC
       messages. Recommended by hizi.io (lower resource usage, better
       scalability and failure tolerance). The message payloads and the
       `X-H-*` header names are the same as below; they travel as queue
       message headers instead of HTTP headers, so this document describes
       them as HTTP.
    2. **RESTful HTTP/2** — listen on an HTTP port for the calls under the
       `calls-to-implement` tag and `POST`/`GET` the calls under the other
       tags to the hizi.io RGS. This variant **must** be used for an
       RGS-to-RGS integration where the third-party RGS is not hosted by
       hizi.io.

    ## Directions of traffic

    * Everything served from the `V2_API_ENDPOINT` server is **provided by
      hizi.io** and called by the game frontend, the game engine or the
      third-party RGS.
    * Everything tagged `calls-to-implement` is served from the
      `backendQueue` server, i.e. it is **implemented by you** and called by
      hizi.io (the janitor, the operator support proxy, monitoring and
      certification bodies).

    ## Amounts and currency

    **All amounts in requests and responses are integers in minor units
    (1/100 of the currency).** `currencyMultiplier` is an FX-style factor
    applied **once**, by the game, when building the stake ladder in
    `getGameConfiguration`; it is *not* applied a second time to transaction
    amounts. `debitInformation.debitAmount` is already in player-currency
    minor units and should equal the chosen stake (or the feature price on a
    buy-feature round).

    ## Idempotency and retries

    All calls should be idempotent on the hashes passed. Note the documented
    limit: **V2 calls are only idempotent on gameround / transaction
    (gamestate) hashes while the gameround is still open** — once the round is
    closed a repeated call is treated as a call on a non-open gameround and
    fails.

    On a lost response, *query before retrying*:

    1. Do not immediately retry the original call.
    2. Query the round with `POST /getGameRoundInfo`.
    3. If `status` is `closed` and the transactions match expectation, treat
       it as success. If `status` is `open` with partial processing (debit
       done, win not credited), close the round with `POST /endGameRound`.
    4. Only retry the original call on `5xx`. Never retry `4xx` (e.g. error
       code 15 `GAMEROUNDNOTSTARTED` means the operation cannot proceed).

    See [Handling Lost Responses and Retry Scenarios](/game-studio-api/retries).

    ## Error signalling

    An HTTP `200` normally means success. **If the `X-H-ERROR-ID` response
    header is present on a `200`, the call must be treated as failed as if a
    `400` had been received.** `X-H-ERROR-MSG` may carry a textual
    description. The numeric codes are the shared catalogue documented in
    [Response Codes & Error Codes](/response-codes) and modelled
    here as `ErrorCodeId`.

    ## Round lifecycle

    OpenAPI cannot express call ordering; see the `round-lifecycle` tag
    description for the state machine the round calls form.

    ## Source documents

    [Overview](/game-studio-api/) ·
    [Basics](/game-studio-api/basics) ·
    [Game Launch](/game-studio-api/game-launch) ·
    [Game configuration](/game-studio-api/game-configuration) ·
    [Calls provided by API V2](/game-studio-api/calls-provided) ·
    [Calls to be implemented by games](/game-studio-api/calls-to-implement) ·
    [Data structures](/game-studio-api/data-structures) ·
    [Freeplays](/game-studio-api/freeplays) ·
    [Lost responses & retries](/game-studio-api/retries) ·
    [Signing appendix](/game-studio-api/signing)
  contact:
    name: hizi.io customer support
    url: https://docs.hizi.io/game-studio-api/
  license:
    name: Proprietary — © Hölle Games / hizi.io

servers:
  - url: '{V2_API_ENDPOINT}'
    description: |
      The hizi.io RGS API V2 endpoint. The concrete host is supplied by
      hizi.io per environment; the path component is `/gameapi/v2` (the
      `V2_API_QUEUE` setting for RPC integrations). The value below is the
      sample endpoint used throughout the documentation examples.
    variables:
      V2_API_ENDPOINT:
        default: https://api.sample.hizi.io/gameapi/v2
        description: Base URL of the hizi.io RGS API V2 (host + `/gameapi/v2`).
  - url: '{backendQueue}'
    description: |
      Your own game engine / third-party RGS base URL, configured in the
      hizi.io backoffice as the game's `backendQueue`. Only the operations
      tagged `calls-to-implement` are served from here.
    variables:
      backendQueue:
        default: https://game-engine.example.com
        description: Base URL of the game engine endpoint (`${backendQueue}`).

tags:
  - name: game-launch
    description: |
      Game launch and session handling, called by the **game frontend** over
      `GET` with a `token` query parameter (no request signing).

      On a launch the player's browser is redirected to the game's index
      document with two parameters, e.g.

      ```
      https://test.gamestudio/index.html?token=3869af18-7e8b-415f-b9d7-dd8f2fb61162&login=http%3A%2F%2Fgamestudio%3A4577%2Fgameapi%2Fv2%2Fconnect
      ```

      `token` is a **short-life** token (valid 5 minutes, single use) and
      `login` is the `/connect` URL to call with it. `/connect` returns a
      **long-life** token that authenticates every subsequent call, together
      with the endpoints, balance, game settings and token data the frontend
      needs. If the short-life token fails, the operator has to request a new
      launch URL.
    externalDocs:
      url: https://docs.hizi.io/game-studio-api/game-launch
  - name: round-lifecycle
    description: |
      The gameround calls provided by API V2, used by the game engine or
      third-party RGS. All are `POST` with a JSON body and must be signed
      (see the `V2Signature` security scheme).

      **Ordering (OpenAPI cannot model this):**

      ```
      startGameRound ──► (open round) ──► endGameRound ──► (closed)
            │                  │
            │                  ├─► addWin      (add uncollected/open win)
            │                  ├─► wagerWin    (stake part/all of the open win)
            │                  ├─► collectWin  (credit part/all of the open win)
            │                  ├─► addSpinCounterGameState / getAvailableSpinCounter
            │                  ├─► getGameRoundInfo / getUncollectedAmount
            │                  └─► voidGameRound (void the initial debit)
            └─► with `endGame: true` the round opens, debits and closes in one call
      ```

      * `startGameRound` debits the bet and opens the round; it can credit
        winnings and close the round immediately, and can close a previously
        left-open round first (`closeOpenGame`).
      * `addWin` adds an **uncollected** (open) win; `wagerWin` stakes part or
        all of the open win and records the result as the new open win;
        `collectWin` credits part or all of the open win to the player.
      * `endGameRound` closes the round and collects any remaining open
        winnings. After closing, only `getGameRoundHistoryInfo` applies —
        `getGameRoundInfo` and the mutating calls will fail
        (`GAMEROUNDNOTACTIVE` / `GAMEROUNDNOTSTARTED`).
      * `voidGameRound` only works while the round is open and no credit has
        been processed successfully.
    externalDocs:
      url: https://docs.hizi.io/game-studio-api/calls-provided
  - name: session-and-lookups
    description: |
      Non-transactional lookups and utilities provided by API V2: connection
      test, token handling, balance, operator settings and round history.
    externalDocs:
      url: https://docs.hizi.io/game-studio-api/calls-provided
  - name: certified-rng
    description: |
      Access to the hizi.io certified RNG.

      **Conditional.** Only relevant when the game logic runs *inside* the
      hizi.io RGS and therefore has to obtain its randomness from hizi.io for
      compliance — direct game-engine integrations hosted by hizi.io, or a
      partner RGS hosted within the hizi.io RGS ("RGS inception"). A classic
      RGS-to-RGS integration keeps using its own certified RNG and passes the
      results in the `rngResult` fields.
    externalDocs:
      url: https://docs.hizi.io/game-studio-api/basics
  - name: freeplays
    description: |
      Freeplays (free spins) are granted and revoked by the operator; the
      hizi.io RGS handles assignment and reporting. They have **no dedicated
      endpoints** — they are a cross-cutting concern over the operations
      tagged here:

      * The game **must** implement `GET /getGameConfiguration` so the
        operator can query the available stakes — freeplays are not supported
        without it.
      * Freeplays are **real-money mode only**.
      * `V2_ConnectReply` and every `V2_GameAPIReply` carry `freePlayInfo`
        (the active package: `used`, `granted`, `won`, `isLast`) and
        `freePlaysAvailable` (available packages grouped by stake).
      * To consume a freeplay, set
        `debitInformation.useTicketInfo.useTicket = true` on
        `POST /startGameRound` for a stake that has free spins available. If
        none are available or they are already used, the call returns an error
        (`SPINNOTAVAILABLE`, code 23).
    externalDocs:
      url: https://docs.hizi.io/game-studio-api/freeplays
  - name: calls-to-implement
    description: |
      Calls **you** implement on your `backendQueue` and hizi.io calls into.
      They supply game information, health and auto-closing of rounds.

      Authentication of these inbound calls uses the **same HMAC primitive**
      but a **different construction** from the outbound direction: the RGS
      signs a fixed, per-endpoint ordered list of scalar fields — the request
      body itself is *not* hashed.

      ```
      X-H-AUTH-SIG = base64( HMAC_SHA256( secret, fields.join('#') ) )
      ```

      Note that the secret is used **both** as the HMAC key **and** as the
      first element of the signed string.

      | endpoint | signed | fields (in order) |
      | --- | --- | --- |
      | `GET /getGameConfiguration` | yes | `[ secret, X-H-AUTH-ID, X-H-TIMESTAMP, currency ]` |
      | `POST /getGameResult` | yes | `[ secret, X-H-AUTH-ID, X-H-TIMESTAMP, gameRound ]` |
      | `GET /health` | no | liveness probe |
      | `GET /certifiedfiles` | no | informational |
      | `POST /doAutoClose` | no | carries `X-H-AUTH-ID` + `X-H-TIMESTAMP` only |

      `GET /health` and `GET /certifiedfiles` are intentionally
      unauthenticated and must be reachable from **any** IP — external
      monitoring and certification bodies use them, so do not restrict them to
      the RGS egress range.
    externalDocs:
      url: https://docs.hizi.io/game-studio-api/calls-to-implement

security:
  - V2AuthId: []
    V2Signature: []
    V2Timestamp: []

paths:
  # ---------------------------------------------------------------------------
  # Game frontend calls (GET, token in query, unsigned)
  # ---------------------------------------------------------------------------
  /status:
    get:
      operationId: getStatus
      tags: [session-and-lookups]
      summary: Check API status
      description: |
        Optional. Checks the status of the V2 API. Can be called from the game
        frontend or from the game backend / third-party RGS. Takes no
        parameters and needs no token.
      security: []
      responses:
        '200':
          description: A JSON structure containing a message.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_StatusReply'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /connect:
    get:
      operationId: connect
      tags: [game-launch, freeplays]
      summary: Establish the game session and exchange the short-life token for a long-life token
      description: |
        **Mandatory to use.** Called by the game frontend with the short-life
        `token` received as a launch URL parameter, against the URL passed in
        the launch URL's `login` parameter.

        The short-life token is valid for **5 minutes and can only be used
        once**; if the call fails the operator has to request a new game launch
        URL.

        The reply carries the long-life `token` to use for all subsequent
        calls, the engine endpoints (`backendUrl`, and optionally
        `webSocketUrl`, `refreshUrl`, `logoutUrl`), the player `balance`, the
        `gameSettings` and the `tokenData`. `tokenData.gameRound` carries any
        **open** gameround so the game can resume it. When freeplays are
        active the reply also carries `freePlayInfo` and `freePlaysAvailable`.

        Example: for a launcher called as
        `https://location-of-gamelauncher/index.html?token=206fa387-9565-4fc7-b6e6-b555ae064cfc&login=https%3A%2F%2Fapi.sample.hizi.io%2Fgameapi%2Fv2%2Fconnect`
        the frontend calls
        `https://api.sample.hizi.io/gameapi/v2/connect?token=206fa387-9565-4fc7-b6e6-b555ae064cfc`.
      security: []
      parameters:
        - $ref: '#/components/parameters/TokenQuery'
      responses:
        '200':
          description: Session established; long-life token and game information returned.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_ConnectReply'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '451':
          $ref: '#/components/responses/LegalBlocked'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /reconnect:
    get:
      operationId: reconnect
      tags: [game-launch]
      summary: Re-establish the session using the long-life token
      description: |
        Optional. Same as `/connect` but takes the **long-life** token returned
        by a previous `/connect`. Used to re-establish the connection after
        error messages. This is the call behind the `refreshUrl` returned by
        `/connect`.
      security: []
      parameters:
        - $ref: '#/components/parameters/TokenQuery'
      responses:
        '200':
          description: Session re-established.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_ConnectReply'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /disconnect:
    get:
      operationId: disconnect
      tags: [game-launch]
      summary: Signal that the player closed or left the game
      description: |
        Optional but recommended — some operators require it. Signals closing
        or leaving of the game frontend to the operator. Pass the **long-life**
        token from a previous `/connect`.

        The return of this call is **undetermined** and the frontend does not
        need to wait for it to come back.
      security: []
      parameters:
        - $ref: '#/components/parameters/TokenQuery'
      responses:
        '200':
          description: |
            Call accepted. The documentation states the response is
            undetermined and no payload is specified; do not depend on a body.

  # ---------------------------------------------------------------------------
  # Game engine / RGS calls (POST, signed)
  # ---------------------------------------------------------------------------
  /testPing:
    post:
      operationId: testPing
      tags: [session-and-lookups]
      summary: Echo a test payload to verify communication and payload signature
      description: |
        Optional. Sends a test payload to the API and gets it back in
        `testDataReceived`. Useful to verify basic communication and that your
        payload signature is computed correctly.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_TestPingRequestBody'
            example:
              testData:
                testInt: 1
                testString: something
                testBoolean: true
      responses:
        '200':
          description: The `testData` sent, proxied back in `testDataReceived`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_TestPingReply'
              example:
                testDataReceived:
                  testInt: 1
                  testString: something
                  testBoolean: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /startGameRound:
    post:
      operationId: startGameRound
      tags: [round-lifecycle, freeplays]
      summary: Start a gameround and debit the bet
      description: |
        **Mandatory.** Starts a new gameround and debits the bet. Optionally it
        can credit winnings and close the gameround immediately (`endGame`),
        and can close any existing open gameround and collect its open
        winnings first (`closeOpenGame`).

        This is the entry point of the round state machine: it is the call that
        creates the gameround hash returned as `gameRoundInfo.hash` (and as
        `tokenData.gameRound`), which all subsequent round calls reference.

        * `rngResult` carries the result of the RNG that produced the outcome —
          your own certified RNG for a classic RGS-to-RGS integration, or the
          result of `POST /getRandomResult` for a hizi.io-hosted engine.
        * `debitInformation.debitAmount` is the already-multiplied stake in
          player-currency minor units (or the feature price on a buy-feature
          round).
        * To consume a freeplay set
          `debitInformation.useTicketInfo.useTicket = true`; the reply carries
          updated `freePlayInfo` / `freePlaysAvailable`.
        * `externalGameProviderRoundId` must not exceed 32 characters.

        **Retry semantics:** idempotent on the transaction hash while the round
        is open. On a lost response, query with `POST /getGameRoundInfo` rather
        than blindly repeating the debit.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_StartGameRoundRequestBody'
            examples:
              noWin:
                summary: Bet of 1.00 EUR with no win, closing the round immediately
                value:
                  token: 83020869-abc0-4123-a95e-ee4f5e8e8343
                  rngResult:
                    rngCallId: 5cd23259ea4c49e7b955e79321da4365
                    result: test
                    winAmount: 0
                  debitInformation:
                    debitAmount: 100
                    currency: EUR
                  endGame: true
              withWin:
                summary: Bet of 1.00 EUR with a 0.77 EUR win left open
                value:
                  token: 83020869-abc0-4123-a95e-ee4f5e8e8343
                  rngResult:
                    rngCallId: 7c1d45ad589046f3b52fe779ffd96632
                    result: test
                    winAmount: 77
                  debitInformation:
                    debitAmount: 100
                    currency: EUR
                  endGame: false
              freeplay:
                summary: Consuming an available freeplay for the 1.00 EUR stake
                value:
                  token: 83020869-abc0-4123-a95e-ee4f5e8e8343
                  rngResult:
                    rngCallId: 5cd23259ea4c49e7b955e79321da4365
                    result: test
                    winAmount: 0
                  debitInformation:
                    debitAmount: 100
                    currency: EUR
                    useTicketInfo:
                      useTicket: true
                  endGame: false
      responses:
        '200':
          description: |
            Round started. `gameRoundInfo.status` is `open`, or `closed` when
            `endGame` was `true`. `amountToCollect` is the open (uncollected)
            win, `amountCredited` what was credited in this call.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-H-ERROR-MSG:
              $ref: '#/components/headers/XHErrorMsg'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: 83020869-abc0-4123-a95e-ee4f5e8e8343
                  language: DE
                  currencyMultiplier: 1
                  gameRound: 9f3e51d518024a98977be80c30106cc3
                balance:
                  totalBalance: 6916
                  mode: REAL
                  currency: EUR
                  balances:
                    - type: real
                      amount: 6916
                      currency: EUR
                amountToCollect: 0
                amountCredited: 0
                gameRoundInfo:
                  hash: 9f3e51d518024a98977be80c30106cc3
                  status: closed
                  stake: 100
                  mode: REAL
                  currency: EUR
                  game: efbdc5b0ef8c434cbf11daab2fda0d5d
                gameState:
                  - hash: 9f3e51d518024a98977be80c30106cc3
                    type: debit
                    result:
                      winAmount: 0
                    processedOn: '2023-01-26T11:28:22.126Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /endGameRound:
    post:
      operationId: endGameRound
      tags: [round-lifecycle]
      summary: Close a gameround and collect any open winnings
      description: |
        **Mandatory.** Ends an open gameround and collects any open winnings.

        This is the terminal transition of the round state machine. Once the
        round is closed the round calls are no longer idempotent on their
        hashes — a repeated call is treated as a call on a non-open gameround
        and fails (`GAMEROUNDNOTACTIVE`). Use `getGameRoundHistoryInfo` for
        closed rounds.

        This is also the call to use to clean up after a lost response where
        `getGameRoundInfo` shows the round still `open` with partial
        processing.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_EndGameRoundRequestBody'
            example:
              token: d182d578-dcce-4850-b091-205c482c0f58
              gameRound: 3c2d66db3e5142d1986e210c809f88e4
      responses:
        '200':
          description: Round closed; `gameRoundInfo.status` is `closed`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-H-ERROR-MSG:
              $ref: '#/components/headers/XHErrorMsg'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: d182d578-dcce-4850-b091-205c482c0f58
                  language: DE
                  currencyMultiplier: 1
                  gameRound: 3c2d66db3e5142d1986e210c809f88e4
                balance:
                  totalBalance: 6954
                  mode: REAL
                  currency: EUR
                  balances:
                    - type: real
                      amount: 6954
                      currency: EUR
                amountToCollect: 0
                amountCredited: 0
                gameRoundInfo:
                  hash: 3c2d66db3e5142d1986e210c809f88e4
                  status: closed
                  stake: 13
                  mode: REAL
                  currency: EUR
                  game: efbdc5b0ef8c434cbf11daab2fda0d5d
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getGameRoundInfo:
    post:
      operationId: getGameRoundInfo
      tags: [round-lifecycle]
      summary: Get information about an open gameround
      description: |
        Optional but strongly recommended: this is the call the retry guidance
        is built on. Returns information about a gameround — its status, stake,
        the uncollected amount and the `gameState` transaction list.

        Use it **before** retrying any transactional call whose response was
        lost, to decide whether to treat the operation as done or to close the
        round with `endGameRound`. A failure with error code 15
        (`GAMEROUNDNOTSTARTED`) means the round was never started.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetGameRoundDetailsRequestBody'
            example:
              token: 83020869-abc0-4123-a95e-ee4f5e8e8343
              gameRound: ff85e29cb4414c89a89829c8411dacc2
      responses:
        '200':
          description: Current state of the gameround.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: 83020869-abc0-4123-a95e-ee4f5e8e8343
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
                amountToCollect: 77
                gameRoundInfo:
                  hash: ff85e29cb4414c89a89829c8411dacc2
                  status: open
                  stake: 100
                  mode: REAL
                  currency: EUR
                  game: efbdc5b0ef8c434cbf11daab2fda0d5d
                gameState:
                  - hash: ff85e29cb4414c89a89829c8411dacc2
                    type: debit
                    result:
                      winAmount: 77
                    processedOn: '2023-01-26T11:28:22.810Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getGameRoundHistoryInfo:
    post:
      operationId: getGameRoundHistoryInfo
      tags: [round-lifecycle]
      summary: Get historical information about a closed gameround
      description: |
        Optional. Returns historical information about a gameround, plus any
        `protectedInfo` stored for it.

        **This call is not available for open gamerounds** — use
        `POST /getGameRoundInfo` for those.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetGameRoundDetailsRequestBody'
            example:
              token: 83020869-abc0-4123-a95e-ee4f5e8e8343
              gameRound: ff85e29cb4414c89a89829c8411dacc2
      responses:
        '200':
          description: Historical state of the closed gameround, including `protectedInfo`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIHistoryReply'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /voidGameRound:
    post:
      operationId: voidGameRound
      tags: [round-lifecycle]
      summary: Void the debit of an open gameround
      description: |
        Optional. Voids the debit of a gameround. The operator receives a
        `doTransactions` call with a **void** operation for the initial debit,
        and `gameRoundInfo.status` becomes `cancel`.

        **This call fails if the gameround is not open, or if any credit has
        already been processed successfully.**
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_VoidGameRoundRequestBody'
            example:
              token: 83020869-abc0-4123-a95e-ee4f5e8e8343
              gameRound: ff85e29cb4414c89a89829c8411dacc2
      responses:
        '200':
          description: |
            Debit voided; `gameRoundInfo.status` is `cancel`. A 200 always
            means success — `errorCode` only appears in the body on failure,
            it is never present alongside a successful void. If `balance` is
            present in the response it is not guaranteed accurate for this
            call and must not be relied on as authoritative; re-fetch the
            player's balance separately if you need a trustworthy figure.

            Note: the documented example for this call returns `balance` as a
            **scalar** amount rather than the `GameAPIBalanceReply` object the
            published `V2_GameAPIReply` type declares, and omits `tokenData`.
            The schema below follows the published type; treat `balance` here
            defensively.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-H-ERROR-MSG:
              $ref: '#/components/headers/XHErrorMsg'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIReply'
              example:
                gameRoundInfo:
                  hash: 8f105c8a01fc46d7bb45c317c619445a
                  status: cancel
                  stake: 13
                  mode: REAL
                  currency: EUR
                  game: 093a992277e14d6bbb1e5ead156f6734
                  baseStake: 13
                  currencyMultiplier: 1
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /collectWin:
    post:
      operationId: collectWin
      tags: [round-lifecycle]
      summary: Collect part or all of the open winnings
      description: |
        Optional. Credits part or all of the open (uncollected) winnings to the
        player, and can close the gameround once everything open has been
        collected.

        `collectInformation.creditAmount` is optional — omitting it collects
        the full open amount. The reply reports `amountCredited` for this call
        and the remaining `amountToCollect`.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_CollectWinRoundRequestBody'
            example:
              token: 83020869-abc0-4123-a95e-ee4f5e8e8343
              gameRound: ff85e29cb4414c89a89829c8411dacc2
              collectInformation:
                currency: EUR
                creditAmount: 37
      responses:
        '200':
          description: Winnings credited; remaining open amount in `amountToCollect`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-H-ERROR-MSG:
              $ref: '#/components/headers/XHErrorMsg'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: 83020869-abc0-4123-a95e-ee4f5e8e8343
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
                balance:
                  totalBalance: 6853
                  mode: REAL
                  currency: EUR
                  balances:
                    - type: real
                      amount: 6853
                      currency: EUR
                amountToCollect: 40
                amountCredited: 37
                gameRoundInfo:
                  hash: ff85e29cb4414c89a89829c8411dacc2
                  status: open
                  stake: 100
                  mode: REAL
                  currency: EUR
                  game: efbdc5b0ef8c434cbf11daab2fda0d5d
                gameState:
                  - hash: ff85e29cb4414c89a89829c8411dacc2
                    type: debit
                    result:
                      winAmount: 77
                    processedOn: '2023-01-26T11:28:22.810Z'
                  - hash: 9e959481e6c8411f88a0fcdb43e9e55f
                    type: credit
                    result: {}
                    collected: 37
                    reason: collect 0.37 EUR
                    processedOn: '2023-01-26T11:28:23.260Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /addWin:
    post:
      operationId: addWin
      tags: [round-lifecycle]
      summary: Add an uncollected (open) win to a gameround
      description: |
        **Mandatory.** Adds an uncollected (open) win to an open gameround.
        Optionally collects the win amount immediately and closes the gameround
        (`collect`).

        The win is recorded in `gameState` as a `genericwin` entry and raises
        `amountToCollect`.

        **Retry semantics:** if the response is lost, do **not** blindly repeat
        the call. Query `POST /getGameRoundInfo`; if the round shows `closed`
        with the expected win, log success, and if it shows `open`, close it
        with `POST /endGameRound`. Only `5xx` failures should be retried.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_AddWinRequestBody'
            example:
              token: d182d578-dcce-4850-b091-205c482c0f58
              gameRound: ff85e29cb4414c89a89829c8411dacc2
              rngResult:
                rngCallId: 69cf2876471845ba8e97f3adbdf1d163
                result: test-uncollected
                winAmount: 10
      responses:
        '200':
          description: Win added; new open amount in `amountToCollect`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-H-ERROR-MSG:
              $ref: '#/components/headers/XHErrorMsg'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: d182d578-dcce-4850-b091-205c482c0f58
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
                amountToCollect: 89
                gameState:
                  - hash: ff85e29cb4414c89a89829c8411dacc2
                    type: debit
                    result:
                      winAmount: 77
                    processedOn: '2023-01-26T11:28:22.810Z'
                  - hash: 9e959481e6c8411f88a0fcdb43e9e55f
                    type: credit
                    result: {}
                    collected: 37
                    reason: collect 0.37 EUR
                    processedOn: '2023-01-26T11:28:23.260Z'
                  - hash: 7ac21c2772124befbcd5c70500f421a8
                    type: wager
                    result:
                      winAmount: 60
                    amountWagered: 21
                    processedOn: '2023-01-26T11:28:24.037Z'
                  - hash: cfbbf8f2a6554d1c9738ecd14aabbb8a
                    type: genericwin
                    result:
                      winAmount: 10
                    processedOn: '2023-01-26T11:28:24.647Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /wagerWin:
    post:
      operationId: wagerWin
      tags: [round-lifecycle]
      summary: Wager part or all of the open winnings
      description: |
        Optional. Wagers (stakes) part or all of the open winnings and records
        the result as the new open win amount — the gamble / double-up
        mechanic. Recorded in `gameState` as a `wager` entry with
        `amountWagered`.

        `wagerInformation.wagerAmount` is the amount taken from the open win;
        `rngResult.winAmount` is the outcome that becomes the new open win.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_WagerWinRoundRequestBody'
            example:
              token: d182d578-dcce-4850-b091-205c482c0f58
              gameRound: ff85e29cb4414c89a89829c8411dacc2
              rngResult:
                rngCallId: c976d024c19e4a76940b7302beb87939
                result: test
                winAmount: 60
              wagerInformation:
                currency: EUR
                wagerAmount: 21
      responses:
        '200':
          description: Wager processed; new open amount in `amountToCollect`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-H-ERROR-MSG:
              $ref: '#/components/headers/XHErrorMsg'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: d182d578-dcce-4850-b091-205c482c0f58
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
                amountCredited: 0
                amountToCollect: 79
                gameState:
                  - hash: ff85e29cb4414c89a89829c8411dacc2
                    type: debit
                    result:
                      winAmount: 77
                    processedOn: '2023-01-26T11:28:22.810Z'
                  - hash: 9e959481e6c8411f88a0fcdb43e9e55f
                    type: credit
                    result: {}
                    collected: 37
                    reason: collect 0.37 EUR
                    processedOn: '2023-01-26T11:28:23.260Z'
                  - hash: 7ac21c2772124befbcd5c70500f421a8
                    type: wager
                    result:
                      winAmount: 60
                    amountWagered: 21
                    processedOn: '2023-01-26T11:28:24.037Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getUncollectedAmount:
    post:
      operationId: getUncollectedAmount
      tags: [round-lifecycle]
      summary: Get the amount of uncollected (open) winnings
      description: Optional. Returns the amount of uncollected (open) winnings of a gameround.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetUncollectedAmountRequestBody'
            example:
              token: d182d578-dcce-4850-b091-205c482c0f58
              gameRound: ff85e29cb4414c89a89829c8411dacc2
      responses:
        '200':
          description: The open (uncollected) amount.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIGetUncollectedAmountReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: d182d578-dcce-4850-b091-205c482c0f58
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
                amountToCollect: 79
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /addSpinCounterGameState:
    post:
      operationId: addSpinCounterGameState
      tags: [round-lifecycle]
      summary: Award or consume a spin counter (e.g. free spins) inside a round
      description: |
        Optional — **not needed for an RGS-to-RGS integration.**

        Adds a game request carrying a counter (e.g. free spins won, via
        `rngResult.result.awarded`) or consumes a counter (e.g. a free spin
        played, via `rngResult.result.used`). When consuming, the RGS checks
        that the game state is available, so it is not possible to consume more
        spins than were awarded.

        The reply reports the remaining `spinsAvailable`.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_AddSpinCounterGameStateRequestBody'
            example:
              token: d182d578-dcce-4850-b091-205c482c0f58
              gameRound: ff85e29cb4414c89a89829c8411dacc2
              counterType: customspin
              rngResult:
                rngCallId: 937bfc0d50c4493fa47ce7221d9afc12
                result:
                  awarded: 10
                winAmount: 0
      responses:
        '200':
          description: Counter recorded; available spins in `spinsAvailable`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-H-ERROR-MSG:
              $ref: '#/components/headers/XHErrorMsg'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIAddSpinCounterGameStateReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: d182d578-dcce-4850-b091-205c482c0f58
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
                amountToCollect: 89
                spinsAvailable: 10
                gameState:
                  - hash: ff85e29cb4414c89a89829c8411dacc2
                    type: debit
                    result:
                      winAmount: 77
                    processedOn: '2023-01-26T11:28:22.810Z'
                  - hash: 9e959481e6c8411f88a0fcdb43e9e55f
                    type: credit
                    result: {}
                    collected: 37
                    reason: collect 0.37 EUR
                    processedOn: '2023-01-26T11:28:23.260Z'
                  - hash: 7ac21c2772124befbcd5c70500f421a8
                    type: wager
                    result:
                      winAmount: 60
                    amountWagered: 21
                    processedOn: '2023-01-26T11:28:24.037Z'
                  - hash: cfbbf8f2a6554d1c9738ecd14aabbb8a
                    type: genericwin
                    result:
                      winAmount: 10
                    processedOn: '2023-01-26T11:28:24.647Z'
                  - hash: 8531967219184c98a06cba7a009640b8
                    type: genericwin
                    result:
                      winAmount: 10
                    processedOn: '2023-01-26T11:28:24.806Z'
                  - hash: 15171253390246f18d889d9d1c18e893
                    type: credit
                    result: {}
                    collected: 10
                    reason: collect 0.10 EUR
                    processedOn: '2023-01-26T11:28:24.826Z'
                  - hash: 9ac37a5ceb754f78a51645fd8a9a316f
                    type: customspin
                    result:
                      winAmount: 0
                    processedOn: '2023-01-26T11:28:25.227Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getAvailableSpinCounter:
    post:
      operationId: getAvailableSpinCounter
      tags: [round-lifecycle]
      summary: Get the current value of a spin counter
      description: |
        Optional — **not needed for an RGS-to-RGS integration.**

        Returns the value of a spin counter, e.g. the number of free spins
        still available after `POST /addSpinCounterGameState`.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetAvailableSpinCounterRequestBody'
            example:
              token: d182d578-dcce-4850-b091-205c482c0f58
              gameRound: ff85e29cb4414c89a89829c8411dacc2
              counterType: customspin
      responses:
        '200':
          description: The counter value in `spinsAvailable`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIGetAvailableSpinCounterReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: d182d578-dcce-4850-b091-205c482c0f58
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
                amountToCollect: 104
                spinsAvailable: 9
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getTokenForGameRound:
    post:
      operationId: getTokenForGameRound
      tags: [session-and-lookups]
      summary: Get a fresh token for an open gameround (janitor / autoclose use)
      description: |
        Optional. Returns a new token for an open gameround, for use by
        janitor / autoclose routines — in particular after receiving
        `POST /doAutoClose`, which carries no player token.

        **This call revokes any existing tokens for the given player /
        operator / game.** It should only be used in janitor calls.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetTokenForGameRoundRequestBody'
            example:
              gameRound: ff85e29cb4414c89a89829c8411dacc2
      responses:
        '200':
          description: Token data containing the new `currentToken`.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GetTokenForGameRoundReply'
              example:
                tokenData:
                  operatorId: 000063d0ef40000000000000001b
                  playerId: c9c5385f9ee449459fcd6ff39ac53911
                  gameId: efbdc5b0ef8c434cbf11daab2fda0d5d
                  mode: REAL
                  currency: EUR
                  currentToken: d182d578-dcce-4850-b091-205c482c0f58
                  language: DE
                  currencyMultiplier: 1
                  gameRound: ff85e29cb4414c89a89829c8411dacc2
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getTokenData:
    post:
      operationId: getTokenData
      tags: [session-and-lookups]
      summary: Resolve a token into its token data
      description: |
        Optional. Returns the token data for a given token ID — operator,
        player, game, mode, currency, language, `currencyMultiplier` and any
        open `gameRound`.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetTokenDataRequestBody'
            example:
              token: cfa822f6-4f53-4ecb-a6f9-647788a6fcd5
      responses:
        '200':
          description: The token data.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GetTokenDataReply'
              example:
                tokenData:
                  operatorId: 000063ea2b200000000000000029
                  playerId: cd4def34682447ab9b5684340bf25cfe
                  gameId: b42a6a2e13554d9cb5c1da72fcb22b8b
                  mode: REAL
                  currency: EUR
                  currentToken: cfa822f6-4f53-4ecb-a6f9-647788a6fcd5
                  language: DE
                  currencyMultiplier: 1
                  gameRound: eb6320faf7ac4cdc85ce6f0043573f2f
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getLastGameRounds:
    post:
      operationId: getLastGameRounds
      tags: [session-and-lookups]
      summary: Get a list of recently closed gamerounds
      description: |
        Optional. Returns a list of recently closed gamerounds for the player
        the token belongs to — typically used for in-game history.

        `maxRecords` defaults to 50 and `maxDays` to 30.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetLastGamesRequestBody'
            example:
              token: d182d578-dcce-4850-b091-205c482c0f58
      responses:
        '200':
          description: List of recently closed gamerounds.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GetLastGamesReply'
              example:
                gameRoundList:
                  - id: a016d46dd75b4725baf06d0ef0fdbe7f
                    status: closed
                    timestamp: '2023-01-26T11:28:28.953Z'
                    bet: 13
                    won: 10
                    currency: EUR
                    gameSpecificDetails:
                      result:
                        result: test
                        rngCallId: 830e8c88405a48b2af5b33f89745084e
                        winAmount: 10
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getRandomResult:
    post:
      operationId: getRandomResult
      tags: [certified-rng]
      summary: Get random integers or doubles from the hizi.io certified RNG
      description: |
        **Conditional.** Returns random integers or doubles within a given
        range from the hizi.io certified RNG.

        Required **only** when the game logic runs inside the hizi.io RGS and
        must obtain its randomness from hizi.io for compliance — direct
        game-engine integrations hosted by hizi.io, or a partner RGS hosted
        within the hizi.io RGS ("RGS inception").

        **A classic RGS-to-RGS integration does not call this endpoint.** If
        your RGS runs on your own infrastructure with your own certified (e.g.
        provably-fair) RNG, keep using it and pass its results in the
        `rngResult` fields of `startGameRound`, `addWin` and the other round
        calls. Your certification is entirely yours; hizi.io is not involved,
        provides no certification suite for it, and no part of it expects
        `/getRandomResult` calls from such an integration.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetRandomResultRequestBody'
            example:
              token: 123162d0-315d-474b-930c-7e43625c2ae3
              rngEndPoint: getDoubles
              rngPayload:
                min: 1
                max: 10
                count: 2
      responses:
        '200':
          description: |
            The requested random values. Note that the published
            `V2_GameAPIRngReply` type declares `rngRequestId`, but the
            documented example response does not include it.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2_GameAPIRngReply'
              example:
                values:
                  - 3.068702629156329
                  - 9.509780327419449
                tokenData:
                  operatorId: 000063d0ef400000000000000029
                  playerId: 9f1cfa5dfcbd4946817675ecbccc26c5
                  gameId: f1252f6014084e1cbf5bb1982f911e0c
                  mode: REAL
                  currency: EUR
                  currentToken: 123162d0-315d-474b-930c-7e43625c2ae3
                  language: DE
                  currencyMultiplier: 1
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getOperatorSettings:
    get:
      operationId: getOperatorSettings
      tags: [session-and-lookups]
      summary: Get the operator-specific game settings
      description: |
        Optional. Returns the operator-specific settings for a game / operator
        / currency combination — the same `GameSettings` structure the frontend
        receives from `/connect`.

        Unlike the other engine calls this is a **GET** with query parameters
        and no body; it is still signed with the `X-H-AUTH-*` headers.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
        - name: game
          in: query
          required: true
          description: Game identifier (as configured in the hizi.io backoffice).
          schema:
            type: string
          example: f1252f6014084e1cbf5bb1982f911e0c-unk
        - name: operator
          in: query
          required: true
          description: Operator identifier.
          schema:
            type: string
          example: 000063d0ef400000000000000029-unk
        - name: currency
          in: query
          required: true
          description: ISO currency code the settings are requested for.
          schema:
            type: string
          example: EUR
      responses:
        '200':
          description: The operator-specific game settings.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GameSettings'
              example:
                autoplayEnabled: true
                autoplayLossLimitRequired: false
                displayCoins: true
                displayJackpotOdds: false
                displayRTP: false
                displayXRTP: false
                forceOrientation: 0
                gambleEnabled: true
                historyURL: ''
                homeURL: ''
                loadMsg: ''
                maxExposure: 25000000
                multipleInstancesAllowed: false
                rcDisplayWinLoss: false
                rcEnabled: false
                stopEnabled: true
                topupURL: ''
                turboEnabled: true
                homeEnabled: true
                dynamicMinSpinTime: false
                redirectTarget: top
                packageBuyEnabled: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  /getBalance:
    post:
      operationId: getBalance
      tags: [session-and-lookups]
      summary: Get the player's balance
      description: |
        Optional. Returns the player's balance for the session the token
        belongs to. Unlike most calls the reply is a bare
        `GameAPIBalanceReply` — it is **not** wrapped in a
        `V2_GameAPIReply`.
      parameters:
        - $ref: '#/components/parameters/XRequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V2_GetTokenDataRequestBody'
            example:
              token: cfa822f6-4f53-4ecb-a6f9-647788a6fcd5
      responses:
        '200':
          description: The player's balance.
          headers:
            X-Response-Status:
              $ref: '#/components/headers/XResponseStatus'
            X-H-ERROR-ID:
              $ref: '#/components/headers/XHErrorId'
            X-Response-Id:
              $ref: '#/components/headers/XResponseId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GameAPIBalanceReply'
              example:
                totalBalance: 6816
                mode: REAL
                currency: EUR
                balances:
                  - type: real
                    amount: 6816
                    currency: EUR
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '5XX':
          $ref: '#/components/responses/ServerError'

  # ---------------------------------------------------------------------------
  # Calls to be implemented by the game engine / third-party RGS
  # (served from ${backendQueue}, called by hizi.io)
  # ---------------------------------------------------------------------------
  /certifiedfiles:
    get:
      operationId: getCertifiedFiles
      tags: [calls-to-implement]
      summary: 'Implemented by you: return the certified components and their hashes'
      description: |
        Optional in general, **mandatory for some jurisdictions.** Returns
        information about your certified components with their hashes. No
        parameters are passed.

        For a classic RGS-to-RGS integration this is how you expose your own
        certified file hashes — your certification is run entirely by you.

        **Not signed and not IP-restricted:** certification bodies and external
        monitoring call this endpoint, so leave it open to all IPs.
      servers:
        - url: '{backendQueue}'
          variables:
            backendQueue:
              default: https://game-engine.example.com
      security: []
      responses:
        '200':
          description: The list of certified files with hashes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CertifiedFilesReply'
              example:
                list:
                  - file: ./dist/certified/certifiedLogic.js
                    hash: e6a01ef0f88b1cac2696ce30166fca0bf6b6c793
                  - file: ./dist/certified/certifiedGameSettings.js
                    hash: 1409450b798f0ac552ea1c128ff40b0f137e3f38

  /getGameConfiguration:
    get:
      operationId: getGameConfiguration
      tags: [calls-to-implement, freeplays]
      summary: 'Implemented by you: return the game configuration for a currency'
      description: |
        Optional in general, but **mandatory if freeplays are to be
        supported**, and mandatory for some operators. Returns the game
        configuration, including the allowed stakes.

        Apply `currencyMultiplier` **once**, here, when building the stake
        ladder:

        ```
        stakes = baseStakes.map(s => s * currencyMultiplier)
        ```

        The same factor scales `maxStake` / `minStake` / `maxPackageStake`. The
        resulting stakes are the amounts the player actually stakes, in
        player-currency minor units. Do **not** apply the multiplier a second
        time to the `debitInformation` / `creditInformation` amounts you later
        send to the RGS.

        **Signing:** this call is signed by the RGS over the ordered field list
        `[ secret, X-H-AUTH-ID, X-H-TIMESTAMP, currency ]` joined with `#` —
        **not** over the request.
      servers:
        - url: '{backendQueue}'
          variables:
            backendQueue:
              default: https://game-engine.example.com
      parameters:
        - name: currency
          in: query
          required: true
          description: ISO currency code the configuration is requested for. This value is part of the signed string.
          schema:
            type: string
          example: EUR
        - name: currencyMultiplier
          in: query
          required: true
          description: |
            FX-style factor for the currency (e.g. EUR 1, USD 1, BRL 6,
            JPY 150) to be applied once when building the stake ladder.
          schema:
            type: number
          example: 1
        - name: gameCode
          in: query
          required: true
          description: Game code the configuration is requested for.
          schema:
            type: string
          example: game-bells
      responses:
        '200':
          description: |
            The game configuration. `stakes`, `currency`, `currencyMultiplier`,
            `expectedRtp`, `maxPayoutOdds` and the top-level `gameCode` are
            mandatory (confirmed against the original red-highlighted source);
            everything else in `config` is documented-but-optional.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GameConfigurationReply'
              example:
                config:
                  stakes: [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000]
                  currency: EUR
                  currencyMultiplier: 1
                  expectedRtp: 96.19
                  oneHundredXOdds: 5522
                  maxWinOdds: 103680
                  maxPayout: 1000
                  maxPayoutOdds: 2488327800
                  maxExposure: 10000000
                  calculatedMaxExposure: 10000000
                  operator: internal_testoperator
                  winlineCount: 5
                gameCode: game-bells
        '401':
          $ref: '#/components/responses/Unauthorized'

  /health:
    get:
      operationId: getHealth
      tags: [calls-to-implement]
      summary: 'Implemented by you: liveness probe'
      description: |
        **Mandatory.** Must return HTTP 200 and a JSON structure with
        information about game health. The format is free. Can also be used for
        testing purposes.

        **Not signed and not IP-restricted:** this is a liveness probe used by
        external monitoring, so do not require a signature and leave it
        reachable from any IP.
      servers:
        - url: '{backendQueue}'
          variables:
            backendQueue:
              default: https://game-engine.example.com
      security: []
      responses:
        '200':
          description: The game is healthy. Payload format is free.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthReply'

  /getGameResult:
    post:
      operationId: getGameResult
      tags: [calls-to-implement]
      summary: 'Implemented by you: render a gameround result as HTML'
      description: |
        **Mandatory.** Returns an HTML representation of a game result. The
        response is proxied back to a request made by an operator, so the
        rendering should include `externalPlayerId` in order to resemble the
        information present in operator support systems.

        With `short` unset or `false`, return a representation that can be
        displayed to the player (used by some operators for in-game history).

        **Signing:** the RGS signs the ordered field list
        `[ secret, X-H-AUTH-ID, X-H-TIMESTAMP, gameRound ]` joined with `#` —
        only the `gameRound` value is signed, **not** the full JSON body.
      servers:
        - url: '{backendQueue}'
          variables:
            backendQueue:
              default: https://game-engine.example.com
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GetGameResultRequestBody'
      responses:
        '200':
          description: An object with the key `msg` containing the HTML representation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetGameResultReply'
              example:
                msg: <html>...</html>
        '401':
          $ref: '#/components/responses/Unauthorized'

  /doGetGameHistory:
    post:
      operationId: doGetGameHistory
      tags: [calls-to-implement]
      summary: 'Implemented by you: return in-game history of game events'
      description: |
        Optional. Can be used to display in-game history of game events.

        The request and response payloads are **not specified** in the
        documentation — contact hizi.io support for enabling and details. Both
        are modelled here as free-form JSON objects rather than guessed at.
      servers:
        - url: '{backendQueue}'
          variables:
            backendQueue:
              default: https://game-engine.example.com
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FreeFormObject'
      responses:
        '200':
          description: Free-format JSON structure; shape agreed with hizi.io support.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FreeFormObject'

  /doAutoClose:
    post:
      operationId: doAutoClose
      tags: [calls-to-implement]
      summary: 'Implemented by you: auto-close a gameround left open'
      description: |
        **Mandatory, also for RGS-to-RGS.** Sent to the game engine by the
        hizi.io janitor task. Most operators require open gamerounds to be
        closed after a given timespan, normally between 12 and 48 hours.

        Handle it as if the player had re-loaded the game and continued the
        round: run your close / autoplay routine and send the normal flow —
        potential winnings and an `endGameRound` request — back to the RGS.
        Any V2 call may be used to inspect the round first (e.g.
        `getGameRoundInfo`, `collectWin`).

        The request carries **no player/session token**, so obtain one with
        `POST /getTokenForGameRound` before calling back into the RGS.

        **Authentication:** the request carries `X-H-AUTH-ID` and
        `X-H-TIMESTAMP` but is **not signed** (no `X-H-AUTH-SIG`). Use
        `X-H-AUTH-ID` to attribute the call and match the referenced open
        `gameRound` on your side. Additionally restricting this endpoint to the
        RGS egress IP range is **recommended** but not required.

        The HTTP status of your response must reflect whether the round was
        closed: `200` for success, anything else for an error.
      servers:
        - url: '{backendQueue}'
          variables:
            backendQueue:
              default: https://game-engine.example.com
      security:
        - V2AuthId: []
          V2Timestamp: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DoAutoCloseRequestBody'
      responses:
        '200':
          description: The gameround was closed. Payload is a free-format JSON structure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FreeFormObject'
        default:
          description: |
            Any non-200 status means the gameround could not be closed. The
            janitor will act on the status code, not on the payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FreeFormObject'

components:
  securitySchemes:
    V2AuthId:
      type: apiKey
      in: header
      name: X-H-AUTH-ID
      description: |
        Game provider ID assigned by hizi.io (Hinterzimmer). Identifies which
        shared secret the signature must be verified against. Mandatory on
        every game engine / RGS call.
    V2Signature:
      type: apiKey
      in: header
      name: X-H-AUTH-SIG
      description: |
        HMAC signature of the request. OpenAPI cannot express a computed
        signature, so the scheme is modelled as an `apiKey` header; the value
        is **derived per request** as follows.

        You need a shared secret, which you can retrieve by logging in to the
        hizi.io backoffice with your credentials. The signature is a
        **base64-encoded SHA-256 HMAC digest** of a defined set of request
        parameters, joined with a hash character (`#`). This allows the secret
        to be shared without ever being sent in the request. A timestamp
        (`X-H-TIMESTAMP`) must also be supplied as a request header and is part
        of the signed data.

        **Outbound (you → hizi.io RGS)** — the fields signed are the
        serialised JSON body and the timestamp:

        ```
        X-H-AUTH-SIG = base64(
          HMAC_SHA256( secret, [ JSON.stringify(body), X-H-TIMESTAMP ].join('#') )
        )
        ```

        See the [Signing appendix](/game-studio-api/signing) for a complete
        reference implementation.

        **Inbound (hizi.io RGS → your `backendQueue`)** — same primitive, but
        the RGS signs a fixed, per-endpoint ordered list of **scalar fields**
        and the body is *not* hashed. The secret is used both as the HMAC key
        and as the first element of the signed string:

        ```
        X-H-AUTH-SIG = base64( HMAC_SHA256( secret, fields.join('#') ) )
        ```

        | endpoint | fields (in order) |
        | --- | --- |
        | `GET /getGameConfiguration` | `[ secret, X-H-AUTH-ID, X-H-TIMESTAMP, currency ]` |
        | `POST /getGameResult` | `[ secret, X-H-AUTH-ID, X-H-TIMESTAMP, gameRound ]` |

        A wrong signature is reported as HTTP 401 / error code 1001
        (`SIGNATUREWRONG`) and must not be retried.

        The header family is shared with the Operator API, which documents the
        same base64/SHA-256-HMAC/`#`-joined construction; the *set of signed
        fields* differs per API and per direction, so do not assume a signer
        written for one is reusable for the other without checking the field
        list.
    V2Timestamp:
      type: apiKey
      in: header
      name: X-H-TIMESTAMP
      description: |
        ISO 8601 timestamp of the request (e.g. `2023-01-26T11:28:21.429Z`).
        Mandatory, and included in the signed data of `X-H-AUTH-SIG`.

  parameters:
    XRequestIdHeader:
      name: X-Request-Id
      in: header
      required: true
      description: |
        Unique ID identifying the call in the logs. Documented as a mandatory
        request header for all game engine / RGS calls.

        Note: the reference implementation in the
        [Signing appendix](/game-studio-api/signing) additionally sets
        `X-Response-Id` (a fresh UUID) and, when relaying a request,
        `X-Forwarded-Response-Id`.
      schema:
        type: string
      example: 8c00b93dd861405cbeb7c14fded1e72a
    TokenQuery:
      name: token
      in: query
      required: true
      description: |
        Session token. For `/connect` this is the **short-life** token received
        as the `token` URL parameter of the game launch (valid 5 minutes,
        single use). For `/reconnect` and `/disconnect` it is the **long-life**
        token returned by a previous `/connect`.
      schema:
        type: string
      example: 206fa387-9565-4fc7-b6e6-b555ae064cfc

  headers:
    XResponseStatus:
      description: |
        Present and set to `ok` for a successful call. Any other value, or the
        absence of this header, indicates an error.
      schema:
        type: string
      example: ok
    XResponseId:
      description: Unique ID of the response, used to identify the call in the logs.
      schema:
        type: string
      example: 1a3b4d1d499c4f43a78cc7a138bebdd5
    XHErrorId:
      description: |
        Error code if something went wrong. **If this header is present the
        call must be treated as failed, even when the HTTP status is 200** (as
        if a 400 had been received). `0` means success.
      schema:
        $ref: '#/components/schemas/ErrorCodeId'
    XHErrorMsg:
      description: Optional textual representation of the error that caused the call to fail.
      schema:
        type: string
    XHRepeatCount:
      description: |
        Internal, informational: how often a call was put into the retry queue.
      schema:
        type: integer

  responses:
    BadRequest:
      description: |
        The request could not be processed due to an error processing the
        request payload. **Do not retry.**
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
        X-Response-Id:
          $ref: '#/components/headers/XResponseId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    Unauthorized:
      description: |
        The signature of the request was wrong and no other authorisation could
        be performed (error code 1001, `SIGNATUREWRONG`). **Do not retry.**
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    Forbidden:
      description: |
        The call was authenticated but no permission to use the endpoint was
        found. **Do not retry.**
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    NotFound:
      description: The endpoint was not found. **Do not retry.**
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    MethodNotAllowed:
      description: The method (GET/POST) is wrong or not allowed. **Do not retry.**
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    RateLimited:
      description: |
        Rate limit exceeded (error code 1300, `RATELIMITEXCEEDED`). Retry after
        waiting some time; if it happens repeatedly, contact customer support.
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    LegalBlocked:
      description: |
        Content not available for legal reasons (e.g. error code 28,
        `REGIONISBLOCKED`). **Do not retry.**
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    NotImplemented:
      description: API call not implemented. **Do not retry.**
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'
    ServerError:
      description: |
        An unexpected internal error occurred; treat it as temporary. The call
        **should be retried** unless an `X-H-DONOTRETRY` header is received.
        (HTTP 501 is the exception — API call not implemented, do not retry.)
      headers:
        X-H-ERROR-ID:
          $ref: '#/components/headers/XHErrorId'
        X-H-ERROR-MSG:
          $ref: '#/components/headers/XHErrorMsg'
        X-H-REPEATCOUNT:
          $ref: '#/components/headers/XHRepeatCount'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorReply'

  schemas:
    # -- generic helpers ------------------------------------------------------
    FreeFormObject:
      type: object
      description: A JSON object whose shape is not specified by the documentation.
      additionalProperties: true

    Amount:
      type: integer
      description: |
        A monetary amount as an integer in **minor units** (1/100 of the
        currency). E.g. `100` is 1.00 EUR.
      format: int64

    Currency:
      type: string
      description: ISO 4217 currency code.
      example: EUR

    Hash:
      type: string
      description: |
        A hizi.io hash — a 32-character hex identifier used for gamerounds and
        transactions (gamestate entries).
      example: ff85e29cb4414c89a89829c8411dacc2

    GameModes:
      type: string
      description: The mode a session or round runs in.
      enum: [DEMO, REAL, HISTORY]

    DeviceModes:
      type: string
      enum: [desktop, mobile]

    GameTransactionStatus:
      type: string
      enum: [success, error, retrying]

    # -- errors ---------------------------------------------------------------
    ErrorCodeId:
      type: integer
      description: |
        Error code as sent in the `X-H-ERROR-ID` response header or used inside
        an `errorCode` object. This is the **shared catalogue** also used by
        the Operator API — see
        [Response Codes & Error Codes](/response-codes).

        | code | name | description |
        | --- | --- | --- |
        | 0 | SUCCESS | Call was successful |
        | 1 | PLAYERNOTFOUND | Player not found |
        | 2 | PLAYERBLOCKED | Player is blocked |
        | 10 | BALANCETOOLOW | Balance not sufficient to make a debit |
        | 11 | BETLIMITREACHED | User bet limit reached, bet cannot be accepted |
        | 12 | GAMEROUNDALREADYSTARTED | An initial bet was already received |
        | 13 | GAMEROUNDNOTACTIVE | Gameround hash known but already closed |
        | 14 | GAMEROUNDNOTFOUND | Gameround hash not found |
        | 15 | GAMEROUNDNOTSTARTED | Gameround known but the initial debit was never received |
        | 16 | TRANSACTIONCOUNTWRONG | transactionCount does not match the transactions passed |
        | 17 | GAMEROUNDPLAYERMISMATCH | Gameround hash does not belong to the player |
        | 18 | CURRENCYINVALID | Currency not valid or not available for that player or game |
        | 19 | INVALIDPARAMETER | An invalid value for a parameter was received |
        | 20 | TRANSACTIONAFTEREND | Debit/credit received after an end transaction |
        | 21 | GAMEROUNDISLOCKED | Gameround locked or a credit is being retried; stranded after 24h |
        | 22 | GAMEROUNDISCLOSING | Round is closing but the operator has not accepted the end call yet |
        | 23 | SPINNOTAVAILABLE | Requested spin type (free or bonus) not available — handle like a low balance |
        | 24 | DEBITSHOULDBECANCELLED | A debit failed and should be cancelled (internal) |
        | 25 | COOLOFFPERIODACTIVE | Player is in a cool-off period |
        | 26 | TRANSACTIONSARERETRYING | New transactions received while others are pending (internal) |
        | 27 | ENDTRANSACTIONALREADYRECEIVED | End transaction already processed and being retried (internal) |
        | 28 | REGIONISBLOCKED | Request blocked for legal reasons |
        | 29 | NOTLOGGEDON | Player is not logged in at the casino |
        | 30 | RESPONSIBLEGAMINGLIMITREACHED | Bet limit reached |
        | 31 | RESPONSIBLESGAMINGSESSIONEXPIRED | Session limit reached / expired |
        | 32 | PLAYERNOAUTHENTICATED | Player is not known in the casino |
        | 33 | DISCOUNTNOTELIGIBLE | Discount submitted in doTransactions is not eligible |
        | 34 | GENERICBETERROR | Unexpected error on a debit transaction |
        | 35 | GENERICWINERROR | Unexpected error on a credit transaction |
        | 36 | OPERATIONBEINGPROCESSED | Another transaction is processing |
        | 37 | TRANSACTIONALREADYEXISTS | Transaction was already processed |
        | 38 | REALITYCHECK | Reality check triggered by the operator |
        | 1001 | SIGNATUREWRONG | Signature is incorrect |
        | 1002 | PAYLOADNOTJSON | Payload received is not valid JSON |
        | 1003 | WRONGHEADERS | Header fields missing or wrong |
        | 1004 | PARAMETERMISSING | A mandatory parameter was missing |
        | 1005 | INVALIDGAMEMODE | An invalid game mode was passed |
        | 1006 | GAMECODENOTFOUND | Game code not found or not available |
        | 1007 | GAMECONFIGNOTSUPPORTED | Requested game config is not supported |
        | 1008 | SESSIONINVALID | Session expired or invalid; request a new launch URL |
        | 1009 | RNGFAILURE | The RNG failed |
        | 1010 | NETWORKERROR | A general network error occurred |
        | 1200 | DATASTRUCTUREWRONG | Payload sent is wrong |
        | 1300 | RATELIMITEXCEEDED | A rate limit was exceeded (usually with HTTP 429) |
        | -1 | UNEXPECTED | An unexpected error that could not be specified in detail |
      enum:
        - -1
        - 0
        - 1
        - 2
        - 10
        - 11
        - 12
        - 13
        - 14
        - 15
        - 16
        - 17
        - 18
        - 19
        - 20
        - 21
        - 22
        - 23
        - 24
        - 25
        - 26
        - 27
        - 28
        - 29
        - 30
        - 31
        - 32
        - 33
        - 34
        - 35
        - 36
        - 37
        - 38
        - 1001
        - 1002
        - 1003
        - 1004
        - 1005
        - 1006
        - 1007
        - 1008
        - 1009
        - 1010
        - 1200
        - 1300

    ErrorCodeObject:
      type: object
      description: |
        An `errorCode` object as described in
        [Response Codes & Error Codes](/response-codes). Its exact
        JSON shape is not formally specified in the documentation; the
        authoritative error signal is the `X-H-ERROR-ID` response header.
      properties:
        errorCode:
          $ref: '#/components/schemas/ErrorCodeId'
        errorMsg:
          type: string
          description: Textual representation of the error.
      additionalProperties: true

    ErrorReply:
      type: object
      description: |
        Error payload. The documentation does not fix a single error body
        shape — inspect `X-H-ERROR-ID` / `X-H-ERROR-MSG` for the authoritative
        result, and note that an error reported *only* in the payload of a
        `200` response is to be ignored, while an `X-H-ERROR-ID` on a `200`
        makes the call a failure.
      properties:
        errorCode:
          $ref: '#/components/schemas/ErrorCodeObject'
      additionalProperties: true

    # -- request bodies (calls provided by API V2) ----------------------------
    V2_TestPingRequestBody:
      type: object
      description: Payload for `POST /testPing`. `testData` is echoed back unchanged.
      required: [testData]
      properties:
        testData:
          description: Arbitrary JSON value to be echoed back in `testDataReceived`.

    V2_StartGameRoundRequestBody:
      type: object
      description: Payload for `POST /startGameRound`.
      required: [token, rngResult, debitInformation, endGame]
      properties:
        token:
          type: string
          description: Long-life session token of the player.
        rngResult:
          $ref: '#/components/schemas/GameRNGResult'
        debitInformation:
          $ref: '#/components/schemas/DebitInformation'
        creditInformation:
          $ref: '#/components/schemas/CreditInformation'
        endGame:
          type: boolean
          description: |
            When `true` the gameround is closed immediately after the debit
            (and optional credit) — a single-call round.
        closeOpenGame:
          type: boolean
          description: |
            When `true` any existing open gameround of the player is closed and
            its open winnings collected before the new round is started.
        useJackpot:
          type: boolean
          description: |
            **Not currently implemented** — ignored by the engine. Part of the
            specified but not yet live jackpot feature.
        betData:
          $ref: '#/components/schemas/BetData'
        externalGameProviderRoundId:
          type: string
          maxLength: 32
          description: |
            Optional round identifier on your side. Must not exceed 32
            characters.

    V2_EndGameRoundRequestBody:
      type: object
      description: Payload for `POST /endGameRound`.
      required: [token, gameRound]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'
        externalGameProviderTransactionId:
          type: string
          description: Optional transaction identifier on your side.

    V2_GetGameRoundDetailsRequestBody:
      type: object
      description: |
        Payload for `POST /getGameRoundInfo` and
        `POST /getGameRoundHistoryInfo`.
      required: [token, gameRound]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'

    V2_AddWinRequestBody:
      type: object
      description: Payload for `POST /addWin`.
      required: [token, gameRound, rngResult]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'
        rngResult:
          $ref: '#/components/schemas/GameRNGResult'
        collect:
          type: boolean
          description: |
            When `true` the win is collected (credited) immediately and the
            gameround is closed.
        externalGameProviderTransactionId:
          type: string
        jackpotPayout:
          type: array
          description: |
            **Not currently implemented** — ignored by the engine.
          items:
            $ref: '#/components/schemas/V2_JackpotPayout'

    V2_WagerWinRoundRequestBody:
      type: object
      description: Payload for `POST /wagerWin`.
      required: [token, gameRound, rngResult, wagerInformation]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'
        rngResult:
          $ref: '#/components/schemas/GameRNGResult'
        wagerInformation:
          $ref: '#/components/schemas/WagerInformation'

    V2_CollectWinRoundRequestBody:
      type: object
      description: Payload for `POST /collectWin`.
      required: [token, gameRound, collectInformation]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'
        collectInformation:
          $ref: '#/components/schemas/CollectInformation'

    V2_GetUncollectedAmountRequestBody:
      type: object
      description: Payload for `POST /getUncollectedAmount`.
      required: [token, gameRound]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'

    V2_AddSpinCounterGameStateRequestBody:
      type: object
      description: |
        Payload for `POST /addSpinCounterGameState`.

        Note: the published type declares `gameStateResult` as non-optional,
        but the documented request example does not send it. It is therefore
        not marked required here.
      required: [token, gameRound, counterType, rngResult]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'
        counterType:
          type: string
          description: |
            Name of the counter being awarded or consumed, e.g. `customspin`.
            The same value appears as the `type` of the resulting `gameState`
            entry.
          example: customspin
        rngResult:
          $ref: '#/components/schemas/GameEventRNGResult'
        gameStateResult:
          description: Game-specific state to store with the counter event.
          oneOf:
            - type: object
              additionalProperties: true
            - type: string

    V2_GetAvailableSpinCounterRequestBody:
      type: object
      description: Payload for `POST /getAvailableSpinCounter`.
      required: [token, gameRound, counterType]
      properties:
        token:
          type: string
        gameRound:
          $ref: '#/components/schemas/Hash'
        counterType:
          type: string
          example: customspin

    V2_GetRandomResultRequestBody:
      type: object
      description: Payload for `POST /getRandomResult`.
      required: [token, rngEndPoint, rngPayload]
      properties:
        token:
          type: string
        rngEndPoint:
          type: string
          description: |
            The certified-RNG endpoint to use. The documentation shows
            `getDoubles` and states that integers can also be requested, but
            does not name the integer endpoint — it is therefore left as a free
            string rather than an invented enum. Confirm the available values
            with hizi.io support.
          example: getDoubles
        rngPayload:
          type: object
          description: Range and count of the values requested.
          required: [max]
          properties:
            count:
              type: integer
              description: How many values to return.
            min:
              type: number
              description: Lower bound of the range.
            max:
              type: number
              description: Upper bound of the range.

    V2_GetTokenForGameRoundRequestBody:
      type: object
      description: |
        Payload for `POST /getTokenForGameRound`. Takes only the gameround —
        no token, since this call exists to obtain one.
      required: [gameRound]
      properties:
        gameRound:
          $ref: '#/components/schemas/Hash'

    V2_GetTokenDataRequestBody:
      type: object
      description: Payload for `POST /getTokenData` and `POST /getBalance`.
      required: [token]
      properties:
        token:
          type: string

    V2_GetLastGamesRequestBody:
      type: object
      description: Payload for `POST /getLastGameRounds`.
      required: [token]
      properties:
        token:
          type: string
        maxRecords:
          type: integer
          default: 50
          description: Maximum number of rounds to return. Default 50.
        maxDays:
          type: integer
          default: 30
          description: How far back to look, in days. Default 30.

    V2_GetOperatorSettingsQuery:
      type: object
      description: |
        Query parameters of `GET /getOperatorSettings`. Kept as a schema for
        parity with the published `V2_GetOperatorSettingsQuery` type; the
        operation itself declares them as individual query parameters.
      required: [game, operator, currency]
      properties:
        game:
          type: string
        operator:
          type: string
        currency:
          $ref: '#/components/schemas/Currency'

    V2_VoidGameRoundRequestBody:
      type: object
      description: |
        Payload for `POST /voidGameRound`.

        Note: the published type declares `token` as optional, but the engine
        requires it at runtime — always send it. It is marked required here.
      required: [gameRound, token]
      properties:
        gameRound:
          $ref: '#/components/schemas/Hash'
        token:
          type: string

    # -- replies --------------------------------------------------------------
    V2_StatusReply:
      type: object
      description: |
        Reply of `GET /status`. Documented only as "a JSON structure containing
        a message"; additional properties are allowed.
      properties:
        message:
          type: string
      additionalProperties: true

    V2_TestPingReply:
      type: object
      description: Reply of `POST /testPing` — the request's `testData`, echoed back.
      properties:
        testDataReceived:
          description: The `testData` value that was sent.
      additionalProperties: true

    V2_GameAPIReply:
      type: object
      description: |
        The standard reply of the gameround calls.

        No property is marked required here: the published type declares
        `tokenData` as non-optional, but the documented `voidGameRound`
        example omits it (and returns `balance` as a scalar), so a strict
        `required` would contradict the documentation. Treat every field as
        best-effort present and check what the specific operation documents.

        `freePlayInfo` / `freePlaysAvailable` are present on every reply once
        freeplays are active for the player.
      properties:
        tokenData:
          $ref: '#/components/schemas/V2_GameToken'
        balance:
          $ref: '#/components/schemas/GameAPIBalanceReply'
        gameRoundInfo:
          $ref: '#/components/schemas/V2_GameRoundInfo'
        commonGameRoundInfo:
          $ref: '#/components/schemas/CD_InfoReply'
        gameState:
          type: array
          description: |
            The transactions (gamestate entries) recorded for the round, in
            processing order.
          items:
            $ref: '#/components/schemas/V2_GameAPIGameState'
        amountToCollect:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: The open (uncollected) win amount of the round.
        amountCredited:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: The amount credited to the player by this call.
        freePlayInfo:
          $ref: '#/components/schemas/V2_FreePlayInfo'
        freePlaysAvailable:
          type: array
          description: |
            Available freeplay packages, grouped by stake, when multiple
            packages were granted to the player by the operator.
          items:
            $ref: '#/components/schemas/V2_FreePlayAvailable'
        passThroughData:
          $ref: '#/components/schemas/PassThroughData'
        promoWinInfo:
          $ref: '#/components/schemas/PromoWinInfo'

    V2_GameAPIHistoryReply:
      description: |
        Reply of `POST /getGameRoundHistoryInfo`: a `V2_GameAPIReply` plus any
        `protectedInfo` stored for the round. The shape of `protectedInfo` is
        not documented and is modelled as a free-form object.
      allOf:
        - $ref: '#/components/schemas/V2_GameAPIReply'
        - type: object
          properties:
            protectedInfo:
              $ref: '#/components/schemas/FreeFormObject'

    V2_GameRoundInfo:
      type: object
      description: Summary of a gameround as returned in `gameRoundInfo`.
      properties:
        hash:
          allOf:
            - $ref: '#/components/schemas/Hash'
          description: The gameround hash.
        status:
          type: string
          description: |
            Status of the round. Values seen in the documentation: `open`,
            `closed` and `cancel` (after `voidGameRound`). The published type
            declares a plain string, so no enum is imposed here.
          example: open
        stake:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: |
            The amount actually staked, in player-currency minor units
            (already multiplied by `currencyMultiplier`).
        baseStake:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: |
            Equals `stake` for a normal round. On a buy-feature round the
            debited amount is the feature price while `baseStake` records the
            underlying stake. It is **not** `stake / currencyMultiplier` —
            both are in the same already-multiplied minor-unit scale.
        mode:
          $ref: '#/components/schemas/GameModes'
        currency:
          $ref: '#/components/schemas/Currency'
        game:
          type: string
          description: Game identifier the round belongs to.
        currencyMultiplier:
          type: number
        commonRoundHash:
          allOf:
            - $ref: '#/components/schemas/Hash'
          description: Hash of the common-draw round, for common-draw games.

    V2_FreePlayInfo:
      type: object
      description: |
        Information about the currently active freeplay package. Also published
        as `TicketWonInfo`.
      required: [used, granted, won, isLast, serial]
      properties:
        used:
          type: integer
          description: Spins of the package already used.
        granted:
          type: integer
          description: Spins originally granted in the package.
        won:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Total win from the package so far.
        isLast:
          type: boolean
          description: |
            `true` when the package is finished, i.e. all free spins have been
            used.
        promotions:
          $ref: '#/components/schemas/PromotionsPayload'
        serial:
          type: string
          description: Identifier of the freeplay package.

    V2_FreePlayAvailable:
      type: object
      description: |
        An available freeplay package, grouped by stake. Note: the
        [Freeplays](/game-studio-api/freeplays) example shows only `currency`,
        `stake` and `count`; `serial` is non-optional in the published type.
      required: [currency, stake, count, serial]
      properties:
        currency:
          $ref: '#/components/schemas/Currency'
        stake:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: The stake the free spins are valid for.
        count:
          type: integer
          description: Number of free spins available at that stake.
        feature:
          type: string
          description: Feature the free spins are bound to, if any.
        serial:
          type: string

    V2_ConnectReply:
      description: |
        Reply of `GET /connect` and `GET /reconnect`: a `V2_GameAPIReply`
        extended with the session and launch information the frontend needs.

        Mandatory for the frontend to use (per
        [Game Launch](/game-studio-api/game-launch)): `token`, `backendUrl`,
        `balance`, `gameSettings` and `tokenData`.
      allOf:
        - $ref: '#/components/schemas/V2_GameAPIReply'
        - type: object
          required: [backendUrl, refreshUrl, logoutUrl, token, gameSettings, gameVariables, operatorProtocol, tokenData]
          properties:
            backendUrl:
              type: string
              description: Location of the game engine endpoint. All further game calls go here.
            refreshUrl:
              type: string
              description: |
                URL to reconnect to a session — equivalent to the launch
                `login` URL but taking the long-life token.
            logoutUrl:
              type: string
              description: |
                URL to log out of the game. Calling it invalidates the
                long-life token; the operator then has to send a new game
                launch request.
            webSocketUrl:
              type: string
              description: |
                Optional websocket URL, usable as an alternative to POST
                requests to `backendUrl`. Request sample code from hizi.io
                support.
            token:
              type: string
              description: |
                The **long-life** token to be used for every subsequent call to
                `backendUrl` / `refreshUrl`.
            gameSettings:
              $ref: '#/components/schemas/GameSettings'
            gameVariables:
              $ref: '#/components/schemas/GameVariables'
            operatorProtocol:
              type: integer
              description: |
                The operator protocol in use — informational, to let the game
                support operator-specific requirements. Request details from
                hizi.io support.
            featuresAvailable:
              type: array
              items:
                $ref: '#/components/schemas/ExternalFeaturePackageDefinition'

    ExternalFeaturePackageDefinition:
      type: object
      description: A buyable feature package offered for the game.
      required: [game, type, rtp, value]
      properties:
        game:
          type: string
        type:
          type: string
        rtp:
          type: number
        value:
          type: number
        description:
          type: string

    V2_GameAPIGetUncollectedAmountReply:
      type: object
      description: Reply of `POST /getUncollectedAmount`.
      required: [tokenData]
      properties:
        tokenData:
          $ref: '#/components/schemas/V2_GameToken'
        amountToCollect:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: The open (uncollected) win amount of the round.

    V2_GameAPIAddSpinCounterGameStateReply:
      description: |
        Reply of `POST /addSpinCounterGameState`: a `V2_GameAPIReply` plus the
        remaining spin counter.
      allOf:
        - $ref: '#/components/schemas/V2_GameAPIReply'
        - type: object
          required: [spinsAvailable]
          properties:
            spinsAvailable:
              type: integer
              description: Spins of the given `counterType` still available.

    V2_GameAPIGetAvailableSpinCounterReply:
      description: |
        Reply of `POST /getAvailableSpinCounter`: the uncollected-amount reply
        plus the spin counter.
      allOf:
        - $ref: '#/components/schemas/V2_GameAPIGetUncollectedAmountReply'
        - type: object
          required: [spinsAvailable]
          properties:
            spinsAvailable:
              type: integer
              description: Spins of the given `counterType` still available.

    V2_GameAPIRngReply:
      type: object
      description: |
        Reply of `POST /getRandomResult`. `rngRequestId` is non-optional in the
        published type but absent from the documented example, so it is not
        marked required here.
      required: [tokenData, values]
      properties:
        tokenData:
          $ref: '#/components/schemas/V2_GameToken'
        values:
          type: array
          description: The random values produced by the certified RNG.
          items:
            type: number
        rngRequestId:
          type: string
          description: Identifier of the RNG request, for audit purposes.

    V2_GetTokenDataReply:
      type: object
      description: Reply of `POST /getTokenData`.
      required: [tokenData]
      properties:
        tokenData:
          $ref: '#/components/schemas/V2_GameToken'

    V2_GetTokenForGameRoundReply:
      description: |
        Reply of `POST /getTokenForGameRound` — identical to
        `V2_GetTokenDataReply`; the new token is in `tokenData.currentToken`.
      allOf:
        - $ref: '#/components/schemas/V2_GetTokenDataReply'

    V2_GetLastGamesReply:
      type: object
      description: Reply of `POST /getLastGameRounds`.
      required: [gameRoundList]
      properties:
        gameRoundList:
          type: array
          description: |
            Recently closed gamerounds. The published type is
            `Record<string, unknown>[]`, so no shape is guaranteed; the
            properties below are the ones the documented example returns.
          items:
            $ref: '#/components/schemas/V2_LastGameRoundEntry'

    V2_LastGameRoundEntry:
      type: object
      description: |
        One entry of `gameRoundList`. Derived from the documented example
        rather than from a published type — additional properties are expected
        and none of these are guaranteed.
      properties:
        id:
          allOf:
            - $ref: '#/components/schemas/Hash'
          description: The gameround hash.
        status:
          type: string
          example: closed
        timestamp:
          type: string
          format: date-time
        bet:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Amount staked in the round.
        won:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Amount won in the round.
        currency:
          $ref: '#/components/schemas/Currency'
        gameSpecificDetails:
          $ref: '#/components/schemas/FreeFormObject'
      additionalProperties: true

    # -- shared and nested types ---------------------------------------------
    V2_GameToken:
      type: object
      description: |
        The contents of a session token: who is playing what, in which mode and
        currency, plus any open gameround.

        Note: `walletMode` is non-optional in the published type but is absent
        from every documented example response, so it is not marked required
        here.
      required: [operatorId, playerId, gameId, mode, currency, language, currencyMultiplier]
      properties:
        operatorId:
          type: string
        playerId:
          type: string
          description: Player ID on the hizi.io side (not the operator's `externalPlayerId`).
        gameId:
          type: string
        mode:
          $ref: '#/components/schemas/GameModes'
        currency:
          $ref: '#/components/schemas/Currency'
        currentToken:
          type: string
          description: The token this data was resolved from.
        gameRound:
          allOf:
            - $ref: '#/components/schemas/Hash'
          description: |
            Any **open** gameround hash of the player — this is what lets a
            game resume an unfinished round after `/connect`.
        walletMode:
          type: string
        language:
          type: string
          example: DE
        currencyMultiplier:
          type: number
          description: |
            FX-style factor for the player's currency, to be applied once when
            building the stake ladder. Usually 1.
          example: 1
        device:
          $ref: '#/components/schemas/DeviceModes'
        commonDraw:
          type: string
          description: Common-draw identifier, for common-draw games.

    GameAPIBalanceReply:
      type: object
      description: |
        The player's balance. Returned as `balance` inside `V2_GameAPIReply`
        and as the whole body of `POST /getBalance`.
      required: [totalBalance, mode, currency, balances]
      properties:
        totalBalance:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Total balance across all `balances` entries.
        mode:
          $ref: '#/components/schemas/GameModes'
        currency:
          $ref: '#/components/schemas/Currency'
        balances:
          type: array
          items:
            $ref: '#/components/schemas/BalanceEntry'
        discountInfo:
          $ref: '#/components/schemas/DiscountInfo'
        tickets:
          type: array
          description: Available freeplay/ticket packages.
          items:
            $ref: '#/components/schemas/TicketEntry'
        ticketInfo:
          $ref: '#/components/schemas/TicketWonInfo'

    BalanceEntry:
      type: object
      description: One component of the player's balance, e.g. `real` or a bonus wallet.
      required: [type, currency, amount]
      properties:
        type:
          type: string
          example: real
        currency:
          $ref: '#/components/schemas/Currency'
        amount:
          $ref: '#/components/schemas/Amount'

    DiscountEntry:
      type: object
      description: A stake-specific discount granted to the player.
      required: [stake, absoluteDiscountAmount]
      properties:
        stake:
          $ref: '#/components/schemas/Amount'
        absoluteDiscountAmount:
          $ref: '#/components/schemas/Amount'
        spinsRemaining:
          type: integer

    DiscountInfo:
      type: object
      required: [text, currency, discounts]
      properties:
        text:
          type: string
        currency:
          $ref: '#/components/schemas/Currency'
        discounts:
          type: array
          items:
            $ref: '#/components/schemas/DiscountEntry'
        campaignId:
          type: string
        validFrom:
          type: string
        validTo:
          type: string

    TicketEntry:
      type: object
      description: |
        An available ticket (freeplay) package. Structurally the same as
        `freePlaysAvailable` entries, reported inside the balance.
      required: [count, stake, currency, serial]
      properties:
        count:
          type: integer
        stake:
          $ref: '#/components/schemas/Amount'
        currency:
          $ref: '#/components/schemas/Currency'
        price:
          $ref: '#/components/schemas/Amount'
        feature:
          type: string
        serial:
          type: string

    TicketWonInfo:
      description: |
        Information about the active ticket (freeplay) package — the same shape
        as `freePlayInfo`.
      allOf:
        - $ref: '#/components/schemas/V2_FreePlayInfo'

    PromotionsPayload:
      type: object
      description: Versioned, operator-specific promotions payload.
      required: [protocolVersion, data]
      properties:
        protocolVersion:
          type: integer
        data:
          description: Free-form promotions data; shape depends on `protocolVersion`.

    PassThroughData:
      description: |
        Operator-specific pass-through payload. Referenced by the published
        `V2_GameAPIReply` type but not defined in the data-structures
        documentation, so it is modelled as a free-form JSON value.
      allOf:
        - $ref: '#/components/schemas/FreeFormObject'

    PromoWinInfo:
      type: object
      required: [referenceId]
      properties:
        referenceId:
          type: string

    CD_InfoReply:
      description: |
        Common-draw round information, present only for common-draw games.
        Referenced by the published `V2_GameAPIReply` type but not defined in
        the data-structures documentation, so it is modelled as a free-form
        JSON object.
      allOf:
        - $ref: '#/components/schemas/FreeFormObject'

    BetData:
      type: object
      description: |
        Game-specific bet data (`Record<string, unknown>`), passed through by
        the RGS.
      additionalProperties: true

    GameRNGResult:
      type: object
      description: |
        The RNG outcome that produced a game result. For a classic RGS-to-RGS
        integration this is the output of **your own** certified RNG; for a
        hizi.io-hosted engine it is the result of `POST /getRandomResult`.
      required: [rngCallId, result]
      properties:
        rngCallId:
          description: |
            Identifier(s) of the RNG call(s) that produced this result — a
            single ID or an array of IDs.
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        result:
          description: |
            The game-specific result. Free-form (`unknown` in the published
            type); the documented examples use a plain string.
        winAmount:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Win produced by this result, in minor units.
        betData:
          $ref: '#/components/schemas/BetData'

    GameEventRNGResult:
      type: object
      description: |
        RNG result for a spin-counter event — like `GameRNGResult` but with a
        counter-shaped `result`.
      required: [rngCallId, result]
      properties:
        rngCallId:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        result:
          $ref: '#/components/schemas/GameEventRNGResultCounter'
        winAmount:
          $ref: '#/components/schemas/Amount'

    GameEventRNGResultCounter:
      type: object
      description: |
        The counter delta of a spin-counter event: `awarded` when spins are
        won, `used` when a spin is consumed.
      properties:
        awarded:
          type: integer
        used:
          type: integer

    GameTransaction:
      type: object
      description: |
        Fields shared by all transaction payloads (`DebitInformation`,
        `CreditInformation`, `CollectInformation`, `WagerInformation`).
      required: [currency]
      properties:
        currency:
          $ref: '#/components/schemas/Currency'
        status:
          $ref: '#/components/schemas/GameTransactionStatus'
        externalGameProviderTransactionId:
          type: string
          description: Transaction identifier on your side.

    UseTicketDebitInformation:
      type: object
      description: |
        Instructs the RGS to consume an available freeplay (ticket) for this
        bet instead of debiting the player's balance. Freeplays are supported
        in **real-money mode only**, and an error is returned when no free
        spin is available for the stake (`SPINNOTAVAILABLE`, code 23).
      required: [useTicket]
      properties:
        useTicket:
          type: boolean
        featureType:
          type: string
          description: Restrict the consumed ticket to a specific feature type.

    BuyFeatureDebitInformation:
      type: object
      description: |
        Buy-feature information. On such a round the debited amount is the
        feature price while `gameRoundInfo.baseStake` records the underlying
        stake.
      required: [featureToBuy, featurePrice, featureResultSetSelected]
      properties:
        featureToBuy:
          type: string
        featurePrice:
          $ref: '#/components/schemas/Amount'
        featureResultSetSelected:
          type: integer

    DebitInformation:
      description: |
        The bet to be debited. `debitAmount` is already in player-currency
        minor units (the already-multiplied stake, or the feature price on a
        buy-feature round) — do **not** apply `currencyMultiplier` again.
      allOf:
        - $ref: '#/components/schemas/GameTransaction'
        - type: object
          required: [debitAmount]
          properties:
            debitAmount:
              $ref: '#/components/schemas/Amount'
            useTicketInfo:
              $ref: '#/components/schemas/UseTicketDebitInformation'
            buyFeatureInfo:
              $ref: '#/components/schemas/BuyFeatureDebitInformation'
            jackpotContribution:
              type: array
              description: |
                **Not currently implemented** — ignored by the engine. See the
                jackpot data types.
              items:
                $ref: '#/components/schemas/V2_JackpotContribution'

    CreditInformation:
      description: A win to be credited to the player.
      allOf:
        - $ref: '#/components/schemas/GameTransaction'
        - type: object
          required: [creditAmount]
          properties:
            creditAmount:
              $ref: '#/components/schemas/Amount'
            jackpotPayout:
              type: array
              description: '**Not currently implemented** — ignored by the engine.'
              items:
                $ref: '#/components/schemas/V2_JackpotPayout'

    CollectInformation:
      description: |
        How much of the open win to collect. `creditAmount` is optional —
        omitting it collects the full open amount.
      allOf:
        - $ref: '#/components/schemas/GameTransaction'
        - type: object
          properties:
            creditAmount:
              $ref: '#/components/schemas/Amount'
            jackpotPayout:
              type: array
              description: '**Not currently implemented** — ignored by the engine.'
              items:
                $ref: '#/components/schemas/V2_JackpotPayout'

    WagerInformation:
      description: |
        How much of the open win to wager. `winAmount` may carry the resulting
        win; the documented examples pass the outcome in
        `rngResult.winAmount`.
      allOf:
        - $ref: '#/components/schemas/GameTransaction'
        - type: object
          required: [wagerAmount]
          properties:
            wagerAmount:
              $ref: '#/components/schemas/Amount'
            winAmount:
              $ref: '#/components/schemas/Amount'
            jackpotPayout:
              type: array
              description: '**Not currently implemented** — ignored by the engine.'
              items:
                $ref: '#/components/schemas/V2_JackpotPayout'

    V2_GameAPIGameState:
      type: object
      description: |
        One transaction (gamestate entry) of a gameround. The `type` values
        seen in the documentation are `debit`, `credit`, `wager`, `genericwin`
        and the game-defined counter types (e.g. `customspin`). The published
        type declares a plain string, so no enum is imposed.

        These hashes are what the calls are idempotent on — while the round is
        open.
      required: [hash, type]
      properties:
        hash:
          allOf:
            - $ref: '#/components/schemas/Hash'
          description: |
            Transaction hash. For the initial debit this equals the gameround
            hash.
        type:
          type: string
          example: debit
        processedOn:
          type: string
          format: date-time
        result:
          type: object
          description: |
            The result recorded for this transaction. Note: `info` is
            non-optional in the published type but absent from every documented
            example (which show `{}` or `{ "winAmount": n }`), so it is not
            marked required here.
          properties:
            winAmount:
              $ref: '#/components/schemas/Amount'
            info:
              $ref: '#/components/schemas/FreeFormObject'
        amountWagered:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Amount wagered, on a `wager` entry.
        collected:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Amount collected, on a `credit` entry.
        reason:
          type: string
          example: collect 0.37 EUR

    GameSettings:
      type: object
      description: |
        Operator/game settings, published as `GameSettings` (referred to as
        `V2_GameSettings` in the documentation). Returned inside
        `V2_ConnectReply` and as the body of `GET /getOperatorSettings`.

        **No fields are marked required.** The published TypeScript interface
        declares every field as non-optional, but the documented
        `/getOperatorSettings` example returns only a subset, so treat every
        field as best-effort present and fall back to your own defaults.
      properties:
        autoplayEnabled:
          type: boolean
        autoplayLossLimitRequired:
          type: boolean
        displayCoins:
          type: boolean
        displayJackpotOdds:
          type: boolean
        displayRTP:
          type: boolean
        displayXRTP:
          type: boolean
        forceOrientation:
          $ref: '#/components/schemas/ForcedOrientation'
        gambleEnabled:
          type: boolean
        historyURL:
          type: string
        homeURL:
          type: string
        loadMsg:
          type: string
        maxExposure:
          $ref: '#/components/schemas/Amount'
        maxStake:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Scaled by `currencyMultiplier`, like the stake ladder.
        maxPackageStake:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Scaled by `currencyMultiplier`, like the stake ladder.
        minStake:
          allOf:
            - $ref: '#/components/schemas/Amount'
          description: Scaled by `currencyMultiplier`, like the stake ladder.
        minLoadTime:
          type: number
        minSpinTime:
          type: number
        multipleInstancesAllowed:
          type: boolean
        rcDisplayWinLoss:
          type: boolean
        rcEnabled:
          type: boolean
          description: Whether the reality check is enabled.
        rcInterval:
          type: number
        stopEnabled:
          type: boolean
        topupURL:
          type: string
        turboEnabled:
          type: boolean
        homeEnabled:
          type: boolean
        dynamicMinSpinTime:
          type: boolean
        redirectTarget:
          type: string
          enum: [self, parent, top]
        packageBuyEnabled:
          type: boolean
        defaultStakeIndex:
          type: integer
        operatorHandlesErrors:
          type: boolean
        displayClock:
          type: boolean
        isSocial:
          type: boolean
        displayCurrency:
          type: boolean
        customStakes:
          type: array
          items:
            $ref: '#/components/schemas/Amount'
        lossLimitURL:
          type: string
        partialCollectEnabled:
          type: boolean
        hideCompanyLogo:
          type: boolean
        abbreviateAmounts:
          type: boolean
        showExactRTP:
          type: boolean
        jurisdiction:
          type: string
        forceDefaultStake:
          type: boolean
        disableFullScreenMobile:
          type: boolean
        displayPaytableOnEnterGame:
          type: boolean
        doNotStoreSettings:
          type: array
          items:
            type: string
        sessionTimeoutInSeconds:
          type: number
        displayNetPosition:
          type: boolean
        hideDemoBalance:
          type: boolean
        preventRedirect:
          type: boolean
        refreshDisabled:
          type: boolean
        displayWinOdds:
          type: array
          items:
            $ref: '#/components/schemas/DisplayWinOdds'
        displaySessionTimer:
          description: Boolean, or a string variant of the session-timer setting.
          oneOf:
            - type: boolean
            - type: string
        skipWinsEqualToOrLessThanStake:
          type: boolean
        reportAnimationEnd:
          type: boolean
        launcherType:
          type: string
          enum: [reelLink, online]
        autoplayShowTotalStake:
          type: boolean
        translateErrors:
          type: boolean
        currencyToDisplay:
          type: string

    ForcedOrientation:
      type: integer
      description: |
        Forced screen orientation: `0` none, `1` landscape, `2` portrait.
      enum: [0, 1, 2]

    DisplayWinOdds:
      type: string
      enum: [maxPayout, maxWin]

    GameVariables:
      type: object
      description: |
        Session variables, published as `GameVariables`. Sent as a
        `Partial<GameVariables>` in `V2_ConnectReply`, so all fields are
        optional.
      properties:
        rcElapsed:
          type: number
          description: Elapsed time towards the next reality check.

    # -- jackpot data types (specified, not yet live) -------------------------
    JackpotMode:
      type: string
      description: |
        **Not currently implemented.** Whether the jackpot is handled by the
        engine or by an external vendor.
      enum: [engine, vendor]

    V2_JackpotContribution:
      description: |
        **Not currently implemented** — specified but not yet live; the engine
        ignores this field.

        A contribution to a jackpot. In `vendor` mode
        `jackpotContributionAmount` uses a **6-decimal** scale, i.e.
        `1` = 0.000001 and `1000000` = 1 (`JackpotContributionScale`) — this is
        *not* the minor-unit scale used everywhere else in this API.
      oneOf:
        - type: object
          required: [jackpotId, jackpotMode]
          properties:
            jackpotId:
              type: string
            jackpotMode:
              type: string
              enum: [engine]
        - type: object
          required: [jackpotId, jackpotContributionAmount, jackpotMode]
          properties:
            jackpotId:
              type: string
            jackpotContributionAmount:
              type: integer
              description: Contribution with 6 decimal digits (see `JackpotContributionScale`).
            jackpotMode:
              type: string
              enum: [vendor]

    V2_JackpotPayout:
      description: |
        **Not currently implemented** — specified but not yet live; the engine
        ignores this field.

        A jackpot payout. Only the `vendor` variant carries an amount.
      oneOf:
        - type: object
          required: [jackpotId, jackpotMode]
          properties:
            jackpotId:
              type: string
            jackpotMode:
              type: string
              enum: [engine]
        - type: object
          required: [jackpotId, jackpotPayoutAmount, jackpotMode]
          properties:
            jackpotId:
              type: string
            jackpotPayoutAmount:
              type: integer
            jackpotMode:
              type: string
              enum: [vendor]

    # -- calls to be implemented by games ------------------------------------
    CertifiedFilesReply:
      type: object
      description: Reply of `GET ${backendQueue}/certifiedfiles`.
      required: [list]
      properties:
        list:
          type: array
          items:
            $ref: '#/components/schemas/CertifiedFileEntry'

    CertifiedFileEntry:
      type: object
      description: One certified component and its hash.
      required: [file, hash]
      properties:
        file:
          type: string
          example: ./dist/certified/certifiedLogic.js
        hash:
          type: string
          example: e6a01ef0f88b1cac2696ce30166fca0bf6b6c793

    GameConfigurationReply:
      type: object
      description: Reply of `GET ${backendQueue}/getGameConfiguration`.
      required: [config, gameCode]
      properties:
        config:
          $ref: '#/components/schemas/GameConfiguration'
        gameCode:
          type: string
          example: game-bells

    GameConfiguration:
      type: object
      description: |
        The game configuration for a currency. Mandatory fields confirmed
        against the original (pre-migration) source, which highlighted them
        in red: `stakes`, `currency`, `currencyMultiplier`, `expectedRtp` and
        `maxPayoutOdds`. Everything else in the documented example
        (`oneHundredXOdds`, `maxWinOdds`, `maxPayout`, `maxExposure`,
        `calculatedMaxExposure`, `operator`, `winlineCount`) is optional, and
        the call itself can be mandatory for some operators / when freeplays
        are supported.
      required: [stakes, currency, currencyMultiplier, expectedRtp, maxPayoutOdds]
      properties:
        stakes:
          type: array
          description: |
            The allowed stake ladder, in player-currency minor units — i.e.
            `baseStakes` already multiplied by `currencyMultiplier`.
          items:
            $ref: '#/components/schemas/Amount'
        currency:
          $ref: '#/components/schemas/Currency'
        currencyMultiplier:
          type: number
          description: The multiplier that was applied to build `stakes`.
        expectedRtp:
          type: number
          description: Expected RTP of the game in percent.
          example: 96.19
        oneHundredXOdds:
          type: number
          description: Odds of a 100× win.
        maxWinOdds:
          type: number
        maxPayout:
          type: number
        maxPayoutOdds:
          type: number
        maxExposure:
          $ref: '#/components/schemas/Amount'
        calculatedMaxExposure:
          $ref: '#/components/schemas/Amount'
        operator:
          type: string
        winlineCount:
          type: integer
      additionalProperties: true

    HealthReply:
      description: |
        Reply of `GET ${backendQueue}/health` — a JSON structure with
        information about game health. The format is explicitly free.
      allOf:
        - $ref: '#/components/schemas/FreeFormObject'

    GetGameResultRequestBody:
      type: object
      description: |
        Payload of `POST ${backendQueue}/getGameResult`. Only `gameRound` is
        part of the signed string.
      properties:
        externalPlayerId:
          type: string
          description: Player ID on the operator side.
        operatorName:
          type: string
          description: Friendly name of the operator.
        gameRound:
          allOf:
            - $ref: '#/components/schemas/Hash'
          description: Hash of the gameround to render.
        gameRoundDetails:
          allOf:
            - $ref: '#/components/schemas/FreeFormObject'
          description: |
            All information stored for the round in the hizi.io database. The
            documentation does not specify its structure.
        short:
          type: boolean
          description: |
            When unset or `false`, return a representation that can be
            displayed to the player (used by some operators for in-game
            history).

    GetGameResultReply:
      type: object
      description: |
        Reply of `POST ${backendQueue}/getGameResult`: a JSON object with the
        key `msg` holding an HTML representation of the game result.
      required: [msg]
      properties:
        msg:
          type: string
          description: HTML representation of the game result.

    DoAutoCloseRequestBody:
      type: object
      description: |
        Payload of `POST ${backendQueue}/doAutoClose`, sent by the hizi.io
        janitor. It carries **no player token** — obtain one with
        `POST /getTokenForGameRound`.
      properties:
        gameRound:
          allOf:
            - $ref: '#/components/schemas/Hash'
          description: Hash of the gameround to be closed.
        gameSettings:
          $ref: '#/components/schemas/GameSettings'
