openapi: 3.0.3
info:
  title: AmGaming SDK API
  version: "0.3.0"
  description: |
    **Public SDK API for Agent Brand integration.**

    Brand developers use this API (directly, or via the official SDK library) to:

    - Pull brand configuration (rates, AMB credentials, allowed domains)
    - Submit game usage events back to Manager
    - Discover & download published SDK releases

    ---

    ## Authentication

    Sensitive endpoints (`/v1/sdk/config`, `/v1/usage/*`) require **HMAC-SHA256
    request signing**. The SDK library handles this for you. If you call the API
    directly, every request MUST carry:

    | Header | Value |
    |---|---|
    | `X-Brand-Code` | Brand code (e.g. `amg88`) |
    | `X-Timestamp` | Unix epoch seconds (±300s window) |
    | `X-Signature` | `hex(HMAC_SHA256(api_key, "<ts>\n<METHOD>\n<path>\n<raw-body>"))` |
    | `Authorization` | `Bearer <api_key>` |

    Public endpoints (`/v1/sdk/releases*`, `/v1/sdk/download/*`) are **unauthenticated**
    and used by the SDK Downloads page and CI/CD.

    ## Version compatibility

    The SDK declares its build via the `X-SDK-Version: <platform>/<semver>` header
    on every authenticated request. The Manager replies with:

    - `X-SDK-Outdated: true|false`
    - `X-SDK-Latest: <platform>/<semver>` — recommended upgrade target

    Blocked versions receive `426 Upgrade Required` with `upgrade_to` in the body.

    ## Rate limits

    - `GET /v1/sdk/config` — **60 req/min per brand** (Redis token bucket)
    - Other authenticated endpoints — pool budget per brand
    - Headers returned: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`

servers:
  - url: https://api-manager.amgaming.pro
    description: Production
  - url: http://127.0.0.1:8080
    description: Local dev

tags:
  - name: SDK Config
    description: Pull per-brand runtime configuration
  - name: Usage Ingestion
    description: Submit game events back to Manager
  - name: Releases
    description: Public release discovery & download

paths:
  /v1/sdk/config:
    get:
      tags: [SDK Config]
      summary: Pull brand configuration
      description: |
        Returns brand info, active AMB credentials, enabled game rates, allowed
        SDK domains, and reseller block (nullable).

        Supports **ETag / If-None-Match** for bandwidth efficiency — the SDK
        library uses this automatically.

        **Rate limit:** 60 req/min per brand.

        **On error:** SDK should keep using its last cached config (stale fallback).
      security:
        - HMACAuth: []
      parameters:
        - $ref: '#/components/parameters/BrandCode'
        - $ref: '#/components/parameters/Timestamp'
        - $ref: '#/components/parameters/Signature'
        - $ref: '#/components/parameters/SDKVersion'
        - in: header
          name: If-None-Match
          schema: { type: string }
          description: ETag from previous response — server returns 304 if unchanged
      responses:
        '200':
          description: Config payload
          headers:
            ETag:
              schema: { type: string }
              description: Opaque cache validator
            Cache-Control:
              schema: { type: string }
              example: "private, max-age=300"
            X-RateLimit-Remaining:
              schema: { type: integer }
            X-SDK-Outdated:
              schema: { type: string, enum: ["true", "false"] }
            X-SDK-Latest:
              schema: { type: string }
              example: "go/0.3.0"
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SDKConfig' }
        '304':
          description: Not modified — keep using cached config
        '401':
          description: Invalid or missing HMAC signature
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '403':
          description: Brand is suspended
        '404':
          description: Brand not found
        '426':
          description: SDK version is blocked — upgrade required
          content:
            application/json:
              schema: { $ref: '#/components/schemas/UpgradeRequired' }
        '429':
          description: Rate limit exceeded (60 req/min)
          headers:
            Retry-After:
              schema: { type: integer }
              description: Seconds until window resets
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '503':
          description: |
            Backend unavailable (DB error or AMB credentials missing). SDK should
            continue serving from its last cache.
          headers:
            Retry-After:
              schema: { type: integer }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /v1/usage/batch:
    post:
      tags: [Usage Ingestion]
      summary: Submit a batch of game events
      description: |
        Idempotent via `idempotency_key` (batch-level + per-event).

        Use for normal flush cycles — accepts 1..1000 events per call.
      security:
        - HMACAuth: []
      parameters:
        - $ref: '#/components/parameters/BrandCode'
        - $ref: '#/components/parameters/Timestamp'
        - $ref: '#/components/parameters/Signature'
        - $ref: '#/components/parameters/SDKVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EventBatch' }
      responses:
        '201':
          description: Batch accepted (new)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IngestResult' }
        '200':
          description: Batch already seen (idempotent replay) — returns prior result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IngestResult' }
        '400':
          description: Invalid payload (missing keys, bad event types, etc.)
        '401':
          description: Invalid HMAC signature

  /v1/usage/snapshot:
    post:
      tags: [Usage Ingestion]
      summary: Submit a snapshot batch (full state)
      description: |
        Same shape as `/v1/usage/batch` — kind is logged as `snapshot` for
        operator visibility. Use for periodic reconciliation or first sync.
      security:
        - HMACAuth: []
      parameters:
        - $ref: '#/components/parameters/BrandCode'
        - $ref: '#/components/parameters/Timestamp'
        - $ref: '#/components/parameters/Signature'
        - $ref: '#/components/parameters/SDKVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EventBatch' }
      responses:
        '201':
          description: Snapshot accepted
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IngestResult' }
        '200':
          description: Already seen
        '400':
          description: Invalid payload
        '401':
          description: Invalid HMAC

  /v1/usage/event/critical:
    post:
      tags: [Usage Ingestion]
      summary: Submit a single critical event (out-of-band)
      description: |
        For events that must not be delayed by batch flush windows (e.g. high-value
        adjustments). Accepts a batch payload like the other endpoints but kind is
        logged as `critical`.
      security:
        - HMACAuth: []
      parameters:
        - $ref: '#/components/parameters/BrandCode'
        - $ref: '#/components/parameters/Timestamp'
        - $ref: '#/components/parameters/Signature'
        - $ref: '#/components/parameters/SDKVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EventBatch' }
      responses:
        '201':
          description: Critical event accepted
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IngestResult' }
        '200':
          description: Already seen
        '401':
          description: Invalid HMAC

  /v1/sdk/releases:
    get:
      tags: [Releases]
      summary: List published SDK releases (public)
      description: |
        No authentication. Used by the SDK Downloads page and tooling/CI.

        Filter by platform via `?platform=go|js|python|other`.
      parameters:
        - in: query
          name: platform
          required: false
          schema:
            type: string
            enum: [go, js, python, other]
          description: Filter to a single platform
      responses:
        '200':
          description: List of releases + recommended version per platform
          content:
            application/json:
              schema:
                type: object
                properties:
                  releases:
                    type: array
                    items: { $ref: '#/components/schemas/Release' }
                  latest:
                    type: object
                    additionalProperties: { type: string }
                    description: Map of `platform` → recommended `version`
                    example: { go: "0.3.0", js: "0.2.1" }

  /v1/sdk/releases/{platform}/{version}:
    get:
      tags: [Releases]
      summary: Get a single SDK release (public)
      parameters:
        - $ref: '#/components/parameters/Platform'
        - $ref: '#/components/parameters/Version'
      responses:
        '200':
          description: Release detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  release: { $ref: '#/components/schemas/Release' }
        '404':
          description: Not found or not published

  /v1/sdk/download/{platform}/{version}:
    get:
      tags: [Releases]
      summary: Download SDK artifact (public)
      description: |
        Streams the published archive with
        `Content-Disposition: attachment; filename="..."`.
        Verify the SHA-256 (from `/v1/sdk/releases`) before installing.
      parameters:
        - $ref: '#/components/parameters/Platform'
        - $ref: '#/components/parameters/Version'
      responses:
        '200':
          description: Archive bytes (tar.gz or zip)
          headers:
            Content-Disposition:
              schema: { type: string }
              example: 'attachment; filename="amgaming-sdk-go-0.3.0.tar.gz"'
            Content-Type:
              schema: { type: string }
              example: application/gzip
          content:
            application/gzip:
              schema: { type: string, format: binary }
            application/zip:
              schema: { type: string, format: binary }
        '400':
          description: Invalid path
        '404':
          description: Version not found / not published / no artifact uploaded

components:
  securitySchemes:
    HMACAuth:
      type: http
      scheme: bearer
      bearerFormat: api_key
      description: |
        Two-part scheme:

        1. Send your `api_key` in `Authorization: Bearer <api_key>`.
        2. Sign every request with HMAC-SHA256 and put the hex digest in
           `X-Signature` (alongside `X-Brand-Code` and `X-Timestamp`).

        Signing payload: `"<X-Timestamp>\n<METHOD>\n<path>\n<raw-body>"`.

        Replay window: 300 seconds. Body limit: 4 MiB.

  parameters:
    BrandCode:
      in: header
      name: X-Brand-Code
      required: true
      schema: { type: string }
      description: Brand code, e.g. `amg88`
      example: amg88
    Timestamp:
      in: header
      name: X-Timestamp
      required: true
      schema: { type: integer, format: int64 }
      description: Unix epoch seconds (±300s replay window)
      example: 1717286400
    Signature:
      in: header
      name: X-Signature
      required: true
      schema: { type: string }
      description: Hex HMAC-SHA256 over `ts\nMETHOD\npath\nbody`
      example: 5fa1c0...
    SDKVersion:
      in: header
      name: X-SDK-Version
      required: false
      schema: { type: string }
      description: |
        Declared SDK build, format `<platform>/<semver>`.
        Used by Manager to enforce the deprecation policy.
      example: go/0.3.0
    Platform:
      in: path
      name: platform
      required: true
      schema:
        type: string
        enum: [go, js, python, other]
    Version:
      in: path
      name: version
      required: true
      schema: { type: string }
      example: "0.3.0"

  schemas:
    SDKConfig:
      type: object
      required: [brand, rates, sdk_domains, cache]
      properties:
        brand: { $ref: '#/components/schemas/BrandBlock' }
        amb:
          allOf: [{ $ref: '#/components/schemas/AMBBlock' }]
          nullable: true
          description: Active AMB credentials. Null when no env configured.
        rates: { $ref: '#/components/schemas/RatesBlock' }
        sdk_domains:
          type: array
          items: { $ref: '#/components/schemas/DomainBlock' }
        reseller:
          allOf: [{ $ref: '#/components/schemas/ResellerBlock' }]
          nullable: true
          description: Reseller block — currently a placeholder, always present (nullable).
        cache: { $ref: '#/components/schemas/CacheMeta' }

    BrandBlock:
      type: object
      required: [id, code, status]
      properties:
        id: { type: string, format: uuid }
        code: { type: string, example: amg88 }
        status: { type: string, enum: [active, suspended] }
        active_env: { type: string, enum: [staging, production], nullable: true }

    AMBBlock:
      type: object
      required: [env, wallet_type, agent_username, endpoint_url, api_key]
      properties:
        env: { type: string, enum: [staging, production] }
        wallet_type: { type: string, example: superapi }
        agent_username: { type: string }
        endpoint_url: { type: string, format: uri }
        api_key:
          type: string
          description: Decrypted AMB SuperAPI key. **Keep secret.**
        signature_key:
          type: string
          description: Optional secondary key for double-signed wallets.

    RatesBlock:
      type: object
      required: [tier, currency, items]
      properties:
        tier: { type: string, example: tier1 }
        currency: { type: string, example: THB }
        items:
          type: array
          items: { $ref: '#/components/schemas/RateItem' }

    RateItem:
      type: object
      required: [product_code, rate_pct, basis, enabled]
      properties:
        product_code: { type: string, example: slot.pgsoft }
        product_name: { type: string }
        category: { type: string, example: slot }
        rate_pct:
          type: number
          format: float
          description: Commission rate in percent (e.g. 1.5 = 1.5%)
        basis:
          type: string
          enum: [turnover, win, net]
        enabled: { type: boolean }

    DomainBlock:
      type: object
      required: [pattern, is_wildcard]
      properties:
        pattern: { type: string, example: "*.amg88.com" }
        is_wildcard: { type: boolean }

    ResellerBlock:
      type: object
      properties:
        enabled: { type: boolean }
        tiers:
          type: array
          items: { $ref: '#/components/schemas/RateItem' }

    CacheMeta:
      type: object
      required: [ttl_seconds, served_at, etag]
      properties:
        ttl_seconds: { type: integer, example: 300 }
        served_at: { type: string, format: date-time }
        etag: { type: string }
        hit: { type: boolean, description: True when served from Redis cache }

    EventBatch:
      type: object
      required: [idempotency_key, events]
      properties:
        idempotency_key:
          type: string
          description: Stable key for this batch — replay-safe.
          example: "brand-amg88-2026-06-05-001"
        events:
          type: array
          minItems: 1
          maxItems: 1000
          items: { $ref: '#/components/schemas/Event' }

    Event:
      type: object
      required: [type, amount_cents, occurred_at, idempotency_key]
      properties:
        type:
          type: string
          enum: [bet, win, deposit, withdraw, adjustment]
        amount_cents:
          type: integer
          format: int64
          description: Signed amount in minor currency units (cents/satang).
        currency:
          type: string
          example: THB
        occurred_at:
          type: string
          format: date-time
          description: Wall-clock time the event happened on the brand side.
        ref:
          type: string
          description: Optional reference id from the upstream game system.
        idempotency_key:
          type: string
          description: Stable per-event key. Re-submitting same key is a no-op.

    IngestResult:
      type: object
      properties:
        batch_id: { type: integer, format: int64 }
        accepted: { type: integer }
        duplicated: { type: integer }
        total_submitted: { type: integer }
        already_seen: { type: boolean }
        kind:
          type: string
          enum: [batch, snapshot, critical]

    Release:
      type: object
      properties:
        platform: { type: string, enum: [go, js, python, other] }
        version: { type: string }
        status:
          type: string
          enum: [latest, stable, deprecated, blocked]
        released_at: { type: string, format: date-time }
        published_at:
          type: string
          format: date-time
          nullable: true
        notes: { type: string }
        changelog_md: { type: string }
        filename: { type: string }
        file_size_bytes: { type: integer }
        file_sha256:
          type: string
          description: Hex SHA-256 of the artifact. **Verify before installing.**
        file_content_type: { type: string, example: application/gzip }
        download_url:
          type: string
          description: Relative URL — prepend the API base.
          example: /v1/sdk/download/go/0.3.0

    UpgradeRequired:
      type: object
      properties:
        error: { type: string, example: sdk_version_blocked }
        current: { type: string, example: go/0.1.0 }
        upgrade_to: { type: string, example: go/0.3.0 }
        message: { type: string }

    Error:
      type: object
      properties:
        error: { type: string }
        detail: { type: string }
