openapi: 3.1.0
info:
  title: Plexus Arena Platform API
  version: "1"
  description: |
    Inbound API that operators call to manage player game sessions: initialize
    a session and receive a platform token, or terminate an active session remotely.

    ## Authentication

    All requests must include an `X-Api-Key` header with the operator's API key.

    Optionally, requests may be HMAC-signed. When signing mode is `required`, the
    following headers must also be present:

    | Header | Description |
    |--------|-------------|
    | `X-Client-ID` | Signing client identifier |
    | `X-Client-TS` | Unix timestamp (seconds, UTC) |
    | `X-Client-Signature` | HMAC-SHA256 signature (hex) |

    Signature input: `timestamp + canonical_request_uri + raw_request_body_bytes`.

    Rules:

    - `canonical_request_uri` is path plus sorted, URL-encoded query string.
    - For requests with no body, body bytes are empty.
    - For JSON requests, sign the exact raw body bytes sent on the wire. Do not parse and re-encode JSON before verification.
    - Validate timestamp freshness against the configured replay window.

    Example:

    | Field | Value |
    |-------|-------|
    | X-Client-ID | `test-client` |
    | Secret | `test-secret` |
    | Timestamp | `1700000000` |
    | Canonical URI | `/api/v1/game-sessions/launch` |
    | Raw body | `{"operator":"acme","game":"danadana","external_id":"player-123","name":"JohnDoe","launch_token":"abc"}` |
    | Signature | `4bb29003ac7d79700bf966e77c61f91f49c6e2307229475a32ab1782cbeaa757` |

    `X-Client-ID` selects the shared HMAC secret; it is not included in the signature input.

servers:
  - url: "https://{platform_host}/api/v1"
    variables:
      platform_host:
        default: arena.example.com
        description: Platform hostname assigned to your operator account

components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: Operator API key. Required on all requests.

  schemas:
    LaunchResponse:
      type: object
      required: [token, launch_token]
      properties:
        token:
          type: string
          description: Platform session token. Pass this to the game iframe via URL or postMessage.
        launch_token:
          type: string
          description: Echo of the operator launch_token provided in the request.

    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Machine-readable error code or validation message.
          examples:
            - game_not_configured
            - operator_not_found
            - missing_launch_token
            - missing_operator
            - invalid_launch_payload
            - invalid_end_session_payload

    EndSessionRequest:
      type: object
      required: [operator, session_token]
      properties:
        operator:
          type: string
          description: Operator identifier (same value used in `/launch`).
        session_token:
          type: string
          description: Platform session token returned by `/launch`. The token is invalidated by this call.
        launch_token:
          type: string
          description: >
            Original operator-issued token provided at launch time. If supplied, this token
            is also invalidated. Omit if the operator does not track its own tokens.

    EndSessionResponse:
      type: object
      required: [ok]
      properties:
        ok:
          type: boolean
          enum: [true]
          description: Always true. The call is idempotent — a second call on an already-expired token also returns ok.

security:
  - ApiKey: []

paths:
  /game-sessions/launch:
    post:
      operationId: launchGameSession
      summary: Initialize a player game session
      description: |
        Validates the operator + game configuration, upserts the player record,
        and returns a platform session token that the operator embeds into the
        game iframe URL or passes via postMessage.

        One of `launch_token` or `session_token` (alias) must be provided.
        If request signing is enabled for your operator account, include the
        HMAC signing headers described in the Authentication section.

      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operator, game, external_id, name]
              properties:
                operator:
                  type: string
                  description: Operator name slug (provided by Plexus Arena).
                  example: acme
                game:
                  type: string
                  description: Game name slug (as configured in the admin portal).
                  example: danadana
                external_id:
                  type: string
                  description: Player's unique identifier in the operator's system.
                  example: player-123
                name:
                  type: string
                  description: Player display name shown in-game.
                  example: JohnDoe
                mode:
                  type: string
                  enum: [demo, real]
                  default: demo
                  description: |
                    Game mode. `real` requires the operator to have a real-money
                    wallet integration configured. Unrecognized values default to `demo`.
                launch_token:
                  type: string
                  description: |
                    Operator-issued session or launch token. The platform forwards
                    this to your `/auth` endpoint to verify the session and
                    bootstrap the player wallet. Also accepted as `session_token`.
                  example: eyJhbGciOiJIUzI1NiJ9...
                session_token:
                  type: string
                  description: Alias for `launch_token`. Provide one or the other.
                  example: eyJhbGciOiJIUzI1NiJ9...

            examples:
              real_money:
                summary: Real-money session launch
                value:
                  operator: acme
                  game: danadana
                  external_id: player-123
                  name: JohnDoe
                  mode: real
                  launch_token: eyJhbGciOiJIUzI1NiJ9...
              demo:
                summary: Demo session launch
                value:
                  operator: acme
                  game: danadana
                  external_id: guest-456
                  name: Guest456
                  mode: demo
                  launch_token: demo-token-xyz

      responses:
        "200":
          description: Session initialized. Embed the returned `token` in the game iframe URL.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LaunchResponse"
              example:
                token: pat_abc123def456
                launch_token: eyJhbGciOiJIUzI1NiJ9...

        "404":
          description: Operator or game not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                game_not_configured:
                  value:
                    error: game_not_configured
                operator_not_found:
                  value:
                    error: operator_not_found

        "400":
          description: Invalid request payload or missing required operator field.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_operator:
                  value:
                    error: missing_operator
                invalid_launch_payload:
                  value:
                    error: invalid_launch_payload

        "422":
          description: Missing or invalid parameters.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_launch_token:
                  value:
                    error: missing_launch_token

  /game-sessions/end:
    post:
      operationId: endGameSession
      summary: Terminate a player game session
      description: |
        Invalidates the platform session token (and optionally the operator launch token)
        so neither can be reused. If the player's LiveView is currently connected, it will
        receive a session-end event: any active bet is cashed out unconditionally at the
        current multiplier before the session redirects to the expired page.

        Always returns 200 — the call is idempotent and safe to retry if the session is
        already expired.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EndSessionRequest"
            examples:
              basic:
                summary: End session by platform token
                value:
                  operator: acme
                  session_token: eyJhbGciOiJIUzI1NiJ9...
              with_launch_token:
                summary: End session and invalidate operator token too
                value:
                  operator: acme
                  session_token: eyJhbGciOiJIUzI1NiJ9...
                  launch_token: operator-issued-token
      responses:
        "200":
          description: Session terminated (or was already expired).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EndSessionResponse"
              examples:
                ok:
                  value:
                    ok: true
        "400":
          description: Invalid request payload or missing required operator field.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_operator:
                  value:
                    error: missing_operator
                invalid_end_session_payload:
                  value:
                    error: invalid_end_session_payload
        "401":
          description: Missing or invalid API key.
