openapi: 3.1.0
info:
  title: Convray API
  version: 0.30.2
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
  x-convray-contract-status: active-apc-core-casino-team
  x-convray-changelog:
    - version: 0.30.2
      date: 2026-08-14
      summary: Conserva la sesión al volver a la landing, evita carreras de refresh y marca toda respuesta API como no almacenable.
    - version: 0.30.1
      date: 2026-08-14
      summary: Entrega invitaciones Account Manager mediante fragmento de URL de un solo uso y contraseña definida por la persona invitada.
    - version: 0.30.0
      date: 2026-08-14
      summary: Agrega equipo casino multiusuario y Account Manager operacional de solo lectura.
    - version: 0.29.0
      date: 2026-08-13
      summary: Cierra por defecto el registro público y conserva login y alta controlada para el piloto.
    - version: 0.28.0
      date: 2026-08-13
      summary: Agrega partner administrado exclusivo y casino principal de solo lectura.
    - version: 0.27.0
      date: 2026-07-25
      summary: >-
        Las Wager Races pasan al modelo de casino: cuelgan de una conexión de casino
        externa, el premio va en dinero del casino y Convray deja de acreditar puntos
        por ellas.
    - version: 0.23.1
      date: 2026-07-22
      summary: Limita APC-4 a destinos versionados, links opacos, clicks durables y redirect seguro.
  description: |
    Contrato operativo de identidad partner global, programa afiliado, ofertas
    CPA, RevShare sobre NGR e Hybrid versionadas, aceptación vinculante, deals
    partner-casino y tracking opaco para Affiliate Platform Core.

    El registro público está gobernado por una política de despliegue fail-closed.
    En modo `open` crea inmediatamente la identidad, el workspace partner global,
    la membresía owner y la sesión inicial; en `closed` o `invite_only` responde
    403 antes de validar el body o crear entidades. La extensión creator histórica
    es opcional y no cambia el tipo partner. La autoridad de cada request privado
    se resuelve en el servidor desde una sesión vigente y nunca desde un tenant_id
    enviado por el cliente. Los access tokens administrativos declaran
    `aud=convray-admin`, están ligados a una sesión server-side y dejan de ser
    válidos cuando esa sesión se revoca.

    Todas las respuestas bajo `/api/v1` incluyen `Cache-Control: private,
    no-store`, `Pragma: no-cache` y `X-Content-Type-Options: nosniff` para evitar
    persistencia accidental de datos operativos en caches compartidas o del
    navegador.

    APC-3 convierte una versión publicada de oferta en una aplicación idempotente,
    conserva snapshots contractuales y permite provisioning L0/L1 con lifecycle
    seguro. APC-4 incorpora exclusivamente destinos HTTPS versionados, links
    opacos ligados a deals activos, clicks durables previos al redirect y visitor
    keys pseudónimos con retención limitada. El cálculo financiero comienza en
    APC-6.
servers:
  - url: "{protocol}://{host}:{port}/api/v1"
    description: Entorno local canónico de desarrollo
    variables:
      protocol:
        default: http
        enum:
          - http
      host:
        default: localhost
      port:
        default: "8080"
tags:
  - name: Identity
    description: Identidad partner global con alta pública gobernada por política de despliegue.
  - name: Session
    description: Creación, rotación, contexto, selección y revocación de sesión.
  - name: YouTube
    description: Conexión OAuth oficial y tenant-scoped del canal del streamer.
  - name: Kick
    description: Conexión tenant-scoped y webhooks firmados del canal Kick.
  - name: Community
    description: Comunidad, puntos y preferencias tenant-scoped del streamer.
  - name: Overlay
    description: Actividad pública de Convray seleccionada por cada streamer.
  - name: Giveaways
    description: Sorteos tenant-scoped, entradas viewer, cierre automático y selección owner auditable.
  - name: Store
    description: Catálogo y canjes tenant-scoped con fulfillment sandbox atómico e idempotente.
  - name: MiniGames
    description: Mini-juegos tenant-scoped operados exclusivamente con Puntos Convray sin valor monetario.
  - name: Platform
    description: Operaciones globales exclusivas de SuperAdmin con grant explícito y motivo auditado.
  - name: Casino
    description: Onboarding, programa afiliado, ofertas CPA/RevShare/Hybrid y Casino Landing B2B del tenant casino.
  - name: Deals
    description: Aplicaciones, aceptación vinculante y relaciones comerciales partner-casino.
  - name: Analytics
    description: Analítica first-party del Streamer Site y agregados autoritativos tenant-scoped.
  - name: Tracking
    description: Destinos versionados, links opacos y redirect público con click durable.
x-convray-problem-catalog:
  product_subscription_required:
    lifecycle: future-product-response
    http_status: 402
    media_type: application/problem+json
    schema:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 402
            code:
              type: string
              const: subscription_required
    example:
      type: https://api.convray.com/problems/subscription-required
      title: Suscripción requerida
      status: 402
      detail: Activa la suscripción del tenant para usar este módulo.
      instance: /api/v1/example-product-resource
      code: subscription_required
      request_id: req_01
paths:
  /partner-registrations:
    post:
      tags:
        - Identity
      operationId: createPartnerRegistration
      summary: Crea una cuenta partner cuando el alta pública está habilitada
      x-convray-runtime-status: gated-apc-1
      x-convray-registration-modes:
        - open
        - closed
        - invite_only
      description: |
        Sólo en modo `open`, crea atómicamente la identidad, el workspace partner
        global, la membresía owner y la sesión inicial. `creator_enabled` habilita
        la extensión creator histórica sin cambiar el tipo partner ni la autoridad
        del core. La configuración por defecto es `closed`.

        En modos `closed` o `invite_only`, responde
        `public_registration_closed` antes de decodificar o validar el body y sin
        crear usuario, tenant, membresía o sesión. El intento queda auditado. El
        login y las sesiones de cuentas existentes no cambian.

        Un payload que intente forzar el valor `casino` mediante exactamente uno
        de los aliases protegidos `role`, `account_type` o `tenant_kind` se
        rechaza con 403 antes de la validación general del payload. No se crea
        ninguna entidad parcial. Cualquier otro campo extra responde 422.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerRegistrationInput"
      responses:
        "201":
          description: Cuenta partner y sesión creadas
          headers:
            Cache-Control:
              $ref: "#/components/headers/NoStore"
            Location:
              description: Contexto de sesión creado
              schema:
                type: string
                const: /api/v1/me
            Set-Cookie:
              $ref: "#/components/headers/RefreshCookie"
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "403":
          $ref: "#/components/responses/RegistrationForbidden"
        "409":
          $ref: "#/components/responses/PartnerRegistrationConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /auth/sessions:
    post:
      tags:
        - Session
      operationId: createSession
      summary: Inicia una sesión administrativa con email y contraseña
      x-convray-runtime-status: active-apc-1
      description: |
        Autentica sin revelar si el email existe. El servidor selecciona una
        asignación platform activa antes que una membresía tenant; una sesión
        casino ni un partner genérico requieren entitlement creator. Una
        autoridad platform nunca se obtiene de una membresía tenant.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginInput"
      responses:
        "200":
          description: Sesión administrativa creada
          headers:
            Set-Cookie:
              $ref: "#/components/headers/RefreshCookie"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "401":
          $ref: "#/components/responses/InvalidCredentials"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /auth/sessions/refresh:
    post:
      tags:
        - Session
      operationId: refreshSession
      summary: Rota la credencial refresh de una sesión administrativa vigente
      x-convray-runtime-status: active-m4
      description: |
        Rota la credencial opaca y emite un access token con
        `aud=convray-admin`, ligado a la misma sesión server-side. Una credencial
        revocada, expirada o reproducida responde 401 sin emitir `Set-Cookie`;
        así una respuesta obsoleta no puede borrar una rotación más reciente de
        otra pestaña.
      security:
        - adminRefreshCookie: []
      responses:
        "200":
          description: Credenciales administrativas rotadas
          headers:
            Set-Cookie:
              $ref: "#/components/headers/RefreshCookie"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /auth/sessions/current:
    patch:
      tags:
        - Session
      operationId: selectActiveMembership
      summary: Selecciona el tenant activo desde una membresía vigente
      x-convray-runtime-status: active-apc-1
      description: |
        Acepta únicamente un membership_id de la lista activa del usuario. Un
        tenant_id, streamer_id o casino_id adicional no cambia la
        autoridad. Al cambiar la membresía, el servidor rota la sesión antes de
        responder y las credenciales anteriores dejan de ser válidas.

        La operación exige simultáneamente bearer y cookie refresh vigentes,
        crea una sesión nueva y revoca las credenciales anteriores. Un partner
        genérico no recibe entitlement creator.
      security:
        - adminBearer: []
          adminRefreshCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ActiveMembershipSelectionInput"
      responses:
        "200":
          description: Membresía activa seleccionada y credenciales rotadas
          headers:
            Set-Cookie:
              $ref: "#/components/headers/RefreshCookie"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    delete:
      tags:
        - Session
      operationId: deleteCurrentSession
      summary: Revoca la sesión administrativa actual en el servidor
      x-convray-runtime-status: active-m4
      description: |
        Revoca la sesión server-side y expira la cookie. Puede autenticarse con
        la cookie refresh administrativa. La operación es idempotente cuando la
        cookie no existe. Reutilizar cualquier credencial revocada responde 401
        en refresh o en un recurso protegido posterior.
      security:
        - adminRefreshCookie: []
      responses:
        "204":
          description: Sesión revocada
          headers:
            Set-Cookie:
              $ref: "#/components/headers/ExpiredRefreshCookie"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /me:
    get:
      tags:
        - Session
      operationId: getCurrentSessionContext
      summary: Devuelve la autoridad administrativa activa
      x-convray-runtime-status: active-m4
      description: |
        Revalida una credencial administrativa con `aud=convray-admin`, la
        sesión server-side y exactamente una rama de autoridad. Un token Viewer
        con otra audiencia no autentica este endpoint y responde 401
        `invalid_session`; no se convierte en una sesión administrativa.

        Devuelve la identidad y autoridad activa. Para `tenant_member` incluye
        el tenant partner o casino y sus membresías activas; para
        `platform_admin` los campos tenant son null porque la sesión global no
        inventa una membresía.
      security:
        - adminBearer: []
      responses:
        "200":
          description: Contexto administrativo vigente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RuntimeUser"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/profile:
    get:
      tags:
        - Identity
      operationId: getPartnerProfile
      summary: Lee el perfil comercial del partner activo
      x-convray-runtime-status: active-apc-1
      security:
        - adminBearer: []
      responses:
        "200":
          description: Perfil propio y canales declarados
          headers:
            ETag:
              description: Versión fuerte del agregado partner
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerProfile"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    patch:
      tags:
        - Identity
      operationId: updatePartnerProfile
      summary: Actualiza campos comerciales y reemplaza canales declarados
      x-convray-runtime-status: active-apc-1
      description: |
        Solo owner. `expected_version` evita sobrescrituras concurrentes. Un
        array `channels` presente reemplaza la colección completa; verificación,
        status, términos y capacidad creator no son editables desde este recurso.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerProfileUpdateInput"
      responses:
        "200":
          description: Perfil actualizado y evidencia persistida atómicamente
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerProfile"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          description: Sin permiso owner u Origin rechazado
          content:
            application/problem+json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/PermissionDeniedProblem"
                  - $ref: "#/components/schemas/CSRFRejectedProblem"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /platform/casinos:
    post:
      tags:
        - Platform
        - Casino
      operationId: onboardCasino
      summary: Crea el agregado casino después de registrar un contrato activo
      x-convray-runtime-status: active-phase-5
      description: |
        Operación atómica exclusiva de SuperAdmin con grant global `tenant.write`.
        Crea contrato, tenant tipado, Casino Landing draft e invitación owner. El
        token se devuelve una sola vez y solo su SHA-256 queda persistido.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoOnboardingInput"
      responses:
        "201":
          description: Casino, contrato, landing e invitación creados
          headers:
            Location:
              schema:
                type: string
                const: /api/v1/casino/dashboard
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoOnboardingResult"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino-invitation-acceptances:
    post:
      tags:
        - Casino
      operationId: acceptCasinoInvitation
      summary: Acepta una invitación casino de un solo uso
      x-convray-runtime-status: active-phase-5
      description: >-
        El token y la contraseña viajan exclusivamente en el body. La web oficial
        puede recibir el token en el fragmento #token de una URL de invitación;
        ese fragmento no se envía por HTTP, se consume localmente y se elimina de
        la barra del navegador antes de ejecutar esta operación.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoInvitationAcceptanceInput"
      responses:
        "201":
          description: Identidad y membresía casino activadas
          headers:
            Location:
              schema:
                type: string
                const: /api/v1/auth/sessions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoInvitationAcceptance"
        "401":
          $ref: "#/components/responses/InvalidCredentials"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "410":
          description: Invitación vencida
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /deals:
    get:
      tags:
        - Deals
      operationId: listDeals
      summary: Lista la proyección allowlisted de deals del tenant efectivo
      x-convray-runtime-status: active-phase-5
      security:
        - adminBearer: []
      responses:
        "200":
          description: Solo relaciones donde participa el tenant efectivo
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DealList"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/dashboard:
    get:
      tags:
        - Casino
      operationId: getCasinoDashboard
      summary: Devuelve el resumen privado del tenant casino efectivo
      x-convray-runtime-status: active-phase-5
      security:
        - adminBearer: []
      responses:
        "200":
          description: Identidad casino, landing privada y deals relacionados
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoDashboard"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/team:
    get:
      tags:
        - Casino
      operationId: getCasinoTeam
      summary: Lista miembros e invitaciones del casino efectivo
      x-convray-runtime-status: active-casino-team
      description: Solo Casino Admin puede consultar correos y estados del equipo. Los tokens nunca forman parte del listado.
      security:
        - adminBearer: []
      responses:
        "200":
          description: Equipo aislado al tenant de la sesión
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoTeam"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
  /casino/team/invitations:
    post:
      tags:
        - Casino
      operationId: createCasinoTeamInvitation
      summary: Invita un Account Manager al casino efectivo
      x-convray-runtime-status: active-casino-team
      description: El servidor fija el rol member. El token opaco se devuelve una sola vez y solo su SHA-256 queda persistido.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoTeamInvitationCreateInput"
      responses:
        "201":
          description: Invitación creada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoTeamInvitation"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/team/invitations/{invitation_id}:
    parameters:
      - name: invitation_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    patch:
      tags:
        - Casino
      operationId: revokeCasinoTeamInvitation
      summary: Revoca una invitación pendiente
      x-convray-runtime-status: active-casino-team
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoTeamMutationInput"
      responses:
        "200":
          description: Invitación revocada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoTeamInvitation"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/team/members/{membership_id}:
    parameters:
      - name: membership_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    patch:
      tags:
        - Casino
      operationId: updateCasinoTeamMember
      summary: Suspende, reactiva o revoca un Account Manager
      x-convray-runtime-status: active-casino-team
      description: No permite modificar owners. Cada cambio incrementa la autoridad de la membresía e invalida sus sesiones anteriores.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoTeamMemberUpdateInput"
      responses:
        "200":
          description: Membresía actualizada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoTeamMember"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/landing/settings:
    get:
      tags:
        - Casino
      operationId: getCasinoLandingSettings
      summary: Lee el borrador privado de la Casino Landing
      x-convray-runtime-status: active-phase-5
      security:
        - adminBearer: []
      responses:
        "200":
          description: Borrador privado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoLandingSettings"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
    patch:
      tags:
        - Casino
      operationId: updateCasinoLandingSettings
      summary: Guarda, publica o despublica la Casino Landing
      x-convray-runtime-status: active-phase-5
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoLandingUpdateInput"
      responses:
        "200":
          description: Borrador actualizado; publicar reemplaza el snapshot atómicamente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoLandingSettings"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/programs:
    get:
      tags:
        - Casino
      operationId: listAffiliatePrograms
      summary: Lista los programas del casino efectivo
      x-convray-runtime-status: active-apc-2
      security:
        - adminBearer: []
      responses:
        "200":
          description: Catálogo privado limitado a 100 programas
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AffiliateProgramList"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Casino
      operationId: createAffiliateProgram
      summary: Crea un programa afiliado para el casino efectivo
      x-convray-runtime-status: active-apc-2
      description: El tenant y el actor se derivan de la sesión; el body no acepta campos de autoridad.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AffiliateProgramCreateInput"
      responses:
        "201":
          description: Programa creado
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/casino/programs/[0-9a-f-]{36}$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AffiliateProgram"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/programs/{program_id}:
    parameters:
      - name: program_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    patch:
      tags:
        - Casino
      operationId: updateAffiliateProgram
      summary: Actualiza por versión optimista un programa propio
      x-convray-runtime-status: active-apc-2
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AffiliateProgramUpdateInput"
      responses:
        "200":
          description: Programa actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AffiliateProgram"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/offers:
    get:
      tags:
        - Casino
      operationId: listCasinoOffers
      summary: Lista ofertas y su historial inmutable del casino efectivo
      x-convray-runtime-status: active-apc-2
      security:
        - adminBearer: []
      responses:
        "200":
          description: Hasta 100 ofertas privadas con versiones descendentes
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OfferList"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Casino
      operationId: createCasinoOffer
      summary: Crea la identidad estable de una oferta
      x-convray-runtime-status: active-apc-2
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OfferCreateInput"
      responses:
        "201":
          description: Oferta draft sin versiones
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/casino/offers/[0-9a-f-]{36}$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Offer"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/offers/{offer_id}/versions:
    parameters:
      - name: offer_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags:
        - Casino
      operationId: createOfferVersion
      summary: Crea la siguiente versión inmutable de una oferta
      x-convray-runtime-status: active-apc-2.1
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OfferVersionCreateInput"
            examples:
              cpa:
                summary: CPA fijo por adquisición
                value:
                  commercial_model: cpa
                  cpa_amount_minor: 2500
                  criteria_summary: Primer depósito confirmado por el casino
                  terms_version: offer-v1
                  terms_url: https://casino.example/cpa-terms
                  valid_from: 2026-07-21T00:00:00Z
              revshare:
                summary: RevShare contractual sobre NGR
                value:
                  commercial_model: revshare
                  revshare_bps: 3000
                  criteria_summary: 30% del NGR atribuible al partner
                  terms_version: offer-v1
                  terms_url: https://casino.example/revshare-terms
                  valid_from: 2026-07-21T00:00:00Z
              hybrid:
                summary: CPA más RevShare contractual sobre NGR
                value:
                  commercial_model: hybrid
                  cpa_amount_minor: 1500
                  revshare_bps: 2000
                  criteria_summary: CPA inicial más 20% del NGR atribuible
                  terms_version: offer-v1
                  terms_url: https://casino.example/hybrid-terms
                  valid_from: 2026-07-21T00:00:00Z
      responses:
        "201":
          description: Versión consecutiva creada; la moneda se deriva del programa
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/casino/offer-versions/[0-9a-f-]{36}$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OfferVersion"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/offer-versions/{version_id}/publications:
    parameters:
      - name: version_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags:
        - Casino
      operationId: publishOfferVersion
      summary: Publica una versión exactamente una vez
      x-convray-runtime-status: active-apc-2.1
      description: Requiere que el programa esté published y actualiza el puntero estable de la oferta.
      security:
        - adminBearer: []
      responses:
        "201":
          description: Publicación inmutable creada
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/casino/offer-versions/[0-9a-f-]{36}/publications$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Offer"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/programs:
    get:
      tags:
        - Deals
      operationId: getPartnerProgramCandidate
      summary: Resuelve una oferta publicada antes de aplicar
      x-convray-runtime-status: active-apc-3
      description: |
        Devuelve snapshots contractuales y elegibilidad para el partner efectivo.
        Consultar no crea una aplicación ni acepta términos.
      security:
        - adminBearer: []
      parameters:
        - name: casino
          in: query
          required: true
          schema:
            type: string
            minLength: 3
            maxLength: 63
            pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        - name: offer
          in: query
          required: true
          schema:
            type: string
            minLength: 3
            maxLength: 63
            pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
      responses:
        "200":
          description: Oferta y elegibilidad vigentes para el partner
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerProgramCandidate"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/deals:
    get:
      tags:
        - Deals
      operationId: listPartnerDeals
      summary: Lista los deals del partner efectivo
      x-convray-runtime-status: active-apc-3
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/RelationshipLimit"
        - $ref: "#/components/parameters/RelationshipCursor"
        - $ref: "#/components/parameters/RelationshipStatus"
      responses:
        "200":
          description: Página de deals aislada al partner derivado de la sesión
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelationshipDealPage"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Deals
      operationId: createPartnerDeal
      summary: Acepta términos y crea una aplicación idempotente
      x-convray-runtime-status: active-apc-3
      description: |
        Partner, actor, timestamps y estado inicial se derivan en el servidor. La
        misma clave y payload devuelve el deal existente sin duplicar evidencia.
        Un partner `managed_exclusive` solo puede crear el deal con su
        `home_casino_tenant_id`; el cliente no puede cambiar esa autoridad.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerDealCreateInput"
      responses:
        "201":
          description: Aplicación creada o replay idempotente confirmado
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/partner/deals/[0-9a-f-]{36}$
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelationshipDeal"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          description: Actor sin permiso o partner no activo
          content:
            application/problem+json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/PermissionDeniedProblem"
                  - $ref: "#/components/schemas/PartnerNotActiveProblem"
                  - $ref: "#/components/schemas/ManagedExclusiveCasinoProblem"
              examples:
                permissionDenied:
                  value:
                    type: https://api.convray.com/problems/permission-denied
                    title: Acción no permitida
                    status: 403
                    detail: No tienes permiso para realizar esta acción.
                    instance: /api/v1/partner/deals
                    code: permission_denied
                    request_id: req_01
                partnerNotActive:
                  value:
                    type: https://api.convray.com/problems/partner-not-active
                    title: Partner no activo
                    status: 403
                    detail: El partner debe estar activo para aplicar a una oferta.
                    instance: /api/v1/partner/deals
                    code: partner_not_active
                    request_id: req_01
                managedExclusiveCasino:
                  value:
                    type: https://api.convray.com/problems/managed-exclusive-casino
                    title: Casino administrado
                    status: 403
                    detail: Este partner opera exclusivamente con el casino asignado por su organización.
                    instance: /api/v1/partner/deals
                    code: managed_exclusive_casino
                    request_id: req_01
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/RelationshipRuleViolation"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/deals/{deal_id}:
    parameters:
      - $ref: "#/components/parameters/RelationshipDealId"
    get:
      tags:
        - Deals
      operationId: getPartnerDeal
      summary: Lee un deal propio del partner
      x-convray-runtime-status: active-apc-3
      security:
        - adminBearer: []
      responses:
        "200":
          description: Deal con snapshots y acciones permitidas por el servidor
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelationshipDeal"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    patch:
      tags:
        - Deals
      operationId: cancelPartnerDeal
      summary: Cancela una aplicación propia aún submitted
      x-convray-runtime-status: active-apc-3
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerDealCancelInput"
      responses:
        "200":
          description: Aplicación cancelada con versión incrementada
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelationshipDeal"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/deals:
    get:
      tags:
        - Deals
      operationId: listCasinoPartnerDeals
      summary: Lista aplicaciones y relaciones del casino efectivo
      x-convray-runtime-status: active-apc-3
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/RelationshipLimit"
        - $ref: "#/components/parameters/RelationshipCursor"
        - $ref: "#/components/parameters/RelationshipStatus"
      responses:
        "200":
          description: Página de deals aislada al casino derivado de la sesión
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelationshipDealPage"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/deals/{deal_id}:
    parameters:
      - $ref: "#/components/parameters/RelationshipDealId"
    get:
      tags:
        - Deals
      operationId: getCasinoPartnerDeal
      summary: Lee un deal del casino efectivo
      x-convray-runtime-status: active-apc-3
      security:
        - adminBearer: []
      responses:
        "200":
          description: Deal con snapshots y acciones según rol vigente
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelationshipDeal"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    patch:
      tags:
        - Deals
      operationId: transitionCasinoPartnerDeal
      summary: Transiciona y provisiona un deal como Casino Admin
      x-convray-runtime-status: active-apc-3
      description: |
        Usa expected_version. Activar requiere mapping L0/L1 no secreto; Casino
        Account Manager conserva acceso de lectura pero no puede mutar.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoDealTransitionInput"
      responses:
        "200":
          description: Deal transicionado con versión incrementada
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelationshipDeal"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/tracking-destinations:
    get:
      tags:
        - Tracking
      operationId: listCasinoTrackingDestinations
      summary: Lista destinos estables del casino efectivo
      x-convray-runtime-status: active-apc-4
      description: Casino Admin y Casino Account Manager tienen lectura. La sesión resuelve el casino; no se acepta autoridad tenant en query o body.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/TrackingLimit"
        - $ref: "#/components/parameters/TrackingCursor"
        - $ref: "#/components/parameters/TrackingDestinationStatus"
        - $ref: "#/components/parameters/TrackingProgramId"
      responses:
        "200":
          description: Página de destinos con su versión publicada actual
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingDestinationPage"
              example:
                items:
                  - id: 2d11815c-7df6-44c8-b49f-0935966cfb1b
                    program_id: a17a19b8-e8fe-489f-ab30-a495c8b92b10
                    code: latam-main
                    description: Landing principal de adquisición
                    status: published
                    current_published_version_id: 036c7863-58f2-45a4-af89-e1a637126f04
                    version: 2
                    created_at: 2026-07-21T12:00:00Z
                    updated_at: 2026-07-21T12:30:00Z
                next_cursor: null
                limit: 25
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Tracking
      operationId: createCasinoTrackingDestination
      summary: Crea un destino estable y su primera versión inmutable
      x-convray-runtime-status: active-apc-4
      description: Solo Casino Admin. El mismo Idempotency-Key y digest devuelve el resultado original; reutilizar la key con otro digest responde 409.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrackingDestinationCreateInput"
            example:
              program_id: a17a19b8-e8fe-489f-ab30-a495c8b92b10
              code: latam-main
              description: Landing principal de adquisición
              initial_version:
                target_url: https://offers.casino.example/register
                allowed_hosts:
                  - offers.casino.example
                parameter_template:
                  click_id: "{click_id}"
                  affiliate_ref: "{provider_account}"
                valid_from: 2026-07-21T12:00:00Z
                valid_until: null
      responses:
        "201":
          description: Identidad draft y primera versión inmutable creadas
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/casino/tracking-destinations/[0-9a-f-]{36}$
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingDestinationDetail"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionOrCSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/tracking-destinations/{destination_id}:
    parameters:
      - $ref: "#/components/parameters/TrackingDestinationId"
    get:
      tags:
        - Tracking
      operationId: getCasinoTrackingDestination
      summary: Lee un destino estable y su versión publicada actual
      x-convray-runtime-status: active-apc-4
      description: Casino Admin y Casino Account Manager tienen lectura. Un destino ajeno se oculta como 404.
      security:
        - adminBearer: []
      responses:
        "200":
          description: Destino y versión publicada allowlisted
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingDestinationDetail"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    patch:
      tags:
        - Tracking
      operationId: transitionCasinoTrackingDestination
      summary: Cambia estado o versión publicada del destino
      x-convray-runtime-status: active-apc-4
      description: Solo Casino Admin. Publicar cambia atómicamente el puntero a una versión inmutable propia; pausar, reactivar o archivar conserva todo el historial.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrackingDestinationTransitionInput"
            example:
              status: published
              published_version_id: 036c7863-58f2-45a4-af89-e1a637126f04
              reason: Publicación aprobada para la campaña regional.
              expected_version: 1
      responses:
        "200":
          description: Destino transicionado con versión optimista incrementada
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingDestinationDetail"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionOrCSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/tracking-destinations/{destination_id}/versions:
    parameters:
      - $ref: "#/components/parameters/TrackingDestinationId"
    get:
      tags:
        - Tracking
      operationId: listCasinoTrackingDestinationVersions
      summary: Lista versiones inmutables de un destino
      x-convray-runtime-status: active-apc-4
      description: Casino Admin y Casino Account Manager tienen lectura. Las versiones se ordenan por version_number descendente.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/TrackingLimit"
        - $ref: "#/components/parameters/TrackingCursor"
      responses:
        "200":
          description: Página de versiones inmutables
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingDestinationVersionPage"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Tracking
      operationId: createCasinoTrackingDestinationVersion
      summary: Crea una nueva versión inmutable todavía no publicada
      x-convray-runtime-status: active-apc-4
      description: Solo Casino Admin. Crear una versión no cambia el puntero publicado. El replay idempotente no crea otra versión.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrackingDestinationVersionCreateInput"
            example:
              target_url: https://offers.casino.example/register-v2
              allowed_hosts:
                - offers.casino.example
              parameter_template:
                click_id: "{click_id}"
                affiliate_ref: "{provider_account}"
              valid_from: 2026-08-01T00:00:00Z
              valid_until: null
      responses:
        "201":
          description: Siguiente versión inmutable creada
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/casino/tracking-destinations/[0-9a-f-]{36}/versions$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingDestinationVersion"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionOrCSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/deals/{deal_id}/tracking-destinations:
    parameters:
      - $ref: "#/components/parameters/RelationshipDealId"
    get:
      tags:
        - Tracking
      operationId: listPartnerDealTrackingDestinations
      summary: Lista destinos publicados disponibles para crear links del deal
      x-convray-runtime-status: active-apc-4
      description: |
        El partner owner que opera links puede leer identidades de destino del
        programa y casino de un deal propio active. Solo incluye destinos
        published cuya versión actual exista y esté vigente. Un deal ajeno,
        inexistente o no active se oculta como 404. La proyección no expone URL
        target, hosts, templates ni datos de otros tenants.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/TrackingLimit"
        - $ref: "#/components/parameters/TrackingCursor"
      responses:
        "200":
          description: Página de identidades de destino elegibles para el deal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingDestinationPage"
              example:
                items:
                  - id: 2d11815c-7df6-44c8-b49f-0935966cfb1b
                    program_id: a17a19b8-e8fe-489f-ab30-a495c8b92b10
                    code: latam-main
                    description: Landing principal de adquisición
                    status: published
                    current_published_version_id: 036c7863-58f2-45a4-af89-e1a637126f04
                    version: 2
                    created_at: 2026-07-21T12:00:00Z
                    updated_at: 2026-07-21T12:30:00Z
                next_cursor: null
                limit: 25
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/deals/{deal_id}/tracking-links:
    parameters:
      - $ref: "#/components/parameters/RelationshipDealId"
    post:
      tags:
        - Tracking
      operationId: createPartnerTrackingLink
      summary: Crea un link opaco para un deal activo propio
      x-convray-runtime-status: active-apc-4
      description: Partner owner. El servidor resuelve partner, casino y destino; un deal ajeno se oculta como 404. El replay idempotente no emite otro token.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrackingLinkCreateInput"
            example:
              destination_id: 2d11815c-7df6-44c8-b49f-0935966cfb1b
              label: Perfil principal
      responses:
        "201":
          description: Link opaco creado
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/partner/tracking-links/[0-9a-f-]{36}$
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingLink"
              example:
                id: ce38f6a9-a261-447c-ac4e-850afeb09072
                deal_id: 541941f5-b6e5-4d18-8f76-9c81889661fc
                destination_id: 2d11815c-7df6-44c8-b49f-0935966cfb1b
                token: AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8
                public_url: https://go.convray.com/r/AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8
                label: Perfil principal
                resolved_destination_version_id: 036c7863-58f2-45a4-af89-e1a637126f04
                status: active
                version: 1
                created_at: 2026-07-21T13:00:00Z
                updated_at: 2026-07-21T13:00:00Z
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionOrCSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/tracking-links:
    get:
      tags:
        - Tracking
      operationId: listPartnerTrackingLinks
      summary: Lista links opacos propios del partner
      x-convray-runtime-status: active-apc-4
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/TrackingLimit"
        - $ref: "#/components/parameters/TrackingCursor"
        - $ref: "#/components/parameters/TrackingDealIdQuery"
        - $ref: "#/components/parameters/TrackingLinkStatus"
      responses:
        "200":
          description: Página de links del partner derivado de la sesión
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingLinkPage"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /partner/tracking-links/{link_id}:
    parameters:
      - $ref: "#/components/parameters/TrackingLinkId"
    get:
      tags:
        - Tracking
      operationId: getPartnerTrackingLink
      summary: Lee un link opaco propio
      x-convray-runtime-status: active-apc-4
      security:
        - adminBearer: []
      responses:
        "200":
          description: Link propio
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingLink"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    patch:
      tags:
        - Tracking
      operationId: transitionPartnerTrackingLink
      summary: Pausa, reactiva o revoca un link propio
      x-convray-runtime-status: active-apc-4
      description: Partner owner. Toda transición exige reason de 10 a 500 caracteres y expected_version; revoked es terminal.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrackingLinkTransitionInput"
            example:
              action: pause
              reason: Campaña pausada temporalmente por revisión.
              expected_version: 1
      responses:
        "200":
          description: Link transicionado y auditado atómicamente
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingLink"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionOrCSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/tracking-links:
    get:
      tags:
        - Tracking
      operationId: listCasinoTrackingLinks
      summary: Lista links de los deals del casino efectivo
      x-convray-runtime-status: active-apc-4
      description: Casino Admin y Casino Account Manager tienen lectura. Los filtros nunca amplían el tenant derivado de la sesión.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/TrackingLimit"
        - $ref: "#/components/parameters/TrackingCursor"
        - $ref: "#/components/parameters/TrackingDealIdQuery"
        - $ref: "#/components/parameters/TrackingLinkStatus"
      responses:
        "200":
          description: Página de links aislada al casino
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingLinkPage"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /casino/tracking-links/{link_id}:
    parameters:
      - $ref: "#/components/parameters/TrackingLinkId"
    get:
      tags:
        - Tracking
      operationId: getCasinoTrackingLink
      summary: Lee un link de un deal del casino efectivo
      x-convray-runtime-status: active-apc-4
      description: Casino Admin y Casino Account Manager tienen lectura. Un link de otro casino se oculta como 404.
      security:
        - adminBearer: []
      responses:
        "200":
          description: Link del casino
          headers:
            ETag:
              schema:
                type: string
                pattern: ^"[1-9][0-9]*"$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingLink"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /r/{opaque_token}:
    parameters:
      - $ref: "#/components/parameters/OpaqueTrackingToken"
    get:
      tags:
        - Tracking
      operationId: redirectOpaqueTrackingLink
      summary: Persiste el click y redirige mediante el link opaco
      x-convray-runtime-status: active-apc-4
      description: |
        Handler backend directo. Confirma el click y la versión de destino resuelta
        con redirect_issued=true antes de escribir el 302. Puede emitir una cookie
        first-party pseudónima. Convray no solicita ni sigue el destino externo y
        no documenta un outcome remoto.
      servers:
        - url: https://go.convray.com
          description: Backend directo de producción mediante HTTPS, sin el prefijo /api/v1
      security: []
      responses:
        "302":
          description: Click durable confirmado; redirect temporal emitido
          headers:
            Location:
              description: Destino HTTPS inmediato validado contra la versión allowlisted
              required: true
              schema:
                type: string
                format: uri
                maxLength: 2048
                pattern: ^https://
              example: https://offers.casino.example/register?click_id=01K0CLICK
            Set-Cookie:
              $ref: "#/components/headers/TrackingVisitorCookie"
        "404":
          $ref: "#/components/responses/TrackingLinkNotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/TrackingRedirectUnavailable"
  /public/casinos/{casino_slug}/affiliate-program:
    parameters:
      - name: casino_slug
        in: path
        required: true
        schema:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
    get:
      tags:
        - Casino
      operationId: getPublicAffiliateProgram
      summary: Devuelve el programa y contenido B2B del snapshot público
      x-convray-runtime-status: active-apc-2
      security: []
      responses:
        "200":
          description: Programa allowlisted sin UUID, tenant, actor ni auditoría
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicAffiliateProgram"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
  /public/casinos/{casino_slug}/offers:
    parameters:
      - name: casino_slug
        in: path
        required: true
        schema:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
    get:
      tags:
        - Casino
      operationId: listPublicCasinoOffers
      summary: Lista ofertas CPA, RevShare e Hybrid vigentes del snapshot público
      x-convray-runtime-status: active-apc-2.1
      security: []
      responses:
        "200":
          description: Ofertas allowlisted sin identificadores internos
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicOfferList"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
  /integrations/youtube:
    get:
      tags:
        - YouTube
      operationId: getYouTubeIntegration
      summary: Consulta la conexión YouTube del tenant streamer activo
      x-convray-runtime-status: active-youtube-v1
      security:
        - adminBearer: []
          adminRefreshCookie: []
      responses:
        "200":
          description: Estado aislado de la integración; no expone credenciales OAuth
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YouTubeIntegrationStatus"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/YouTubeForbidden"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    delete:
      tags:
        - YouTube
      operationId: disconnectYouTubeIntegration
      summary: Desconecta el canal del tenant streamer activo
      x-convray-runtime-status: active-youtube-v1
      description: |
        Elimina localmente las credenciales cifradas y detiene futuras
        sincronizaciones. La revocación remota se intenta después de confirmar
        el cambio local y no bloquea el resto del dashboard.
      security:
        - adminBearer: []
      responses:
        "200":
          description: Conexión eliminada de forma idempotente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YouTubeIntegrationStatus"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/YouTubeForbidden"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /integrations/youtube/authorizations:
    post:
      tags:
        - YouTube
      operationId: createYouTubeAuthorization
      summary: Inicia la autorización oficial de Google con PKCE
      x-convray-runtime-status: active-youtube-v1
      description: |
        Solo el owner del tenant streamer activo puede iniciar el flujo. El
        servidor persiste un `state` de un solo uso y cifra el verifier PKCE;
        ninguno de estos secretos se devuelve salvo la URL oficial de Google.
      security:
        - adminBearer: []
      responses:
        "201":
          description: Autorización efímera creada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YouTubeAuthorizationStart"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/YouTubeForbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/YouTubeUnavailable"
  /integrations/youtube/sync:
    post:
      tags:
        - YouTube
      operationId: syncYouTubeCatalog
      summary: Actualiza manualmente el catálogo YouTube del tenant streamer activo
      x-convray-runtime-status: active-youtube-v1
      description: |
        Renueva el token si corresponde, consulta la playlist de subidas y
        reemplaza únicamente el catálogo público del tenant autorizado. La misma
        operación también es ejecutada por el worker periódico con lease.
      security:
        - adminBearer: []
      responses:
        "200":
          description: Catálogo actualizado o error del proveedor registrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/YouTubeIntegrationStatus"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/YouTubeForbidden"
        "409":
          description: Canal no conectado o sincronización ya en curso
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "502":
          description: YouTube no respondió o devolvió datos inválidos
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "503":
          $ref: "#/components/responses/YouTubeUnavailable"
  /integrations/youtube/callback:
    get:
      tags:
        - YouTube
      operationId: completeYouTubeAuthorization
      summary: Completa el callback OAuth de Google
      x-convray-runtime-status: active-youtube-v1
      description: |
        Consume una vez el `state`, intercambia el código en el backend,
        resuelve el canal mediante `channels.list(mine=true)`, importa su catálogo
        inicial y vuelve al dashboard del tenant original. Nunca recibe un
        tenant_id del cliente.
      security: []
      parameters:
        - name: state
          in: query
          required: true
          schema:
            type: string
            minLength: 43
            maxLength: 43
        - name: code
          in: query
          schema:
            type: string
        - name: error
          in: query
          schema:
            type: string
      responses:
        "303":
          description: Regreso al dashboard tenant-scoped con el resultado en query string
          headers:
            Location:
              schema:
                type: string
                format: uri
        "400":
          description: State inválido, vencido o reproducido
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          description: Google no pudo completar el intercambio o resolver el canal
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "503":
          $ref: "#/components/responses/YouTubeUnavailable"
  /integrations/kick:
    get:
      tags:
        - Kick
      operationId: getKickIntegration
      summary: Consulta el canal Kick del tenant streamer activo
      x-convray-runtime-status: active-kick-v1
      security:
        - adminBearer: []
      responses:
        "200":
          description: Estado tenant-scoped sin access ni refresh tokens del owner
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KickIntegrationStatus"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/KickForbidden"
        "503":
          $ref: "#/components/responses/KickUnavailable"
    delete:
      tags:
        - Kick
      operationId: disconnectKickIntegration
      summary: Elimina las suscripciones webhook y desconecta el canal
      x-convray-runtime-status: active-kick-v1
      security:
        - adminBearer: []
      responses:
        "200":
          description: Canal y regla automática de chat desconectados
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KickIntegrationStatus"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/KickForbidden"
        "502":
          description: Kick no confirmó la eliminación de las suscripciones
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "503":
          $ref: "#/components/responses/KickUnavailable"
  /integrations/kick/authorizations:
    post:
      tags:
        - Kick
      operationId: createKickChannelAuthorization
      summary: Inicia OAuth PKCE para demostrar propiedad del canal
      x-convray-runtime-status: active-kick-v1
      description: |
        Solicita únicamente `user:read` y `channel:read`. El access token del
        owner se usa para resolver el canal y se descarta. Las suscripciones se
        crean después con un App Access Token; no se persisten tokens personales.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KickAuthorizationInput"
      responses:
        "201":
          description: Autorización efímera creada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KickAuthorizationStart"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/KickForbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/KickUnavailable"
  /integrations/kick/callback:
    get:
      tags:
        - Kick
      operationId: completeKickChannelAuthorization
      summary: Completa OAuth, resuelve el canal y crea suscripciones webhook
      x-convray-runtime-status: active-kick-v1
      security: []
      parameters:
        - name: state
          in: query
          required: true
          schema:
            type: string
            minLength: 43
            maxLength: 43
        - name: code
          in: query
          schema:
            type: string
        - name: error
          in: query
          schema:
            type: string
      responses:
        "303":
          description: Regreso al dashboard del tenant original con el resultado
          headers:
            Location:
              schema:
                type: string
                format: uri
        "400":
          description: State inválido, vencido o reproducido
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "409":
          description: El canal ya pertenece a otro tenant
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          description: Kick no completó el intercambio, canal o suscripciones
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "503":
          $ref: "#/components/responses/KickUnavailable"
  /integrations/kick/webhook:
    post:
      tags:
        - Kick
      operationId: receiveKickWebhook
      summary: Recibe un evento Kick firmado e idempotente
      x-convray-runtime-status: active-kick-v1
      description: |
        Verifica RSA-SHA256 sobre message-id, timestamp RFC3339 y body crudo.
        `Kick-Event-Message-Id` es la evidencia e idempotency key. Un chat solo
        acredita puntos si el subject Kick está vinculado y el viewer confirmó
        una suscripción comunitaria activa en el mismo tenant.
      security: []
      parameters:
        - name: Kick-Event-Message-Id
          in: header
          required: true
          schema:
            type: string
            minLength: 8
            maxLength: 128
        - name: Kick-Event-Subscription-Id
          in: header
          required: true
          schema:
            type: string
            minLength: 8
            maxLength: 128
        - name: Kick-Event-Signature
          in: header
          required: true
          schema:
            type: string
            format: byte
        - name: Kick-Event-Message-Timestamp
          in: header
          required: true
          schema:
            type: string
            format: date-time
        - name: Kick-Event-Type
          in: header
          required: true
          schema:
            $ref: "#/components/schemas/KickEventType"
        - name: Kick-Event-Version
          in: header
          required: true
          schema:
            type: string
            const: "1"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "204":
          description: Evento válido procesado o duplicado reconocido
        "401":
          description: Firma, timestamp, suscripción o evidencia inválidos
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "413":
          description: Body mayor a 64 KiB
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "503":
          $ref: "#/components/responses/KickUnavailable"
  /mini-games:
    get:
      tags:
        - MiniGames
      operationId: getMiniGameOverview
      summary: Obtiene configuraciones de mini-juegos del tenant streamer activo
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      security:
        - adminBearer: []
      responses:
        "200":
          description: Configuraciones sandbox aisladas por tenant
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameOverview"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /mini-games/{game}/configurations:
    post:
      tags:
        - MiniGames
      operationId: createMiniGameConfiguration
      summary: Crea un borrador de configuración sandbox para un juego compatible
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      description: Solo configura costos enteros en Puntos Convray; no acepta dinero, criptoactivos, depósitos ni retiros.
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/MiniGameKey"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MiniGameConfigurationCreateInput"
      responses:
        "200":
          description: Replay idempotente del borrador existente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameConfiguration"
        "201":
          description: Borrador creado y auditado
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/mini-games/(flip|dice|plinko|roulette)/configurations/[0-9a-f-]{36}$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameConfiguration"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /mini-games/{game}/configurations/{configurationId}/publication:
    post:
      tags:
        - MiniGames
      operationId: publishMiniGameConfiguration
      summary: Publica una configuración sandbox
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/MiniGameKey"
        - $ref: "#/components/parameters/MiniGameConfigurationId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MiniGameConfigurationPublicationInput"
      responses:
        "200":
          description: Configuración activa o replay de la versión publicada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameConfiguration"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /mini-games/{game}/configurations/{configurationId}/suspension:
    post:
      tags:
        - MiniGames
      operationId: suspendMiniGameConfiguration
      summary: Suspende una configuración activa
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      security:
        - adminBearer: []
      parameters:
        - $ref: "#/components/parameters/MiniGameKey"
        - $ref: "#/components/parameters/MiniGameConfigurationId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MiniGameConfigurationSuspensionInput"
      responses:
        "200":
          description: Configuración suspendida sin alterar rondas ya confirmadas
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameConfiguration"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /viewer/mini-games:
    get:
      tags:
        - MiniGames
      operationId: getViewerMiniGameCatalog
      summary: Obtiene un juego y el saldo privado del viewer en una comunidad
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      security:
        - viewerSessionCookie: []
      parameters:
        - $ref: "#/components/parameters/StreamerSlugQuery"
        - $ref: "#/components/parameters/MiniGameKeyQuery"
      responses:
        "200":
          description: Catálogo tenant-scoped; nunca expone saldo de otra comunidad
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ViewerMiniGameCatalog"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
  /viewer/mini-games/{game}/rounds:
    post:
      tags:
        - MiniGames
      operationId: prepareMiniGameRound
      summary: Prepara una ronda y entrega su commitment previo
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      security:
        - viewerSessionCookie: []
      parameters:
        - $ref: "#/components/parameters/MiniGameKey"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MiniGameRoundPrepareInput"
      responses:
        "200":
          description: Replay idempotente de la ronda preparada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameRoundPrepareResult"
        "201":
          description: Ronda preparada por primera vez sin debitar puntos
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/viewer/mini-games/rounds/[0-9a-f-]{36}$
            Cache-Control:
              schema:
                type: string
                const: private, no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameRoundPrepareResult"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/FairnessUnavailable"
  /viewer/mini-games/{game}/rounds/{roundId}/plays:
    post:
      tags:
        - MiniGames
      operationId: playMiniGameRound
      summary: Liquida una ronda con puntos y proof verificable
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      description: Débito, premio bruto cuando corresponda, saldo, límites, auditoría y outbox se confirman o revierten juntos. Plinko siempre entrega el premio bruto de su bucket, incluso cuando representa una pérdida neta.
      security:
        - viewerSessionCookie: []
      parameters:
        - $ref: "#/components/parameters/MiniGameKey"
        - $ref: "#/components/parameters/MiniGameRoundId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MiniGameRoundPlayInput"
      responses:
        "200":
          description: Settlement inicial o replay, indicado por `replayed`
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameRoundPlayResult"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/FairnessUnavailable"
  /viewer/mini-games/rounds/{roundId}:
    get:
      tags:
        - MiniGames
      operationId: getViewerMiniGameRound
      summary: Obtiene una ronda settled propia con su proof
      x-convray-runtime-status: active-flip-dice-plinko-roulette-sandbox
      security:
        - viewerSessionCookie: []
      parameters:
        - $ref: "#/components/parameters/MiniGameRoundId"
        - $ref: "#/components/parameters/StreamerSlugQuery"
      responses:
        "200":
          description: Ronda settled del viewer y tenant efectivos
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MiniGameSettledRound"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
  /store:
    get:
      tags:
        - Store
      operationId: getStoreOverview
      summary: Obtiene catálogo y órdenes de Tienda del tenant streamer activo
      x-convray-runtime-status: active-store-sandbox
      security:
        - adminBearer: []
      responses:
        "200":
          description: Hasta cien recompensas y cien órdenes aisladas por tenant
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoreOverview"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /store/rewards:
    post:
      tags:
        - Store
      operationId: createStoreReward
      summary: Crea un borrador de recompensa sandbox
      x-convray-runtime-status: active-store-sandbox
      description: |
        La etapa sandbox acepta exclusivamente fulfillment simulado. No admite
        dinero, retiros, transferencias, criptoactivos ni beneficios externos.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreRewardCreateInput"
      responses:
        "201":
          description: Borrador creado con stock reservado para el tenant
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/store/rewards/[0-9a-f-]{36}$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoreReward"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /store/rewards/{rewardId}/publication:
    post:
      tags:
        - Store
      operationId: publishStoreReward
      summary: Publica una recompensa sandbox vigente en el Streamer Site
      x-convray-runtime-status: active-store-sandbox
      security:
        - adminBearer: []
      parameters:
        - name: rewardId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - expected_version
              properties:
                expected_version:
                  type: integer
                  minimum: 1
      responses:
        "200":
          description: Recompensa publicada con versión optimista
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoreReward"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          description: Versión desactualizada, estado no publicable o vigencia vencida
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /viewer/store/orders:
    get:
      tags:
        - Store
      operationId: listViewerStoreOrders
      summary: Lista los canjes privados del viewer en una comunidad
      x-convray-runtime-status: active-store-sandbox
      security:
        - viewerSessionCookie: []
      parameters:
        - name: streamer_slug
          in: query
          required: true
          schema:
            type: string
            pattern: ^[a-z0-9][a-z0-9-]{2,39}$
      responses:
        "200":
          description: Hasta cien órdenes propias, sin órdenes de otros viewers o tenants
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoreOrderCollection"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
  /viewer/store/redemptions:
    post:
      tags:
        - Store
      operationId: redeemStoreReward
      summary: Debita puntos, reserva stock y entrega una orden sandbox atómicamente
      x-convray-runtime-status: active-store-sandbox
      description: |
        Requiere sesión viewer, membresía activa, stock, saldo y términos vigentes.
        `Idempotency-Key` devuelve la orden original sin repetir débito ni entrega.
      security:
        - viewerSessionCookie: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 8
            maxLength: 128
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoreRedemptionInput"
      responses:
        "200":
          description: Replay idempotente de una orden existente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoreRedemptionResult"
        "201":
          description: Canje, débito y entrega simulada confirmados por primera vez
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoreRedemptionResult"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          description: Saldo, stock, límite, términos, vigencia o idempotencia incompatibles
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /giveaways:
    get:
      tags:
        - Giveaways
      operationId: listGiveaways
      summary: Lista los sorteos del tenant streamer activo
      x-convray-runtime-status: active-giveaway-foundation
      security:
        - adminBearer: []
      responses:
        "200":
          description: Hasta cien sorteos aislados por tenant
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GiveawayCollection"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Giveaways
      operationId: createGiveaway
      summary: Crea un borrador de sorteo con premio en puntos
      x-convray-runtime-status: active-giveaway-foundation
      description: |
        Crea únicamente un borrador dentro del programa de puntos del tenant
        activo. Esta primera vertical no acepta dinero, criptoactivos, códigos,
        bienes físicos ni proveedores externos.
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GiveawayCreateInput"
      responses:
        "201":
          description: Borrador creado y auditado
          headers:
            Location:
              schema:
                type: string
                pattern: ^/api/v1/giveaways/[0-9a-f-]{36}$
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Giveaway"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /giveaways/{giveawayId}/publication:
    post:
      tags:
        - Giveaways
      operationId: publishGiveaway
      summary: Publica un borrador vigente en el Streamer Site
      x-convray-runtime-status: active-giveaway-foundation
      security:
        - adminBearer: []
      parameters:
        - name: giveawayId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - expected_version
              properties:
                expected_version:
                  type: integer
                  minimum: 1
      responses:
        "200":
          description: Sorteo publicado con versión optimista
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Giveaway"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          description: Versión desactualizada, estado no publicable o vigencia vencida
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /giveaways/{giveawayId}/draw:
    post:
      tags:
        - Giveaways
      operationId: drawGiveaway
      summary: Selecciona al azar los ganadores de un sorteo cerrado
      x-convray-runtime-status: active-giveaway-draw
      description: |
        Solo el owner del tenant puede ejecutar la selección después del cierre.
        Usa el snapshot inmutable de participantes, entropía del sistema operativo
        y HMAC-SHA256 versionado. Repetir la operación devuelve el mismo resultado.
      security:
        - adminBearer: []
      parameters:
        - name: giveawayId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Resultado final o replay idempotente de la selección existente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GiveawayDrawResult"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          description: El sorteo todavía no cerró o el snapshot no está disponible
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /viewer/giveaway-entries:
    get:
      tags:
        - Giveaways
      operationId: listViewerGiveawayEntries
      summary: Lista entradas y resultados privados del viewer en el Streamer Site actual
      x-convray-runtime-status: active-giveaway-entries
      security:
        - viewerSessionCookie: []
      parameters:
        - name: streamer_slug
          in: query
          required: true
          schema:
            type: string
            pattern: ^[a-z0-9][a-z0-9-]{2,39}$
      responses:
        "200":
          description: Hasta cien entradas y resultados propios, sin datos de otros viewers o tenants
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GiveawayEntryCollection"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
    post:
      tags:
        - Giveaways
      operationId: enterGiveaway
      summary: Registra una entrada y debita puntos de forma atómica
      x-convray-runtime-status: active-giveaway-entries
      description: |
        Exige sesión viewer, membresía activa, período abierto, bases vigentes,
        confirmación de edad. `Idempotency-Key` se aplica por sorteo y
        viewer; un replay devuelve la entrada original sin un segundo débito.
      security:
        - viewerSessionCookie: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 8
            maxLength: 128
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GiveawayEntryCreateInput"
      responses:
        "200":
          description: Replay idempotente de una entrada existente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GiveawayEntryResult"
        "201":
          description: Entrada y débito confirmados por primera vez
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GiveawayEntryResult"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          description: Período cerrado, saldo insuficiente, límite, bases cambiadas o conflicto de idempotencia
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/overlay-settings:
    patch:
      tags:
        - Community
        - Overlay
      operationId: updateCommunityOverlaySettings
      summary: Elige qué actividad Convray muestra el overlay del tenant activo
      x-convray-runtime-status: active-overlay-v1
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OverlaySettingsUpdate"
      responses:
        "200":
          description: Preferencias guardadas con versión optimista
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OverlaySettings"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "409":
          description: La versión de configuración quedó desactualizada
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/point-rules/{ruleID}:
    patch:
      tags:
        - Community
      operationId: updateCommunityPointRule
      summary: Ajusta y activa una regla de acumulación de puntos del tenant activo
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      parameters:
        - name: ruleID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PointRuleUpdate"
      responses:
        "200":
          description: Regla guardada con versión optimista
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PointRule"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          description: La regla no existe dentro de esta comunidad
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "409":
          description: La versión quedó desactualizada o la fuente no puede entregar evidencia
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /go/{referralToken}:
    get:
      tags:
        - Community
      operationId: redirectCasinoReferral
      summary: Cuenta el click y manda al casino del streamer
      description: >-
        Link público del Streamer Site. El click se registra en la misma transacción
        que emite el redirect: si no se pudo contar, la persona no sale, porque un
        contador que a veces no suma no sirve para negociar con un casino.


        El visitante recibe una cookie con material aleatorio; lo que se guarda es su
        HMAC, no la cookie, y caduca a los 90 días. Sirve para separar clicks de
        personas, no para identificar a nadie.
      x-convray-runtime-status: active-community-v1
      security: []
      parameters:
        - name: referralToken
          in: path
          required: true
          schema:
            type: string
            pattern: "^[A-Za-z0-9_-]{43}$"
      responses:
        "302":
          description: Click contado y redirect emitido
          headers:
            Location:
              schema:
                type: string
                format: uri
            Set-Cookie:
              description: Sólo en el primer click del visitante.
              schema:
                type: string
        "404":
          description: No hay un casino activo detrás de este link
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "503":
          description: No se pudo confirmar un redirect seguro
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
  /casino-ingest/wagers:
    post:
      tags:
        - Community
      operationId: ingestCasinoWagers
      summary: Recibe un lote de wager firmado por un casino conectado
      description: >-
        No lleva sesión: el casino se autentica firmando el cuerpo. La firma es
        `HMAC-SHA256(secreto, "<connection_id>.<timestamp>.<cuerpo crudo>")` en
        hexadecimal, y el sello de tiempo debe caer dentro de los cinco minutos
        para que una request capturada no se pueda reenviar.


        Los eventos se leen del cuerpo firmado, así que alterar el JSON después de
        firmar invalida el lote entero. La idempotencia es por `external_event_id`
        dentro de la conexión: reenviar el mismo lote no vuelve a sumar y responde
        cuántos ya estaban.


        Un lote se acepta entero o se rechaza entero.
      x-convray-runtime-status: active-community-v1
      security: []
      parameters:
        - name: Convray-Connection-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
        - name: Convray-Timestamp
          in: header
          required: true
          description: Momento de la firma en RFC3339.
          schema:
            type: string
            format: date-time
        - name: Convray-Signature
          in: header
          required: true
          description: HMAC-SHA256 en hexadecimal.
          schema:
            type: string
            pattern: "^[0-9a-fA-F]{64}$"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoWagerBatch"
      responses:
        "202":
          description: Lote aceptado; informa cuántos eventos eran nuevos y cuántos ya estaban
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoWagerIngestResult"
        "401":
          description: La firma, el sello de tiempo o la conexión no son válidos
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "413":
          description: El lote supera el tamaño admitido
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/casino-connections:
    get:
      tags:
        - Community
      operationId: listCommunityCasinoConnections
      summary: Lista los casinos externos conectados por el tenant activo
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      responses:
        "200":
          description: Conexiones con su origen, estado y pista del secreto
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - connections
                properties:
                  connections:
                    type: array
                    items:
                      $ref: "#/components/schemas/CasinoConnection"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Community
      operationId: createCommunityCasinoConnection
      summary: Conecta un casino externo y emite su secreto de ingesta
      description: >-
        El secreto en claro viaja una única vez, en esta respuesta. Después queda
        cifrado en reposo y ni el panel puede volver a leerlo.
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoConnectionCreate"
      responses:
        "201":
          description: Conexión creada, con el secreto de ingesta en claro
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreatedCasinoConnection"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/casino-connections/{connectionID}:
    patch:
      tags:
        - Community
      operationId: updateCommunityCasinoConnection
      summary: Renombra o pausa una conexión de casino
      description: >-
        La identidad y el secreto de la conexión son inmutables: apuntar a otro casino
        es crear una conexión nueva.
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      parameters:
        - name: connectionID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoConnectionUpdate"
      responses:
        "200":
          description: Conexión actualizada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoConnection"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          description: La conexión no existe dentro de esta comunidad
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "409":
          description: La versión quedó desactualizada
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/casino-metrics:
    get:
      tags:
        - Community
      operationId: listCommunityCasinoMetrics
      summary: Clicks, visitantes y volumen apostado por casino conectado
      description: >-
        Lo que el streamer le muestra a un casino: cuánta gente le mandó y cuánto
        apostó esa gente.


        El único total agregado es el de clicks. El volumen apostado se liquida en la
        moneda de cada conexión, y el pseudónimo del visitante está atado a su
        conexión para que no se pueda cruzar entre casinos, así que ni el importe ni
        los visitantes se suman entre conexiones sin perder sentido.
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      parameters:
        - name: window_days
          in: query
          required: false
          description: Ventana en días. Cualquier otro valor cae en 30.
          schema:
            type: integer
            enum: [7, 30, 90]
            default: 30
      responses:
        "200":
          description: Métricas por conexión dentro de la ventana pedida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoMetrics"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/casino-connections/{connectionID}/referral:
    put:
      tags:
        - Community
      operationId: configureCommunityCasinoReferral
      summary: Configura el link medido hacia un casino conectado
      description: >-
        El destino no lo inventa Convray: es el link de afiliado que el streamer ya
        tiene con ese casino. Convray lo envuelve en un token propio, cuenta el
        click y redirige.


        El enlace se valida como HTTPS al puerto estándar y se comprueba que no
        resuelva a una red interna. El token se conserva entre cambios de destino
        para no romper los links ya publicados.
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      parameters:
        - name: connectionID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CasinoReferralConfigure"
      responses:
        "200":
          description: Conexión con su link medido configurado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CasinoConnection"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          description: La conexión no existe dentro de esta comunidad
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "409":
          description: La versión quedó desactualizada
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/wager-races:
    get:
      tags:
        - Community
      operationId: listCommunityWagerRaces
      summary: Lista las carreras por volumen apostado del tenant activo
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      responses:
        "200":
          description: Carreras con premios y clasificación congelada si ya cerraron
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - races
                properties:
                  races:
                    type: array
                    items:
                      $ref: "#/components/schemas/WagerRace"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Community
      operationId: createCommunityWagerRace
      summary: Crea una carrera en borrador con su ventana y su tabla de premios
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WagerRaceCreate"
      responses:
        "201":
          description: Carrera creada en borrador
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WagerRace"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/wager-races/{raceID}:
    patch:
      tags:
        - Community
      operationId: updateCommunityWagerRace
      summary: Ajusta o activa una carrera que todavía no cerró
      description: >-
        El cierre no se pide desde acá: Convray cierra la carrera al vencer su ventana,
        congela la clasificación y acredita los premios. Una carrera cerrada es inmutable.
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      parameters:
        - name: raceID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WagerRaceUpdate"
      responses:
        "200":
          description: Carrera guardada con versión optimista
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WagerRace"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          description: La carrera no existe dentro de esta comunidad
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "409":
          description: La versión quedó desactualizada o la carrera ya cerró
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/chat-code-settings:
    patch:
      tags:
        - Community
      operationId: updateCommunityChatCodeSettings
      summary: Habilita o acota la emisión de códigos desde el chat del canal
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatCodeSettingsUpdate"
      responses:
        "200":
          description: Interruptor y techo guardados
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatCodeSettings"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/redemption-codes:
    get:
      tags:
        - Community
      operationId: listCommunityRedemptionCodes
      summary: Lista los códigos de comunidad del tenant activo
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      responses:
        "200":
          description: Códigos emitidos por este streamer
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - codes
                properties:
                  codes:
                    type: array
                    items:
                      $ref: "#/components/schemas/RedemptionCode"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
    post:
      tags:
        - Community
      operationId: createCommunityRedemptionCode
      summary: Emite un código canjeable una sola vez por viewer
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RedemptionCodeCreate"
      responses:
        "201":
          description: Código emitido
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RedemptionCode"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "409":
          description: Ya existe un código con ese texto en la comunidad
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /community/redemption-codes/{codeID}:
    patch:
      tags:
        - Community
      operationId: updateCommunityRedemptionCode
      summary: Ajusta la vigencia, el cupo o el estado de un código
      x-convray-runtime-status: active-community-v1
      security:
        - adminBearer: []
      parameters:
        - name: codeID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RedemptionCodeUpdate"
      responses:
        "200":
          description: Código guardado con versión optimista
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RedemptionCode"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          description: El código no existe dentro de esta comunidad
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "409":
          description: La versión quedó desactualizada
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /viewer/redemption-codes/redemptions:
    post:
      tags:
        - Community
      operationId: redeemCommunityCode
      summary: Canjea un código de comunidad con la sesión de viewer
      description: >-
        El canje es idempotente por viewer y por código. Reintentar devuelve 200 con
        replayed en true y el mismo saldo, sin acreditar de nuevo.
      x-convray-runtime-status: active-community-v1
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RedemptionCodeRedeem"
      responses:
        "201":
          description: Código acreditado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RedemptionCodeRedeemResult"
        "200":
          description: El código ya había sido canjeado por este viewer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RedemptionCodeRedeemResult"
        "401":
          description: La sesión de viewer no existe, expiró o fue revocada
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "403":
          $ref: "#/components/responses/CSRFRejected"
        "404":
          description: El código no existe, está deshabilitado, venció o agotó su cupo
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "409":
          description: El viewer no está suscrito a la comunidad
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /public/streamer-sites/{slug}:
    get:
      tags:
        - Community
        - Giveaways
        - Store
        - MiniGames
      operationId: getPublicStreamerSite
      summary: Resuelve el Streamer Site publicado, sus códigos de slots, sorteos y catálogo de Tienda
      x-convray-runtime-status: active-streamer-site-v1
      security: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
      responses:
        "200":
          description: Snapshot público sin IDs tenant, membresías, secretos ni borradores
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicStreamerSite"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
  /public/streamer-sites/{slug}/events:
    post:
      tags:
        - Analytics
      operationId: recordPublicStreamerSiteEvent
      summary: Registra una impresión o interacción first-party idempotente
      description: |
        Solo acepta eventos desde el Origin web autorizado. El servidor emite
        una cookie HttpOnly aleatoria y persiste exclusivamente un digest
        tenant-scoped; no persiste IP, User-Agent, referrer ni el valor crudo
        de la cookie. `event_id` es la clave idempotente dentro del tenant.
      x-convray-runtime-status: active-streamer-site-analytics-v1
      security: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StreamerSiteEventInput"
            examples:
              pageView:
                value:
                  event_id: 11111111-1111-4111-8111-111111111111
                  event_type: page_view
                  surface: home
              click:
                value:
                  event_id: 22222222-2222-4222-8222-222222222222
                  event_type: click
                  surface: home
                  target_type: social
                  target_key: kick
      responses:
        "202":
          description: Evento aceptado o replay idempotente confirmado
          headers:
            Set-Cookie:
              $ref: "#/components/headers/AnalyticsVisitorCookie"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StreamerSiteEventReceipt"
        "403":
          description: Origin no autorizado
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/ProblemBase"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "409":
          $ref: "#/components/responses/VersionConflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
  /streamer-site/analytics:
    get:
      tags:
        - Analytics
      operationId: getStreamerSiteAnalytics
      summary: Obtiene métricas first-party y actividad autoritativa del tenant streamer activo
      description: |
        Los plays se agregan desde `mini_game_rounds` settled. Entradas de
        sorteos y canjes se agregan desde sus ledgers operativos; nunca desde
        clicks reportados por el navegador. Una tasa sin denominador es null.
      x-convray-runtime-status: active-streamer-site-analytics-v1
      security:
        - adminBearer: []
      parameters:
        - name: period
          in: query
          required: false
          schema:
            type: string
            enum:
              - 7d
              - 30d
              - month
            default: 30d
      responses:
        "200":
          description: Reporte aislado al tenant derivado de la sesión
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StreamerSiteAnalytics"
        "401":
          $ref: "#/components/responses/InvalidSession"
        "403":
          $ref: "#/components/responses/PermissionDenied"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/AuditUnavailable"
  /public/streamer-sites/{slug}/overlay-events:
    get:
      tags:
        - Overlay
      operationId: getPublicConvrayOverlayEvents
      summary: Devuelve actividad pública y sanitizada de Convray para OBS
      x-convray-runtime-status: active-overlay-v1
      security: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        - name: since
          in: query
          required: false
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Hasta 20 eventos habilitados por el streamer; nunca incluye IDs privados ni payloads de proveedores
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConvrayOverlayFeed"
        "404":
          $ref: "#/components/responses/ResourceNotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
components:
  securitySchemes:
    adminBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      x-convray-required-audience: convray-admin
      description: |
        Access token administrativo de corta duración. Debe validar firma,
        issuer, `aud=convray-admin`, expiración y estado de la sesión server-side.
    adminRefreshCookie:
      type: apiKey
      in: cookie
      name: convray_refresh
      description: |
        Credencial opaca, rotativa y revocable de una sesión administrativa;
        nunca se expone a JavaScript.
    viewerSessionCookie:
      type: apiKey
      in: cookie
      name: convray_viewer_session
      description: Sesión viewer opaca, HttpOnly, revocable y separada de la sesión administrativa.
  headers:
    NoStore:
      description: La respuesta contiene contexto de identidad y no puede almacenarse en cachés.
      schema:
        type: string
        const: no-store
    PrivateNoStore:
      description: La respuesta autenticada contiene contexto sensible y no puede almacenarse en cachés compartidas ni privadas.
      schema:
        type: string
        const: private, no-store
    RequestId:
      description: Identificador de correlación aceptado o generado por el servidor.
      schema:
        type: string
        minLength: 1
        maxLength: 128
    RefreshCookie:
      description: convray_refresh opaca; HttpOnly; Secure; SameSite=Lax; Path=/api/v1/auth/sessions. El entorno local puede omitir Secure.
      schema:
        type: string
    ExpiredRefreshCookie:
      description: Expira convray_refresh con Max-Age=0 y el mismo Path/SameSite usado al crearla.
      schema:
        type: string
    AnalyticsVisitorCookie:
      description: convray_visitor aleatoria; HttpOnly; Secure en entornos no locales; SameSite=Lax; Path=/api/v1/public/streamer-sites/. El valor crudo no se persiste.
      schema:
        type: string
    TrackingVisitorCookie:
      description: Cookie first-party opcional con 128 bits aleatorios; HttpOnly; Secure; SameSite=Lax; Path=/r. El valor crudo no se persiste y su sidecar pseudónimo tiene retención máxima de 90 días.
      schema:
        type: string
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 8
        maxLength: 128
    RelationshipDealId:
      name: deal_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    RelationshipLimit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    RelationshipCursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
        format: uuid
    RelationshipStatus:
      name: status
      in: query
      required: false
      schema:
        type: string
        enum:
          - submitted
          - provisioning
          - active
          - rejected
          - cancelled
          - suspended
          - ended
    TrackingLimit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    TrackingCursor:
      name: cursor
      in: query
      required: false
      description: UUID cursor emitido por la página APC-4 anterior.
      schema:
        type: string
        format: uuid
    TrackingDestinationId:
      name: destination_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    TrackingDestinationStatus:
      name: status
      in: query
      required: false
      schema:
        type: string
        enum:
          - draft
          - published
          - paused
          - archived
    TrackingProgramId:
      name: program_id
      in: query
      required: false
      schema:
        type: string
        format: uuid
    TrackingLinkId:
      name: link_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    TrackingDealIdQuery:
      name: deal_id
      in: query
      required: false
      schema:
        type: string
        format: uuid
    TrackingLinkStatus:
      name: status
      in: query
      required: false
      schema:
        type: string
        enum:
          - active
          - inactive
          - revoked
    OpaqueTrackingToken:
      name: opaque_token
      in: path
      required: true
      description: Identificador público aleatorio sin autoridad ni datos internos.
      schema:
        type: string
        minLength: 43
        maxLength: 43
        pattern: ^[A-Za-z0-9_-]{43}$
    MiniGameKey:
      name: game
      in: path
      required: true
      schema:
        type: string
        enum:
          - flip
          - dice
          - plinko
          - roulette
    MiniGameKeyQuery:
      name: game
      in: query
      required: false
      schema:
        type: string
        enum:
          - flip
          - dice
          - plinko
          - roulette
        default: flip
    MiniGameConfigurationId:
      name: configurationId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    MiniGameRoundId:
      name: roundId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    StreamerSlugQuery:
      name: streamer_slug
      in: query
      required: true
      schema:
        type: string
        pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
  schemas:
    PlinkoPayoutTable:
      type: array
      description: Numeradores de premio bruto para buckets 0..8; el denominador fijo es 50.
      minItems: 9
      maxItems: 9
      prefixItems:
        - type: integer
          const: 500
        - type: integer
          const: 152
        - type: integer
          const: 75
        - type: integer
          const: 40
        - type: integer
          const: 8
        - type: integer
          const: 40
        - type: integer
          const: 75
        - type: integer
          const: 152
        - type: integer
          const: 500
      items: false
    MiniGameConfiguration:
      type: object
      additionalProperties: false
      required:
        - id
        - game
        - status
        - environment
        - rules_version
        - configuration_version
        - min_points_cost
        - max_points_cost
        - points_increment
        - award_numerator
        - award_denominator
        - payout_model
        - payout_table
        - terms_version
        - terms_text
        - supersedes_configuration_id
        - revision
        - published_at
        - suspended_at
        - created_at
      properties:
        id:
          type: string
          format: uuid
        game:
          type: string
          enum:
            - flip
            - dice
            - plinko
            - roulette
        status:
          type: string
          enum:
            - draft
            - active
            - suspended
            - superseded
        environment:
          type: string
          const: sandbox
        rules_version:
          type: integer
          const: 1
        configuration_version:
          type: integer
          minimum: 1
        min_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        max_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        points_increment:
          type: integer
          const: 50
        award_numerator:
          type: integer
          const: 99
        award_denominator:
          type: integer
          const: 50
        payout_model:
          type: string
          enum:
            - fixed
            - plinko-buckets-v1
            - roulette-european-v1
        payout_table:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/PlinkoPayoutTable"
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        terms_text:
          type: string
          minLength: 20
          maxLength: 10000
        supersedes_configuration_id:
          type:
            - string
            - "null"
          format: uuid
        revision:
          type: integer
          minimum: 1
        published_at:
          type:
            - string
            - "null"
          format: date-time
        suspended_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
    MiniGameConfigurationCreateInput:
      type: object
      additionalProperties: false
      required:
        - min_points_cost
        - max_points_cost
        - terms_version
        - terms_text
      properties:
        min_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        max_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        terms_text:
          type: string
          minLength: 20
          maxLength: 10000
        supersedes_configuration_id:
          type:
            - string
            - "null"
          format: uuid
    MiniGameConfigurationPublicationInput:
      type: object
      additionalProperties: false
      required:
        - expected_revision
        - non_monetary_confirmed
      properties:
        expected_revision:
          type: integer
          minimum: 1
        non_monetary_confirmed:
          type: boolean
          const: true
    MiniGameConfigurationSuspensionInput:
      type: object
      additionalProperties: false
      required:
        - expected_revision
        - reason_code
      properties:
        expected_revision:
          type: integer
          minimum: 1
        reason_code:
          type: string
          const: owner_paused
    MiniGameOverview:
      type: object
      additionalProperties: false
      required:
        - program_symbol
        - configurations
      properties:
        program_symbol:
          type: string
          pattern: ^[A-Z0-9]{2,10}$
        configurations:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/MiniGameConfiguration"
    ViewerMiniGameCatalog:
      type: object
      additionalProperties: false
      required:
        - game
        - available
        - sandbox
        - program_symbol
        - balance_points
      properties:
        game:
          type: string
          enum:
            - flip
            - dice
            - plinko
            - roulette
        available:
          type: boolean
        sandbox:
          type: boolean
          const: true
        program_symbol:
          type: string
          pattern: ^[A-Z0-9]{2,10}$
        balance_points:
          type: integer
          minimum: 0
        configuration:
          $ref: "#/components/schemas/MiniGameConfiguration"
    MiniGameRoundPrepareInput:
      type: object
      additionalProperties: false
      required:
        - streamer_slug
      properties:
        streamer_slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
    MiniGamePreparedRound:
      type: object
      additionalProperties: false
      required:
        - id
        - game
        - state
        - environment
        - rules_version
        - configuration_id
        - configuration_version
        - commitment
        - min_points_cost
        - max_points_cost
        - points_increment
        - award_numerator
        - award_denominator
        - payout_model
        - payout_table
        - terms_version
        - expires_at
      properties:
        id:
          type: string
          format: uuid
        game:
          type: string
          enum:
            - flip
            - dice
            - plinko
            - roulette
        state:
          type: string
          const: prepared
        environment:
          type: string
          const: sandbox
        rules_version:
          type: integer
          const: 1
        configuration_id:
          type: string
          format: uuid
        configuration_version:
          type: integer
          minimum: 1
        commitment:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
        min_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        max_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        points_increment:
          type: integer
          const: 50
        award_numerator:
          type: integer
          const: 99
        award_denominator:
          type: integer
          const: 50
        payout_model:
          type: string
          enum:
            - fixed
            - plinko-buckets-v1
            - roulette-european-v1
        payout_table:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/PlinkoPayoutTable"
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        expires_at:
          type: string
          format: date-time
    MiniGameRoundPrepareResult:
      type: object
      additionalProperties: false
      required:
        - round
        - replayed
      properties:
        round:
          $ref: "#/components/schemas/MiniGamePreparedRound"
        replayed:
          type: boolean
    MiniGameRoundPlayInput:
      type: object
      additionalProperties: false
      required:
        - streamer_slug
        - selection
        - points_cost
        - client_seed
        - accepted_terms
        - accepted_terms_version
      properties:
        streamer_slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        selection:
          type: string
          enum:
            - heads
            - tails
            - under
            - over
            - drop
        points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        client_seed:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
        accepted_terms:
          type: boolean
          const: true
        accepted_terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
    MiniGameProofContext:
      type: object
      additionalProperties: false
      required:
        - round_id
        - tenant_id
        - viewer_id
        - configuration_id
        - rules_version
        - configuration_version
      properties:
        round_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        viewer_id:
          type: string
          format: uuid
        configuration_id:
          type: string
          format: uuid
        rules_version:
          type: integer
          const: 1
        configuration_version:
          type: integer
          minimum: 1
    MiniGameProof:
      type: object
      additionalProperties: false
      required:
        - algorithm
        - commitment
        - server_seed
        - client_seed
        - digest
        - context
        - verified
      properties:
        algorithm:
          type: string
          enum:
            - flip-v1
            - dice-v1
            - plinko-v1
            - roulette-v1
        commitment:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
        server_seed:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
        client_seed:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
        digest:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
        context:
          $ref: "#/components/schemas/MiniGameProofContext"
        verified:
          type: boolean
          const: true
    MiniGameSettledRound:
      type: object
      additionalProperties: false
      required:
        - id
        - game
        - state
        - environment
        - selection
        - outcome
        - won
        - points_cost
        - points_awarded
        - balance_points
        - proof
        - settled_at
      properties:
        id:
          type: string
          format: uuid
        game:
          type: string
          enum:
            - flip
            - dice
            - plinko
            - roulette
        state:
          type: string
          const: settled
        environment:
          type: string
          const: sandbox
        selection:
          type: string
          enum:
            - heads
            - tails
            - under
            - over
            - drop
        outcome:
          oneOf:
            - type: string
              enum:
                - heads
                - tails
            - type: string
              pattern: ^(0|[1-9][0-9]{0,3})$
        won:
          type: boolean
        points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        points_awarded:
          type: integer
          minimum: 0
          maximum: 50000
        balance_points:
          type: integer
          minimum: 0
        proof:
          $ref: "#/components/schemas/MiniGameProof"
        settled_at:
          type: string
          format: date-time
    MiniGameRoundPlayResult:
      type: object
      additionalProperties: false
      required:
        - round
        - replayed
      properties:
        round:
          $ref: "#/components/schemas/MiniGameSettledRound"
        replayed:
          type: boolean
    StoreReward:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - description
        - image_url
        - points_cost
        - stock_total
        - stock_remaining
        - redemption_count
        - max_per_viewer
        - eligibility_type
        - fulfillment_type
        - sandbox
        - terms_version
        - terms_text
        - valid_from
        - valid_until
        - status
        - version
        - published_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          minLength: 3
          maxLength: 100
        description:
          type: string
          minLength: 3
          maxLength: 1000
        image_url:
          type:
            - string
            - "null"
          format: uri
          pattern: ^https://
        points_cost:
          type: integer
          minimum: 1
          maximum: 1000000000
        stock_total:
          type: integer
          minimum: 1
          maximum: 1000000
        stock_remaining:
          type: integer
          minimum: 0
          maximum: 1000000
        redemption_count:
          type: integer
          minimum: 0
        max_per_viewer:
          type: integer
          minimum: 1
          maximum: 1000
        eligibility_type:
          type: string
          const: active_member
        fulfillment_type:
          type: string
          const: sandbox
        sandbox:
          type: boolean
          const: true
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        terms_text:
          type: string
          minLength: 20
          maxLength: 10000
        valid_from:
          type: string
          format: date-time
        valid_until:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - draft
            - published
            - archived
        version:
          type: integer
          minimum: 1
        published_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    StoreRewardCreateInput:
      type: object
      additionalProperties: false
      required:
        - name
        - description
        - points_cost
        - stock_total
        - max_per_viewer
        - terms_version
        - terms_text
        - valid_from
        - valid_until
      properties:
        name:
          type: string
          minLength: 3
          maxLength: 100
        description:
          type: string
          minLength: 3
          maxLength: 1000
        image_url:
          type:
            - string
            - "null"
          format: uri
          pattern: ^https://
        points_cost:
          type: integer
          minimum: 1
          maximum: 1000000000
        stock_total:
          type: integer
          minimum: 1
          maximum: 1000000
        max_per_viewer:
          type: integer
          minimum: 1
          maximum: 1000
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        terms_text:
          type: string
          minLength: 20
          maxLength: 10000
        valid_from:
          type: string
          format: date-time
        valid_until:
          type: string
          format: date-time
    StoreOrder:
      type: object
      additionalProperties: false
      required:
        - id
        - reward_id
        - reward_name
        - recipient_alias
        - quantity
        - points_debited
        - balance_after
        - status
        - fulfillment_type
        - sandbox
        - accepted_terms_version
        - territory_scope
        - provider
        - provider_reference
        - version
        - created_at
        - fulfilled_at
      properties:
        id:
          type: string
          format: uuid
        reward_id:
          type: string
          format: uuid
        reward_name:
          type: string
          minLength: 3
          maxLength: 100
        recipient_alias:
          type: string
          minLength: 2
          maxLength: 48
        quantity:
          type: integer
          const: 1
        points_debited:
          type: integer
          minimum: 0
          maximum: 1000000000
        balance_after:
          type: integer
          minimum: 0
        status:
          type: string
          enum:
            - pending
            - reserved
            - fulfilled
            - failed
            - cancelled
            - refunded
        fulfillment_type:
          type: string
          const: sandbox
        sandbox:
          type: boolean
          const: true
        accepted_terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        territory_scope:
          type: string
          const: sandbox
        provider:
          oneOf:
            - type: string
              const: convray_sandbox
            - type: "null"
        provider_reference:
          type:
            - string
            - "null"
          maxLength: 200
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        fulfilled_at:
          type:
            - string
            - "null"
          format: date-time
    StoreOverview:
      type: object
      additionalProperties: false
      required:
        - program_symbol
        - rewards
        - orders
      properties:
        program_symbol:
          type: string
          pattern: ^[A-Z0-9]{2,10}$
        rewards:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/StoreReward"
        orders:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/StoreOrder"
    StoreOrderCollection:
      type: object
      additionalProperties: false
      required:
        - orders
      properties:
        orders:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/StoreOrder"
    StoreRedemptionInput:
      type: object
      additionalProperties: false
      required:
        - streamer_slug
        - reward_id
        - accepted_terms
        - accepted_terms_version
      properties:
        streamer_slug:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{2,39}$
        reward_id:
          type: string
          format: uuid
        accepted_terms:
          type: boolean
          const: true
        accepted_terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
    StoreRedemptionResult:
      type: object
      additionalProperties: false
      required:
        - order
        - replayed
      properties:
        order:
          $ref: "#/components/schemas/StoreOrder"
        replayed:
          type: boolean
    Giveaway:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - description
        - prize_type
        - prize_description
        - prize_points_amount
        - terms_text
        - terms_url
        - starts_at
        - ends_at
        - winners_count
        - entry_points_cost
        - max_entries_per_viewer
        - terms_version
        - status
        - version
        - published_at
        - created_at
        - updated_at
        - results_finalized
        - drawn_at
        - winners
        - metrics
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          minLength: 3
          maxLength: 100
        description:
          type: string
          minLength: 3
          maxLength: 1000
        prize_type:
          type: string
          const: points
        prize_description:
          type: string
          minLength: 3
          maxLength: 160
        prize_points_amount:
          type: integer
          minimum: 1
          maximum: 1000000000
        terms_text:
          type:
            - string
            - "null"
          minLength: 20
          maxLength: 10000
        terms_url:
          type:
            - string
            - "null"
          format: uri
          pattern: ^https://
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        winners_count:
          type: integer
          minimum: 1
          maximum: 100
        entry_points_cost:
          type: integer
          minimum: 0
          maximum: 1000000000
        max_entries_per_viewer:
          type: integer
          minimum: 1
          maximum: 1000
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        status:
          type: string
          enum:
            - draft
            - published
            - closed
            - cancelled
        version:
          type: integer
          minimum: 1
        published_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        results_finalized:
          type: boolean
        drawn_at:
          type:
            - string
            - "null"
          format: date-time
        winners:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/GiveawayWinner"
        metrics:
          $ref: "#/components/schemas/GiveawayMetrics"
    GiveawayMetrics:
      type: object
      additionalProperties: false
      required:
        - entries_count
        - participants_count
        - winners_selected
        - entry_points_collected
        - prize_points_awarded
      properties:
        entries_count:
          type: integer
          minimum: 0
        participants_count:
          type: integer
          minimum: 0
        winners_selected:
          type: integer
          minimum: 0
        entry_points_collected:
          type: integer
          minimum: 0
        prize_points_awarded:
          type: integer
          minimum: 0
    GiveawayCreateInput:
      type: object
      additionalProperties: false
      required:
        - title
        - description
        - prize_description
        - prize_points_amount
        - starts_at
        - ends_at
        - winners_count
        - entry_points_cost
        - max_entries_per_viewer
        - terms_version
      anyOf:
        - required:
            - terms_text
        - required:
            - terms_url
      properties:
        title:
          type: string
          minLength: 3
          maxLength: 100
        description:
          type: string
          minLength: 3
          maxLength: 1000
        prize_description:
          type: string
          minLength: 3
          maxLength: 160
        prize_points_amount:
          type: integer
          minimum: 1
          maximum: 1000000000
        terms_text:
          type:
            - string
            - "null"
          minLength: 20
          maxLength: 10000
        terms_url:
          type:
            - string
            - "null"
          format: uri
          pattern: ^https://
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        winners_count:
          type: integer
          minimum: 1
          maximum: 100
        entry_points_cost:
          type: integer
          minimum: 0
          maximum: 1000000000
          default: 0
        max_entries_per_viewer:
          type: integer
          minimum: 1
          maximum: 1000
          default: 1
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
          default: v1
    GiveawayCollection:
      type: object
      additionalProperties: false
      required:
        - giveaways
      properties:
        wager_races:
          type: array
          maxItems: 2
          description: La carrera vigente y la última cerrada.
          items:
            $ref: "#/components/schemas/PublicWagerRace"
        giveaways:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/Giveaway"
    GiveawayWinner:
      type: object
      additionalProperties: false
      required:
        - rank
        - public_alias
        - public_reference
      properties:
        rank:
          type: integer
          minimum: 1
          maximum: 100
        public_alias:
          type: string
          minLength: 2
          maxLength: 48
        public_reference:
          type: string
          pattern: ^[A-Z0-9]{8}$
    GiveawayDrawResult:
      type: object
      additionalProperties: false
      required:
        - giveaway_id
        - results_finalized
        - replayed
        - drawn_at
        - winners
      properties:
        giveaway_id:
          type: string
          format: uuid
        results_finalized:
          type: boolean
          const: true
        replayed:
          type: boolean
        drawn_at:
          type: string
          format: date-time
        winners:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/GiveawayWinner"
    PublicGiveaway:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - description
        - prize_description
        - prize_points_amount
        - terms_text
        - terms_url
        - starts_at
        - ends_at
        - winners_count
        - entry_points_cost
        - max_entries_per_viewer
        - terms_version
        - status
        - closed_at
        - results_finalized
        - winners
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          minLength: 3
          maxLength: 100
        description:
          type: string
          minLength: 3
          maxLength: 1000
        prize_description:
          type: string
          minLength: 3
          maxLength: 160
        prize_points_amount:
          type: integer
          minimum: 1
          maximum: 1000000000
        terms_text:
          type:
            - string
            - "null"
          minLength: 20
          maxLength: 10000
        terms_url:
          type:
            - string
            - "null"
          format: uri
          pattern: ^https://
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        winners_count:
          type: integer
          minimum: 1
          maximum: 100
        entry_points_cost:
          type: integer
          minimum: 0
          maximum: 1000000000
        max_entries_per_viewer:
          type: integer
          minimum: 1
          maximum: 1000
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        status:
          type: string
          enum:
            - scheduled
            - active
            - closed
        closed_at:
          type:
            - string
            - "null"
          format: date-time
        results_finalized:
          type: boolean
        winners:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/GiveawayWinner"
    GiveawayEntry:
      type: object
      additionalProperties: false
      required:
        - id
        - giveaway_id
        - entry_number
        - points_debited
        - balance_after
        - status
        - terms_version
        - territory_code
        - created_at
      properties:
        id:
          type: string
          format: uuid
        giveaway_id:
          type: string
          format: uuid
        entry_number:
          type: integer
          minimum: 1
        points_debited:
          type: integer
          minimum: 0
          maximum: 1000000000
        balance_after:
          type: integer
          minimum: 0
        status:
          type: string
          enum:
            - registered
            - eligible
            - excluded
            - refunded
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        territory_code:
          type: string
          pattern: ^[A-Z]{2}$
        created_at:
          type: string
          format: date-time
    GiveawayEntryCreateInput:
      type: object
      additionalProperties: false
      required:
        - streamer_slug
        - giveaway_id
        - accepted_terms
        - accepted_terms_version
        - age_confirmed
      properties:
        streamer_slug:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{2,39}$
        giveaway_id:
          type: string
          format: uuid
        accepted_terms:
          type: boolean
          const: true
        accepted_terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        age_confirmed:
          type: boolean
          const: true
    GiveawayEntryResult:
      type: object
      additionalProperties: false
      required:
        - entry
        - replayed
      properties:
        entry:
          $ref: "#/components/schemas/GiveawayEntry"
        replayed:
          type: boolean
    GiveawayEntryCollection:
      type: object
      additionalProperties: false
      required:
        - entries
        - results
      properties:
        entries:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/GiveawayEntry"
        results:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/ViewerGiveawayResult"
    ViewerGiveawayResult:
      type: object
      additionalProperties: false
      required:
        - giveaway_id
        - status
        - winner_rank
        - prize_points_awarded
      properties:
        giveaway_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - pending
            - won
            - not_selected
        winner_rank:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 100
        prize_points_awarded:
          type: integer
          minimum: 0
          maximum: 1000000000
    PublicCommunity:
      type: object
      additionalProperties: false
      required:
        - program_name
        - program_symbol
        - status
        - viewer_auth_providers
      properties:
        program_name:
          type: string
          minLength: 2
          maxLength: 40
        program_symbol:
          type: string
          pattern: ^[A-Z0-9]{2,10}$
        status:
          type: string
          enum:
            - active
            - paused
        viewer_auth_providers:
          type: array
          uniqueItems: true
          minItems: 1
          maxItems: 2
          items:
            type: string
            enum:
              - kick
              - discord
    PublicSocialLinks:
      type: object
      additionalProperties: false
      required:
        - kick
        - discord
        - youtube
        - twitch
        - instagram
        - x
      properties:
        kick:
          type:
            - string
            - "null"
          format: uri
        discord:
          type:
            - string
            - "null"
          format: uri
        youtube:
          type:
            - string
            - "null"
          format: uri
        twitch:
          type:
            - string
            - "null"
          format: uri
        instagram:
          type:
            - string
            - "null"
          format: uri
        x:
          type:
            - string
            - "null"
          format: uri
    PublicYouTubeVideo:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - channel_title
        - thumbnail_url
        - url
        - published_at
        - duration_seconds
      properties:
        id:
          type: string
          minLength: 6
          maxLength: 32
        title:
          type: string
          minLength: 1
          maxLength: 500
        channel_title:
          type: string
          minLength: 1
          maxLength: 200
        thumbnail_url:
          type: string
          format: uri
          pattern: ^https://
        url:
          type: string
          format: uri
          pattern: ^https://www\.youtube\.com/watch
        published_at:
          type: string
          format: date-time
        duration_seconds:
          type: integer
          minimum: 0
    PublicYouTubeCatalog:
      type: object
      additionalProperties: false
      required:
        - channel_id
        - channel_title
        - channel_url
        - last_synced_at
        - videos
        - shorts
      properties:
        channel_id:
          type: string
          minLength: 1
          maxLength: 128
        channel_title:
          type: string
          minLength: 1
          maxLength: 200
        channel_url:
          type: string
          format: uri
          pattern: ^https://www\.youtube\.com/channel/
        last_synced_at:
          type:
            - string
            - "null"
          format: date-time
        videos:
          type: array
          maxItems: 6
          items:
            $ref: "#/components/schemas/PublicYouTubeVideo"
        shorts:
          type: array
          maxItems: 6
          items:
            $ref: "#/components/schemas/PublicYouTubeVideo"
    PublicStreamerSite:
      type: object
      additionalProperties: false
      required:
        - slug
        - display_name
        - tagline
        - bio
        - creator_color
        - social_links
        - slot_codes
        - community
        - giveaways
        - wager_races
        - mini_games
        - store
        - youtube
      properties:
        slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        display_name:
          type: string
          minLength: 1
          maxLength: 80
        tagline:
          type: string
          maxLength: 160
        bio:
          type: string
          maxLength: 1000
        creator_color:
          type: string
          pattern: ^#[0-9A-F]{6}$
        social_links:
          $ref: "#/components/schemas/PublicSocialLinks"
        slot_codes:
          type: array
          minItems: 6
          maxItems: 6
          items:
            $ref: "#/components/schemas/PublicStreamerSiteSlotCode"
        community:
          oneOf:
            - $ref: "#/components/schemas/PublicCommunity"
            - type: "null"
        giveaways:
          type: array
          maxItems: 2
          items:
            $ref: "#/components/schemas/PublicGiveaway"
        mini_games:
          type: array
          maxItems: 8
          items:
            $ref: "#/components/schemas/PublicMiniGame"
        store:
          oneOf:
            - $ref: "#/components/schemas/PublicStore"
            - type: "null"
        youtube:
          oneOf:
            - $ref: "#/components/schemas/PublicYouTubeCatalog"
            - type: "null"
    StreamerSiteEventInput:
      oneOf:
        - type: object
          additionalProperties: false
          required:
            - event_id
            - event_type
            - surface
          properties:
            event_id:
              type: string
              format: uuid
            event_type:
              type: string
              const: page_view
            surface:
              type: string
              enum:
                - home
                - how_it_works
                - games
                - game_flip
                - game_dice
                - game_plinko
                - game_roulette
        - type: object
          additionalProperties: false
          required:
            - event_id
            - event_type
            - surface
            - target_type
            - target_key
          properties:
            event_id:
              type: string
              format: uuid
            event_type:
              type: string
              const: click
            surface:
              type: string
              enum:
                - home
                - how_it_works
                - games
                - game_flip
                - game_dice
                - game_plinko
                - game_roulette
            target_type:
              type: string
              pattern: ^[a-z][a-z0-9_]{1,31}$
            target_key:
              type: string
              minLength: 1
              maxLength: 128
              pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$
    StreamerSiteEventReceipt:
      type: object
      additionalProperties: false
      required:
        - accepted
        - replayed
      properties:
        accepted:
          type: boolean
          const: true
        replayed:
          type: boolean
    StreamerSiteAnalytics:
      type: object
      additionalProperties: false
      required:
        - period
        - web
        - top_interactions
        - mini_games
        - conversions
      properties:
        period:
          $ref: "#/components/schemas/StreamerSiteAnalyticsPeriod"
        web:
          $ref: "#/components/schemas/StreamerSiteWebAnalytics"
        top_interactions:
          type: array
          maxItems: 10
          items:
            $ref: "#/components/schemas/StreamerSiteInteractionAnalytics"
        mini_games:
          type: array
          maxItems: 8
          items:
            $ref: "#/components/schemas/StreamerSiteMiniGameAnalytics"
        conversions:
          $ref: "#/components/schemas/StreamerSiteConversionAnalytics"
    StreamerSiteAnalyticsPeriod:
      type: object
      additionalProperties: false
      required:
        - key
        - start_at
        - end_at
      properties:
        key:
          type: string
          enum:
            - 7d
            - 30d
            - month
        start_at:
          type: string
          format: date-time
        end_at:
          type: string
          format: date-time
    StreamerSiteWebAnalytics:
      type: object
      additionalProperties: false
      required:
        - impressions
        - unique_visitors
        - clicks
        - unique_clicks
        - ctr
        - unique_ctr
      properties:
        impressions:
          type: integer
          minimum: 0
        unique_visitors:
          type: integer
          minimum: 0
        clicks:
          type: integer
          minimum: 0
        unique_clicks:
          type: integer
          minimum: 0
        ctr:
          type:
            - number
            - "null"
          minimum: 0
          description: Clicks divididos por impresiones; null sin impresiones.
        unique_ctr:
          type:
            - number
            - "null"
          minimum: 0
          description: Visitantes con click divididos por visitantes; null sin visitantes.
    StreamerSiteInteractionAnalytics:
      type: object
      additionalProperties: false
      required:
        - target_type
        - target_key
        - clicks
        - unique_clicks
      properties:
        target_type:
          type: string
          pattern: ^[a-z][a-z0-9_]{1,31}$
        target_key:
          type: string
          minLength: 1
          maxLength: 128
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$
        clicks:
          type: integer
          minimum: 0
        unique_clicks:
          type: integer
          minimum: 0
    StreamerSiteMiniGameAnalytics:
      type: object
      additionalProperties: false
      required:
        - game
        - plays
        - unique_players
        - points_played
        - points_awarded
      properties:
        game:
          type: string
          enum:
            - flip
            - dice
            - plinko
            - roulette
        plays:
          type: integer
          minimum: 0
        unique_players:
          type: integer
          minimum: 0
        points_played:
          type: integer
          minimum: 0
        points_awarded:
          type: integer
          minimum: 0
    StreamerSiteConversionAnalytics:
      type: object
      additionalProperties: false
      required:
        - community_subscriptions
        - giveaway_entries
        - store_redemptions
      properties:
        community_subscriptions:
          type: integer
          minimum: 0
        giveaway_entries:
          type: integer
          minimum: 0
        store_redemptions:
          type: integer
          minimum: 0
    PublicStreamerSiteSlotCode:
      type: object
      additionalProperties: false
      required:
        - slot_key
        - code
      properties:
        slot_key:
          type: string
          enum:
            - frutas
            - faraon
            - bonanza
            - dragon
            - trebol
            - cofre
        code:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{2,63}$
    PublicStore:
      type: object
      additionalProperties: false
      required:
        - sandbox
        - rewards
      properties:
        sandbox:
          type: boolean
          const: true
        rewards:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/PublicStoreReward"
    PublicMiniGame:
      type: object
      additionalProperties: false
      required:
        - game
        - environment
        - rules_version
        - min_points_cost
        - max_points_cost
        - points_increment
        - award_numerator
        - award_denominator
        - payout_model
        - payout_table
        - configuration_version
      properties:
        game:
          type: string
          enum:
            - flip
            - dice
            - plinko
            - roulette
        environment:
          type: string
          const: sandbox
        rules_version:
          type: integer
          const: 1
        min_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        max_points_cost:
          type: integer
          minimum: 50
          maximum: 5000
          multipleOf: 50
        points_increment:
          type: integer
          const: 50
        award_numerator:
          type: integer
          const: 99
        award_denominator:
          type: integer
          const: 50
        payout_model:
          type: string
          enum:
            - fixed
            - plinko-buckets-v1
            - roulette-european-v1
        payout_table:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/PlinkoPayoutTable"
        configuration_version:
          type: integer
          minimum: 1
    PublicStoreReward:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - description
        - image_url
        - points_cost
        - stock_remaining
        - max_per_viewer
        - fulfillment_type
        - sandbox
        - terms_version
        - terms_text
        - valid_from
        - valid_until
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          minLength: 3
          maxLength: 100
        description:
          type: string
          minLength: 3
          maxLength: 1000
        image_url:
          type:
            - string
            - "null"
          format: uri
          pattern: ^https://
        points_cost:
          type: integer
          minimum: 1
          maximum: 1000000000
        stock_remaining:
          type: integer
          minimum: 0
        max_per_viewer:
          type: integer
          minimum: 1
          maximum: 1000
        fulfillment_type:
          type: string
          const: sandbox
        sandbox:
          type: boolean
          const: true
        terms_version:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$
        terms_text:
          type: string
          minLength: 20
          maxLength: 10000
        valid_from:
          type: string
          format: date-time
        valid_until:
          type: string
          format: date-time
    KickEventType:
      type: string
      enum:
        - chat.message.sent
        - channel.followed
        - channel.subscription.renewal
        - channel.subscription.gifts
        - channel.subscription.new
        - channel.reward.redemption.updated
        - livestream.status.updated
        - livestream.metadata.updated
        - moderation.banned
        - kicks.gifted
    KickChannel:
      type: object
      additionalProperties: false
      required:
        - broadcaster_user_id
        - slug
        - username
      properties:
        broadcaster_user_id:
          type: string
          pattern: ^[1-9][0-9]*$
        slug:
          type: string
          minLength: 1
          maxLength: 64
        username:
          type: string
          minLength: 1
          maxLength: 100
        profile_picture_url:
          type: string
          format: uri
          pattern: ^https://
    KickEventSubscription:
      type: object
      additionalProperties: false
      required:
        - id
        - event_type
        - version
        - status
      properties:
        id:
          type: string
          minLength: 8
          maxLength: 128
        event_type:
          $ref: "#/components/schemas/KickEventType"
        version:
          type: integer
          const: 1
        status:
          type: string
          enum:
            - active
            - error
        error_code:
          type: string
          pattern: ^[a-z0-9_]{1,64}$
    KickIntegrationStatus:
      type: object
      additionalProperties: false
      required:
        - provider
        - status
        - configuration_ready
        - scopes
        - subscriptions
        - chat_points_enabled
      properties:
        provider:
          type: string
          const: kick
        status:
          type: string
          enum:
            - disconnected
            - connected
            - error
        configuration_ready:
          type: boolean
        channel:
          $ref: "#/components/schemas/KickChannel"
        scopes:
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
              - user:read
              - channel:read
        subscriptions:
          type: array
          maxItems: 10
          items:
            $ref: "#/components/schemas/KickEventSubscription"
        chat_points_enabled:
          type: boolean
        connected_at:
          type: string
          format: date-time
        last_event_at:
          type: string
          format: date-time
        last_error_code:
          type: string
          pattern: ^[a-z0-9_]{1,64}$
    KickAuthorizationInput:
      type: object
      additionalProperties: false
      required:
        - enable_chat_points
      properties:
        enable_chat_points:
          type: boolean
          description: Consentimiento explícito para activar la regla base de 5 puntos con intervalo de 60 segundos.
    KickAuthorizationStart:
      type: object
      additionalProperties: false
      required:
        - authorization_url
        - expires_at
      properties:
        authorization_url:
          type: string
          format: uri
          pattern: ^https://id\.kick\.com/oauth/authorize
        expires_at:
          type: string
          format: date-time
    OverlayEventType:
      type: string
      enum:
        - community.viewer_joined
        - community.code_published
        - store.reward_redeemed
        - giveaway.won
        - achievement.unlocked
        - milestone.reached
    OverlaySettings:
      type: object
      additionalProperties: false
      required:
        - enabled_event_types
        - version
        - updated_at
      properties:
        enabled_event_types:
          type: array
          uniqueItems: true
          maxItems: 5
          items:
            $ref: "#/components/schemas/OverlayEventType"
        overlay_path:
          type: string
          pattern: ^/overlay/[a-z0-9-]+$
        version:
          type: integer
          minimum: 1
        updated_at:
          type: string
          format: date-time
    OverlaySettingsUpdate:
      type: object
      additionalProperties: false
      required:
        - enabled_event_types
        - expected_version
      properties:
        enabled_event_types:
          type: array
          uniqueItems: true
          maxItems: 5
          items:
            $ref: "#/components/schemas/OverlayEventType"
        expected_version:
          type: integer
          minimum: 1
    CasinoConnection:
      type: object
      additionalProperties: false
      required:
        - id
        - display_name
        - slug
        - source
        - status
        - currency
        - ingest_secret_hint
        - version
      properties:
        id:
          type: string
          format: uuid
        display_name:
          type: string
          minLength: 2
          maxLength: 60
        slug:
          type: string
          pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          minLength: 2
          maxLength: 40
          description: Identificador estable de la conexión. No cambia después de crearla.
        source:
          type: string
          enum: [webhook, manual, native]
          description: >-
            De dónde vienen los números. 'native' lo asigna Convray cuando ese casino
            además es tenant de la plataforma; no se puede pedir desde el panel.
        status:
          type: string
          enum: [active, paused, error]
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
        ingest_secret_hint:
          type: string
          minLength: 4
          maxLength: 12
          description: Últimos caracteres del secreto, para reconocer cuál se configuró dónde.
        referral_url:
          type: string
          format: uri
          nullable: true
          description: Link de afiliado del streamer con este casino.
        referral_token:
          type: string
          nullable: true
          pattern: "^[A-Za-z0-9_-]{43}$"
          description: Token opaco del link medido. El link público es /go/{token}.
        referral_click_param:
          type: string
          nullable: true
          pattern: "^[A-Za-z][A-Za-z0-9_]{0,63}$"
          description: Cómo llama el casino al identificador de click. Si falta, se redirige sin agregar nada.
        last_event_at:
          type: string
          format: date-time
          nullable: true
        last_error_code:
          type: string
          nullable: true
        version:
          type: integer
          minimum: 1
    CreatedCasinoConnection:
      allOf:
        - $ref: "#/components/schemas/CasinoConnection"
        - type: object
          required:
            - ingest_secret
          properties:
            ingest_secret:
              type: string
              description: >-
                Secreto con el que el casino firma cada evento. Se entrega una sola vez,
                al crear la conexión, y después queda cifrado en reposo.
    CasinoConnectionCreate:
      type: object
      additionalProperties: false
      required:
        - display_name
        - source
        - currency
      properties:
        display_name:
          type: string
          minLength: 2
          maxLength: 60
        slug:
          type: string
          pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          minLength: 2
          maxLength: 40
          description: Opcional; si falta se deriva del nombre visible.
        source:
          type: string
          enum: [webhook, manual]
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
    CasinoConnectionMetrics:
      type: object
      additionalProperties: false
      required:
        - connection_id
        - display_name
        - currency
        - status
        - has_referral_link
        - clicks
        - unique_visitors
        - wager_total_minor
        - active_players
      properties:
        connection_id:
          type: string
          format: uuid
        display_name:
          type: string
          maxLength: 60
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
        status:
          type: string
          enum: [active, paused, error]
        has_referral_link:
          type: boolean
          description: Sin link medido configurado los clicks no se cuentan.
        clicks:
          type: integer
          minimum: 0
        unique_visitors:
          type: integer
          minimum: 0
          description: Personas distintas dentro de esta conexión, no comparables entre conexiones.
        wager_total_minor:
          type: integer
          minimum: 0
          description: Volumen apostado en la unidad menor de la moneda de la conexión.
        active_players:
          type: integer
          minimum: 0
        last_event_at:
          type: string
          format: date-time
          nullable: true
    CasinoDailyPoint:
      type: object
      additionalProperties: false
      required:
        - date
        - clicks
        - wager_total_minor
      properties:
        date:
          type: string
          format: date
        clicks:
          type: integer
          minimum: 0
        wager_total_minor:
          type: integer
          minimum: 0
    CasinoTopPlayer:
      type: object
      additionalProperties: false
      required:
        - player_alias
        - currency
        - wager_total_minor
        - bets
      properties:
        player_alias:
          type: string
          maxLength: 48
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
        wager_total_minor:
          type: integer
          minimum: 0
        bets:
          type: integer
          minimum: 0
    CasinoMetrics:
      type: object
      additionalProperties: false
      required:
        - window_days
        - connections
        - clicks
        - daily
        - top_players
      properties:
        window_days:
          type: integer
          enum: [7, 30, 90]
        connections:
          type: array
          items:
            $ref: "#/components/schemas/CasinoConnectionMetrics"
        clicks:
          type: integer
          minimum: 0
          description: Único total agregado; el resto sólo tiene sentido por conexión.
        daily:
          type: array
          description: >-
            Un punto por día de la ventana, en UTC, incluidos los días sin actividad:
            una serie a la que le faltan los ceros convierte dos picos aislados en
            tráfico sostenido.
          items:
            $ref: "#/components/schemas/CasinoDailyPoint"
        top_players:
          type: array
          maxItems: 8
          items:
            $ref: "#/components/schemas/CasinoTopPlayer"
    CasinoReferralConfigure:
      type: object
      additionalProperties: false
      required:
        - referral_url
        - expected_version
      properties:
        referral_url:
          type: string
          format: uri
          maxLength: 2048
          description: HTTPS al puerto estándar, sin credenciales ni fragmento, que resuelva a una red pública.
        referral_click_param:
          type: string
          pattern: "^[A-Za-z][A-Za-z0-9_]{0,63}$"
          description: Opcional. Si el casino sabe recibir un identificador de click, cómo lo llama.
        expected_version:
          type: integer
          minimum: 1
    CasinoWagerEvent:
      type: object
      additionalProperties: false
      required:
        - external_event_id
        - external_player_ref
        - player_alias
        - wagered_amount_minor
        - currency
        - occurred_at
      properties:
        external_event_id:
          type: string
          minLength: 1
          maxLength: 120
          description: Identificador del evento en el casino. Es la clave de idempotencia.
        external_player_ref:
          type: string
          minLength: 1
          maxLength: 120
          description: Referencia estable del jugador en el casino. No hace falta que tenga cuenta en Convray.
        player_alias:
          type: string
          minLength: 1
          maxLength: 48
          description: Nombre visible del jugador. Se publica enmascarado en el leaderboard.
        wagered_amount_minor:
          type: integer
          minimum: 1
          maximum: 100000000000
          description: Monto apostado en la unidad menor y entera de la moneda.
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
          description: Tiene que coincidir con la moneda de la conexión.
        occurred_at:
          type: string
          format: date-time
    CasinoWagerBatch:
      type: object
      additionalProperties: false
      required:
        - events
      properties:
        events:
          type: array
          minItems: 1
          maxItems: 500
          items:
            $ref: "#/components/schemas/CasinoWagerEvent"
    CasinoWagerIngestResult:
      type: object
      additionalProperties: false
      required:
        - accepted
        - duplicated
      properties:
        accepted:
          type: integer
          minimum: 0
          description: Eventos que Convray no tenía y quedaron guardados.
        duplicated:
          type: integer
          minimum: 0
          description: Eventos cuyo external_event_id ya estaba registrado.
    CasinoConnectionUpdate:
      type: object
      additionalProperties: false
      required:
        - display_name
        - status
        - expected_version
      properties:
        display_name:
          type: string
          minLength: 2
          maxLength: 60
        status:
          type: string
          enum: [active, paused]
          description: "El estado 'error' lo escribe la ingesta, no el panel."
        expected_version:
          type: integer
          minimum: 1
    WagerRacePrize:
      type: object
      additionalProperties: false
      required:
        - rank
        - amount_minor
        - description
      description: >-
        Lo que el casino ofrece por un puesto. Necesita monto, descripción, o ambos:
        un puesto premiado que no ofrece nada no debería estar en la tabla.
      properties:
        rank:
          type: integer
          minimum: 1
          maximum: 20
        amount_minor:
          type: integer
          minimum: 0
          maximum: 100000000000
          description: Monto en la unidad menor y entera de la moneda de la conexión.
        description:
          type: string
          maxLength: 120
          description: "Lo que no es plata: giros, bonos, entradas. Puede ir vacío."
    WagerRaceStanding:
      type: object
      additionalProperties: false
      required:
        - rank
        - external_player_ref
        - public_alias
        - wagered_total
        - prize_amount_minor
        - prize_description
      properties:
        rank:
          type: integer
          minimum: 1
        external_player_ref:
          type: string
          maxLength: 120
          description: >-
            Referencia del jugador tal como la conoce el casino. Sólo la ve el dueño
            de la comunidad; nunca sale por la superficie pública.
        public_alias:
          type: string
          maxLength: 48
        wagered_total:
          type: integer
          minimum: 1
        prize_amount_minor:
          type: integer
          minimum: 0
        prize_description:
          type: string
          maxLength: 120
    WagerRace:
      type: object
      additionalProperties: false
      description: >-
        Una carrera compite por lo apostado en un casino conectado y el premio lo pone
        ese casino. Convray congela la clasificación al vencer la ventana y no acredita
        puntos por ella.
      required:
        - id
        - connection_id
        - title
        - status
        - currency
        - starts_at
        - ends_at
        - minimum_wager
        - prizes
        - standings
        - version
      properties:
        id:
          type: string
          format: uuid
        connection_id:
          type: string
          format: uuid
          description: Casino conectado sobre el que se corre. No cambia después de crearla.
        title:
          type: string
          minLength: 3
          maxLength: 80
        status:
          type: string
          enum: [draft, active, closed]
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
          description: Se hereda de la conexión; el panel no la elige.
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        minimum_wager:
          type: integer
          minimum: 1
          description: Mínimo apostado en el casino, en unidad menor, para entrar en la clasificación.
        prizes:
          type: array
          maxItems: 20
          items:
            $ref: "#/components/schemas/WagerRacePrize"
        standings:
          type: array
          description: Clasificación congelada al cerrar. Vacía mientras la carrera no cierre.
          items:
            $ref: "#/components/schemas/WagerRaceStanding"
        version:
          type: integer
          minimum: 1
        closed_at:
          type: string
          format: date-time
          nullable: true
    WagerRaceCreate:
      type: object
      additionalProperties: false
      required:
        - connection_id
        - title
        - starts_at
        - ends_at
        - minimum_wager
        - prizes
      properties:
        connection_id:
          type: string
          format: uuid
        title:
          type: string
          minLength: 3
          maxLength: 80
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        minimum_wager:
          type: integer
          minimum: 1
          maximum: 100000000000
        prizes:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: "#/components/schemas/WagerRacePrize"
    WagerRaceUpdate:
      type: object
      additionalProperties: false
      required:
        - title
        - status
        - starts_at
        - ends_at
        - minimum_wager
        - prizes
        - expected_version
      properties:
        title:
          type: string
          minLength: 3
          maxLength: 80
        status:
          type: string
          enum: [draft, active]
          description: Cerrar no es una transición que el panel pueda pedir.
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        minimum_wager:
          type: integer
          minimum: 1
          maximum: 100000000000
        prizes:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: "#/components/schemas/WagerRacePrize"
        expected_version:
          type: integer
          minimum: 1
    PublicWagerRace:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - status
        - currency
        - casino_name
        - join_path
        - starts_at
        - ends_at
        - minimum_wager
        - prizes
        - standings
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
          enum: [active, closed]
        currency:
          type: string
          pattern: "^[A-Z]{3}$"
        casino_name:
          type: string
          maxLength: 60
        join_path:
          type: string
          pattern: "^(|/go/[A-Za-z0-9_-]{43})$"
          description: >-
            Ruta interna del link medido. Va vacía si el streamer todavía no lo
            configuró, y en ese caso la carrera se muestra sin CTA. Nunca es una URL
            absoluta: eso convertiría la página en un redirect abierto.
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        minimum_wager:
          type: integer
          minimum: 1
        prizes:
          type: array
          items:
            $ref: "#/components/schemas/WagerRacePrize"
        standings:
          type: array
          description: En una carrera activa se calcula en vivo; en una cerrada sale de la foto congelada.
          items:
            $ref: "#/components/schemas/PublicWagerRaceStanding"
    PublicWagerRaceStanding:
      type: object
      additionalProperties: false
      description: >-
        Fila pública del leaderboard. El alias va enmascarado y la referencia del
        jugador en el casino nunca sale por esta superficie.
      required:
        - rank
        - public_alias
        - wagered_total
        - prize_amount_minor
        - prize_description
      properties:
        rank:
          type: integer
          minimum: 1
        public_alias:
          type: string
          maxLength: 48
        wagered_total:
          type: integer
          minimum: 1
        prize_amount_minor:
          type: integer
          minimum: 0
        prize_description:
          type: string
          maxLength: 120
    ChatCodeSettings:
      type: object
      additionalProperties: false
      required:
        - enabled
        - max_amount
      properties:
        enabled:
          type: boolean
          description: Nace en false; emitir puntos desde un mensaje es autoridad concedida explícitamente.
        max_amount:
          type: integer
          minimum: 1
          maximum: 100000
          description: Techo por código emitido al aire, más bajo que el del panel.
    ChatCodeSettingsUpdate:
      type: object
      additionalProperties: false
      required:
        - enabled
        - max_amount
      properties:
        enabled:
          type: boolean
        max_amount:
          type: integer
          minimum: 1
          maximum: 100000
    RedemptionCode:
      type: object
      additionalProperties: false
      required:
        - id
        - code
        - display_name
        - amount
        - redeemed_count
        - status
        - version
        - created_at
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
          pattern: ^[A-Z0-9][A-Z0-9-]{2,31}$
        display_name:
          type: string
          minLength: 2
          maxLength: 80
        amount:
          type: integer
          minimum: 1
        max_redemptions:
          type: integer
          nullable: true
          minimum: 1
        redeemed_count:
          type: integer
          minimum: 0
        status:
          type: string
          enum: [active, disabled]
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_until:
          type: string
          format: date-time
          nullable: true
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
    RedemptionCodeCreate:
      type: object
      additionalProperties: false
      required:
        - code
        - display_name
        - amount
      properties:
        code:
          type: string
          minLength: 3
          maxLength: 32
          description: Se normaliza a mayúsculas. Sólo letras, dígitos y guiones simples, para dictarlo al aire sin ambigüedad.
        display_name:
          type: string
          minLength: 2
          maxLength: 80
        amount:
          type: integer
          minimum: 1
          maximum: 1000000
        max_redemptions:
          type: integer
          nullable: true
          minimum: 1
          maximum: 1000000
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_until:
          type: string
          format: date-time
          nullable: true
    RedemptionCodeUpdate:
      type: object
      additionalProperties: false
      required:
        - display_name
        - status
        - expected_version
      properties:
        display_name:
          type: string
          minLength: 2
          maxLength: 80
        status:
          type: string
          enum: [active, disabled]
        max_redemptions:
          type: integer
          nullable: true
          minimum: 1
          maximum: 1000000
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_until:
          type: string
          format: date-time
          nullable: true
        expected_version:
          type: integer
          minimum: 1
    RedemptionCodeRedeem:
      type: object
      additionalProperties: false
      required:
        - streamer_slug
        - code
      properties:
        streamer_slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        code:
          type: string
          minLength: 3
          maxLength: 32
    RedemptionCodeRedeemResult:
      type: object
      additionalProperties: false
      required:
        - code
        - display_name
        - amount
        - balance
        - replayed
      properties:
        code:
          type: string
        display_name:
          type: string
        amount:
          type: integer
          minimum: 1
        balance:
          type: integer
          minimum: 0
        replayed:
          type: boolean
          description: True cuando el viewer ya había canjeado el código y no se acreditó de nuevo.
    PointRule:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - source_type
        - source_status
        - status
        - amount
        - frequency_seconds
        - version
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          minLength: 2
          maxLength: 100
        source_type:
          type: string
          description: Fuente de evidencia que alimenta la regla.
        source_status:
          type: string
          enum: [available, unavailable, degraded, suspended]
          description: Una regla sólo puede activarse si su fuente está available.
        status:
          type: string
          enum: [draft, active, paused, archived]
        amount:
          type: integer
          minimum: 1
          description: Puntos acreditados por evento válido.
        frequency_seconds:
          type: integer
          minimum: 0
          description: Ventana mínima entre acreditaciones al mismo viewer. Cero acredita cada evento.
        per_viewer_limit:
          type: integer
          nullable: true
          minimum: 1
        global_limit:
          type: integer
          nullable: true
          minimum: 1
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_until:
          type: string
          format: date-time
          nullable: true
        version:
          type: integer
          minimum: 1
    PointRuleUpdate:
      type: object
      additionalProperties: false
      required:
        - name
        - status
        - amount
        - frequency_seconds
        - expected_version
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 100
        status:
          type: string
          enum: [draft, active, paused]
          description: El retiro definitivo (archived) no se expone acá porque afecta el historial acreditado.
        amount:
          type: integer
          minimum: 1
          maximum: 1000000
        frequency_seconds:
          type: integer
          minimum: 0
          maximum: 86400
        per_viewer_limit:
          type: integer
          nullable: true
          minimum: 1
          maximum: 100000000
        global_limit:
          type: integer
          nullable: true
          minimum: 1
          maximum: 1000000000
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_until:
          type: string
          format: date-time
          nullable: true
        expected_version:
          type: integer
          minimum: 1
    ConvrayOverlayEvent:
      type: object
      additionalProperties: false
      required:
        - id
        - event_type
        - display_text
        - occurred_at
      properties:
        id:
          type: string
          format: uuid
        event_type:
          $ref: "#/components/schemas/OverlayEventType"
        actor_alias:
          type: string
          minLength: 2
          maxLength: 48
        actor_avatar_url:
          type: string
          format: uri
          pattern: ^https://
        display_text:
          type: string
          minLength: 1
          maxLength: 240
        occurred_at:
          type: string
          format: date-time
    ConvrayOverlayFeed:
      type: object
      additionalProperties: false
      required:
        - server_time
        - events
      properties:
        server_time:
          type: string
          format: date-time
        events:
          type: array
          maxItems: 20
          items:
            $ref: "#/components/schemas/ConvrayOverlayEvent"
    YouTubeChannel:
      type: object
      additionalProperties: false
      required:
        - id
        - title
      properties:
        id:
          type: string
          minLength: 1
        title:
          type: string
          minLength: 1
        handle:
          type: string
        thumbnail_url:
          type: string
          format: uri
    YouTubeIntegrationStatus:
      type: object
      additionalProperties: false
      required:
        - provider
        - status
        - configuration_ready
        - scopes
        - video_count
        - short_count
        - sync_in_progress
      properties:
        provider:
          type: string
          const: youtube
        status:
          type: string
          enum:
            - disconnected
            - connected
            - error
        configuration_ready:
          type: boolean
        channel:
          $ref: "#/components/schemas/YouTubeChannel"
        scopes:
          type: array
          uniqueItems: true
          items:
            type: string
        connected_at:
          type: string
          format: date-time
        last_synced_at:
          type: string
          format: date-time
        next_sync_at:
          type: string
          format: date-time
        last_error_code:
          type: string
        video_count:
          type: integer
          minimum: 0
        short_count:
          type: integer
          minimum: 0
        sync_in_progress:
          type: boolean
    YouTubeAuthorizationStart:
      type: object
      additionalProperties: false
      required:
        - authorization_url
        - expires_at
      properties:
        authorization_url:
          type: string
          format: uri
          pattern: ^https://accounts\\.google\\.com/
        expires_at:
          type: string
          format: date-time
    CasinoOnboardingInput:
      type: object
      additionalProperties: false
      required:
        - display_name
        - slug
        - legal_entity_ref
        - document_ref
        - contract_effective_from
        - owner_email
        - invitation_expires_at
        - reason
      properties:
        display_name:
          type: string
          minLength: 2
          maxLength: 120
        slug:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        legal_entity_ref:
          type: string
          minLength: 3
          maxLength: 120
        document_ref:
          type: string
          minLength: 3
          maxLength: 240
        contract_effective_from:
          type: string
          format: date-time
        contract_effective_until:
          type:
            - string
            - "null"
          format: date-time
        owner_email:
          type: string
          format: email
          maxLength: 254
        invitation_expires_at:
          type: string
          format: date-time
        reason:
          type: string
          minLength: 10
          maxLength: 500
    CasinoContractSummary:
      type: object
      additionalProperties: false
      required:
        - id
        - status
        - effective_from
        - effective_until
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          const: active
        effective_from:
          type: string
          format: date-time
        effective_until:
          type:
            - string
            - "null"
          format: date-time
    CasinoInvitationSummary:
      type: object
      additionalProperties: false
      required:
        - id
        - email
        - role
        - expires_at
        - token
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        role:
          type: string
          enum:
            - owner
            - member
    CasinoTeamMember:
      type: object
      additionalProperties: false
      required: [membership_id, email, role, status, version, created_at]
      properties:
        membership_id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        role:
          type: string
          enum: [owner, member]
        status:
          type: string
          enum: [active, suspended, revoked]
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
    CasinoTeamInvitation:
      type: object
      additionalProperties: false
      required: [id, email, role, status, expires_at, version, created_at]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        role:
          type: string
          const: member
        status:
          type: string
          enum: [pending, accepted, revoked, expired]
        expires_at:
          type: string
          format: date-time
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        token:
          type: string
          minLength: 80
          writeOnly: true
          description: Solo está presente en la respuesta inmediata de creación; la web oficial lo incorpora al fragmento de un enlace de un solo uso.
    CasinoTeam:
      type: object
      additionalProperties: false
      required: [members, invitations]
      properties:
        members:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/CasinoTeamMember"
        invitations:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/CasinoTeamInvitation"
    CasinoTeamInvitationCreateInput:
      type: object
      additionalProperties: false
      required: [email, expires_at]
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        expires_at:
          type: string
          format: date-time
    CasinoTeamMutationInput:
      type: object
      additionalProperties: false
      required: [expected_version, reason]
      properties:
        expected_version:
          type: integer
          minimum: 1
        reason:
          type: string
          minLength: 3
          maxLength: 240
    CasinoTeamMemberUpdateInput:
      type: object
      additionalProperties: false
      required: [status, expected_version, reason]
      properties:
        status:
          type: string
          enum: [active, suspended, revoked]
        expected_version:
          type: integer
          minimum: 1
        reason:
          type: string
          minLength: 3
          maxLength: 240
        expires_at:
          type: string
          format: date-time
        token:
          type: string
          minLength: 80
          writeOnly: true
          description: Secreto devuelto una sola vez; nunca se persiste en claro.
    CasinoOnboardingResult:
      type: object
      additionalProperties: false
      required:
        - casino
        - contract
        - landing_id
        - invitation
      properties:
        casino:
          $ref: "#/components/schemas/CasinoTenantSummary"
        contract:
          $ref: "#/components/schemas/CasinoContractSummary"
        landing_id:
          type: string
          format: uuid
        invitation:
          $ref: "#/components/schemas/CasinoInvitationSummary"
    Deal:
      type: object
      additionalProperties: false
      required:
        - id
        - code
        - name
        - commercial_model
        - currency
        - cpa_amount_minor
        - revshare_bps
        - status
        - valid_from
        - valid_until
        - version
        - casino
        - streamer
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
        name:
          type: string
        commercial_model:
          type: string
          enum:
            - cpa
            - revshare
            - hybrid
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        cpa_amount_minor:
          type:
            - integer
            - "null"
          minimum: 0
        revshare_bps:
          type:
            - integer
            - "null"
          minimum: 0
          maximum: 10000
        status:
          type: string
          enum:
            - draft
            - active
            - suspended
            - ended
        valid_from:
          type: string
          format: date-time
        valid_until:
          type:
            - string
            - "null"
          format: date-time
        version:
          type: integer
          minimum: 1
        casino:
          $ref: "#/components/schemas/CasinoTenantSummary"
        streamer:
          $ref: "#/components/schemas/PartnerTenantSummary"
    DealList:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/Deal"
    CasinoInvitationAcceptanceInput:
      type: object
      additionalProperties: false
      required:
        - token
        - password
      properties:
        token:
          type: string
          minLength: 80
          writeOnly: true
        password:
          type: string
          minLength: 12
          maxLength: 72
          writeOnly: true
    CasinoInvitationAcceptance:
      type: object
      additionalProperties: false
      required:
        - user_id
        - tenant_id
        - tenant_slug
        - role
      properties:
        user_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        tenant_slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        role:
          type: string
          const: owner
    CasinoLandingSettings:
      type: object
      additionalProperties: false
      required:
        - id
        - slug
        - display_name
        - headline
        - description
        - accent_color
        - publication_status
        - has_unpublished_changes
        - version
        - published_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        display_name:
          type: string
          minLength: 1
          maxLength: 80
        headline:
          type: string
          maxLength: 160
        description:
          type: string
          maxLength: 1200
        accent_color:
          type: string
          pattern: ^#[0-9A-F]{6}$
        publication_status:
          type: string
          enum:
            - draft
            - published
            - unpublished
        has_unpublished_changes:
          type: boolean
        version:
          type: integer
          minimum: 1
        published_at:
          type:
            - string
            - "null"
          format: date-time
        updated_at:
          type: string
          format: date-time
    CasinoLandingUpdateInput:
      type: object
      additionalProperties: false
      required:
        - expected_version
      properties:
        display_name:
          type: string
          minLength: 1
          maxLength: 80
        headline:
          type: string
          maxLength: 160
        description:
          type: string
          maxLength: 1200
        accent_color:
          type: string
          pattern: ^#[0-9A-F]{6}$
        publication_status:
          type: string
          enum:
            - draft
            - published
            - unpublished
        expected_version:
          type: integer
          minimum: 1
    CasinoDashboard:
      type: object
      additionalProperties: false
      required:
        - casino
        - landing
        - programs
        - offers
        - deals
      properties:
        casino:
          $ref: "#/components/schemas/CasinoTenantSummary"
        landing:
          $ref: "#/components/schemas/CasinoLandingSettings"
        programs:
          $ref: "#/components/schemas/AffiliateProgramList"
        offers:
          $ref: "#/components/schemas/OfferList"
        deals:
          $ref: "#/components/schemas/DealList"
    AffiliateProgram:
      type: object
      additionalProperties: false
      required:
        - id
        - code
        - name
        - brand_name
        - country_code
        - currency
        - status
        - terms_version
        - terms_url
        - terms_summary
        - version
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 2
          maxLength: 120
        brand_name:
          type: string
          minLength: 2
          maxLength: 120
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        status:
          type: string
          enum:
            - draft
            - published
            - paused
            - archived
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        terms_summary:
          type: string
          minLength: 10
          maxLength: 1200
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AffiliateProgramList:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/AffiliateProgram"
    AffiliateProgramCreateInput:
      type: object
      additionalProperties: false
      required:
        - code
        - name
        - brand_name
        - country_code
        - currency
        - terms_version
        - terms_url
        - terms_summary
      properties:
        code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 2
          maxLength: 120
        brand_name:
          type: string
          minLength: 2
          maxLength: 120
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        status:
          type: string
          enum:
            - draft
            - published
            - paused
            - archived
          default: draft
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        terms_summary:
          type: string
          minLength: 10
          maxLength: 1200
    AffiliateProgramUpdateInput:
      type: object
      additionalProperties: false
      required:
        - expected_version
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 120
        brand_name:
          type: string
          minLength: 2
          maxLength: 120
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        status:
          type: string
          enum:
            - draft
            - published
            - paused
            - archived
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        terms_summary:
          type: string
          minLength: 10
          maxLength: 1200
        expected_version:
          type: integer
          minimum: 1
    OfferVersion:
      type: object
      additionalProperties: false
      required:
        - id
        - offer_id
        - version_number
        - commercial_model
        - cpa_amount_minor
        - revshare_bps
        - revshare_basis
        - currency
        - qualification_event
        - criteria_summary
        - terms_version
        - terms_url
        - valid_from
        - valid_until
        - published_at
        - created_at
      properties:
        id:
          type: string
          format: uuid
        offer_id:
          type: string
          format: uuid
        version_number:
          type: integer
          minimum: 1
        commercial_model:
          type: string
          enum:
            - cpa
            - revshare
            - hybrid
        cpa_amount_minor:
          type:
            - integer
            - "null"
          minimum: 0
        revshare_bps:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 10000
          description: Puntos base sobre NGR; 3000 equivale a 30%.
        revshare_basis:
          type:
            - string
            - "null"
          enum:
            - ngr
            - null
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        qualification_event:
          type: string
          const: player.first_deposit
        criteria_summary:
          type: string
          minLength: 10
          maxLength: 1000
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        valid_from:
          type: string
          format: date-time
        valid_until:
          type:
            - string
            - "null"
          format: date-time
        published_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
    Offer:
      type: object
      additionalProperties: false
      required:
        - id
        - program_id
        - code
        - name
        - status
        - current_published_version_id
        - version
        - created_at
        - updated_at
        - versions
      properties:
        id:
          type: string
          format: uuid
        program_id:
          type: string
          format: uuid
        code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 2
          maxLength: 120
        status:
          type: string
          enum:
            - draft
            - published
            - paused
            - archived
        current_published_version_id:
          type:
            - string
            - "null"
          format: uuid
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        versions:
          type: array
          items:
            $ref: "#/components/schemas/OfferVersion"
    OfferList:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/Offer"
    OfferCreateInput:
      type: object
      additionalProperties: false
      required:
        - program_id
        - code
        - name
      properties:
        program_id:
          type: string
          format: uuid
        code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 2
          maxLength: 120
    OfferVersionCreateInput:
      type: object
      additionalProperties: false
      required:
        - commercial_model
        - criteria_summary
        - terms_version
        - terms_url
        - valid_from
      properties:
        commercial_model:
          type: string
          enum:
            - cpa
            - revshare
            - hybrid
        cpa_amount_minor:
          type:
            - integer
            - "null"
          minimum: 0
        revshare_bps:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 10000
          description: Puntos base contractuales sobre NGR.
        criteria_summary:
          type: string
          minLength: 10
          maxLength: 1000
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        valid_from:
          type: string
          format: date-time
        valid_until:
          type:
            - string
            - "null"
          format: date-time
      oneOf:
        - title: CPA
          required:
            - cpa_amount_minor
          properties:
            commercial_model:
              const: cpa
            cpa_amount_minor:
              type: integer
              minimum: 0
            revshare_bps:
              type: "null"
        - title: RevShare sobre NGR
          required:
            - revshare_bps
          properties:
            commercial_model:
              const: revshare
            cpa_amount_minor:
              type: "null"
            revshare_bps:
              type: integer
              minimum: 1
              maximum: 10000
        - title: Hybrid CPA más RevShare sobre NGR
          required:
            - cpa_amount_minor
            - revshare_bps
          properties:
            commercial_model:
              const: hybrid
            cpa_amount_minor:
              type: integer
              minimum: 0
            revshare_bps:
              type: integer
              minimum: 1
              maximum: 10000
    PublicAffiliateProgram:
      type: object
      additionalProperties: false
      required:
        - casino_slug
        - display_name
        - headline
        - description
        - accent_color
        - program_code
        - program_name
        - brand_name
        - country_code
        - currency
        - terms_version
        - terms_url
        - terms_summary
        - published_at
      properties:
        casino_slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        display_name:
          type: string
          minLength: 1
          maxLength: 80
        headline:
          type: string
          maxLength: 160
        description:
          type: string
          maxLength: 1200
        accent_color:
          type: string
          pattern: ^#[0-9A-F]{6}$
        program_code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        program_name:
          type: string
          minLength: 2
          maxLength: 120
        brand_name:
          type: string
          minLength: 2
          maxLength: 120
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        terms_summary:
          type: string
          minLength: 10
          maxLength: 1200
        published_at:
          type: string
          format: date-time
    PublicOffer:
      type: object
      additionalProperties: false
      required:
        - code
        - name
        - version_number
        - commercial_model
        - cpa_amount_minor
        - revshare_bps
        - revshare_basis
        - currency
        - qualification_event
        - criteria_summary
        - terms_version
        - terms_url
        - valid_from
        - valid_until
        - published_at
      properties:
        code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 2
          maxLength: 120
        version_number:
          type: integer
          minimum: 1
        commercial_model:
          type: string
          enum:
            - cpa
            - revshare
            - hybrid
        cpa_amount_minor:
          type:
            - integer
            - "null"
          minimum: 0
        revshare_bps:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 10000
          description: Puntos base contractuales sobre NGR.
        revshare_basis:
          type:
            - string
            - "null"
          enum:
            - ngr
            - null
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        qualification_event:
          type: string
          const: player.first_deposit
        criteria_summary:
          type: string
          minLength: 10
          maxLength: 1000
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        valid_from:
          type: string
          format: date-time
        valid_until:
          type:
            - string
            - "null"
          format: date-time
        published_at:
          type: string
          format: date-time
    PublicOfferList:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/PublicOffer"
    PartnerRegistrationInput:
      type: object
      additionalProperties: false
      required:
        - email
        - password
        - partner_slug
        - display_name
        - country_code
        - terms_version
        - accepted_terms
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        password:
          type: string
          minLength: 12
          maxLength: 72
          writeOnly: true
        partner_slug:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        display_name:
          type: string
          minLength: 2
          maxLength: 120
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
        terms_version:
          type: string
          const: partner-registration-2026-07-21
        accepted_terms:
          type: boolean
          const: true
        creator_enabled:
          type: boolean
          default: false
          description: Habilita la extensión creator histórica sin cambiar el tipo partner.
    PartnerChannelInput:
      type: object
      additionalProperties: false
      required:
        - channel_type
        - url
      properties:
        channel_type:
          type: string
          enum:
            - website
            - streaming
            - social
            - community
            - media_buying
            - email
        url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        audience_size:
          type:
            - integer
            - "null"
          minimum: 0
    PartnerChannel:
      type: object
      additionalProperties: false
      required:
        - id
        - channel_type
        - url
        - audience_size
        - verification_status
      properties:
        id:
          type: string
          format: uuid
        channel_type:
          type: string
          enum:
            - website
            - streaming
            - social
            - community
            - media_buying
            - email
        url:
          type: string
          format: uri
          maxLength: 500
          pattern: ^https://
        audience_size:
          type:
            - integer
            - "null"
          minimum: 0
        verification_status:
          type: string
          enum:
            - declared
            - verified
            - rejected
    PartnerProfile:
      type: object
      additionalProperties: false
      required:
        - tenant_id
        - slug
        - display_name
        - status
        - country_code
        - description
        - website_url
        - terms_version
        - terms_accepted_at
        - email_verified_at
        - creator_enabled
        - operating_model
        - home_casino_tenant_id
        - version
        - updated_at
        - channels
      properties:
        tenant_id:
          type: string
          format: uuid
        slug:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        display_name:
          type: string
          minLength: 2
          maxLength: 120
        status:
          type: string
          enum:
            - pending_verification
            - active
            - suspended
            - closed
        country_code:
          type:
            - string
            - "null"
          pattern: ^[A-Z]{2}$
        description:
          type: string
          maxLength: 500
        website_url:
          oneOf:
            - type: string
              format: uri
              maxLength: 500
              pattern: ^https://
            - type: "null"
        terms_version:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 80
        terms_accepted_at:
          oneOf:
            - type: string
              format: date-time
            - type: "null"
        email_verified_at:
          oneOf:
            - type: string
              format: date-time
            - type: "null"
        creator_enabled:
          type: boolean
          readOnly: true
          description: Capability histórica habilitada durante el registro partner.
        operating_model:
          type: string
          readOnly: true
          enum:
            - global
            - managed_exclusive
          description: "`managed_exclusive` fija el workspace a un casino principal sin deshabilitar creator."
        home_casino_tenant_id:
          type:
            - string
            - "null"
          format: uuid
          readOnly: true
          description: "Casino principal inmutable cuando `operating_model` es `managed_exclusive`."
        version:
          type: integer
          minimum: 1
        updated_at:
          type: string
          format: date-time
        channels:
          type: array
          maxItems: 20
          items:
            $ref: "#/components/schemas/PartnerChannel"
    PartnerProfileUpdateInput:
      type: object
      additionalProperties: false
      required:
        - expected_version
      properties:
        display_name:
          type: string
          minLength: 2
          maxLength: 120
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
        description:
          type: string
          maxLength: 500
        website_url:
          type: string
          maxLength: 500
          pattern: ^(|https://)
          description: URL HTTPS o cadena vacía para eliminarla.
        channels:
          type: array
          maxItems: 20
          items:
            $ref: "#/components/schemas/PartnerChannelInput"
        expected_version:
          type: integer
          minimum: 1
    RelationshipParty:
      type: object
      additionalProperties: false
      required:
        - id
        - slug
        - display_name
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        display_name:
          type: string
          minLength: 1
          maxLength: 120
    RelationshipProgramSnapshot:
      type: object
      additionalProperties: false
      required:
        - id
        - code
        - name
        - brand_name
        - country_code
        - currency
        - version
        - terms_version
        - terms_url
        - terms_summary
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 2
          maxLength: 120
        brand_name:
          type: string
          minLength: 2
          maxLength: 120
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        version:
          type: integer
          minimum: 1
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          pattern: ^https://
        terms_summary:
          type: string
          minLength: 10
          maxLength: 1200
    RelationshipOfferSnapshot:
      type: object
      additionalProperties: false
      required:
        - id
        - version_id
        - code
        - name
        - version_number
        - commercial_model
        - cpa_amount_minor
        - revshare_bps
        - revshare_basis
        - currency
        - qualification_event
        - criteria_summary
        - terms_version
        - terms_url
        - valid_from
        - valid_until
      properties:
        id:
          type: string
          format: uuid
        version_id:
          type: string
          format: uuid
        code:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 2
          maxLength: 120
        version_number:
          type: integer
          minimum: 1
        commercial_model:
          type: string
          enum:
            - cpa
            - revshare
            - hybrid
        cpa_amount_minor:
          type:
            - integer
            - "null"
          minimum: 0
        revshare_bps:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 10000
        revshare_basis:
          type:
            - string
            - "null"
          const: ngr
        currency:
          type: string
          pattern: ^[A-Z]{3}$
        qualification_event:
          type: string
          const: player.first_deposit
        criteria_summary:
          type: string
          minLength: 10
          maxLength: 1000
        terms_version:
          type: string
          minLength: 3
          maxLength: 80
        terms_url:
          type: string
          format: uri
          pattern: ^https://
        valid_from:
          type: string
          format: date-time
        valid_until:
          type:
            - string
            - "null"
          format: date-time
      allOf:
        - if:
            properties:
              commercial_model:
                const: cpa
          then:
            properties:
              cpa_amount_minor:
                type: integer
                minimum: 0
              revshare_bps:
                type: "null"
              revshare_basis:
                type: "null"
        - if:
            properties:
              commercial_model:
                const: revshare
          then:
            properties:
              cpa_amount_minor:
                type: "null"
              revshare_bps:
                type: integer
                minimum: 1
                maximum: 10000
              revshare_basis:
                type: string
                const: ngr
        - if:
            properties:
              commercial_model:
                const: hybrid
          then:
            properties:
              cpa_amount_minor:
                type: integer
                minimum: 0
              revshare_bps:
                type: integer
                minimum: 1
                maximum: 10000
              revshare_basis:
                type: string
                const: ngr
    RelationshipProviderMapping:
      type: object
      additionalProperties: false
      required:
        - id
        - provisioning_level
        - mapping_type
        - mapping_value
        - created_at
      properties:
        id:
          type: string
          format: uuid
        provisioning_level:
          type: string
          enum:
            - l0
            - l1
        mapping_type:
          type: string
          enum:
            - manual_code
            - sub_id
            - promo_code
            - affiliate_id
        mapping_value:
          type: string
          minLength: 1
          maxLength: 160
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,159}$
        created_at:
          type: string
          format: date-time
    RelationshipDealReference:
      type: object
      additionalProperties: false
      required:
        - id
        - status
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - submitted
            - provisioning
            - active
            - suspended
    PartnerProgramCandidate:
      type: object
      additionalProperties: false
      required:
        - casino
        - program
        - offer
        - can_apply
        - eligibility_reason
        - existing_deal
      properties:
        casino:
          $ref: "#/components/schemas/RelationshipParty"
        program:
          $ref: "#/components/schemas/RelationshipProgramSnapshot"
        offer:
          $ref: "#/components/schemas/RelationshipOfferSnapshot"
        can_apply:
          type: boolean
        eligibility_reason:
          type:
            - string
            - "null"
          enum:
            - partner_not_active
            - open_deal_exists
            - managed_exclusive_other_casino
            - null
        existing_deal:
          oneOf:
            - $ref: "#/components/schemas/RelationshipDealReference"
            - type: "null"
    PartnerDealCreateInput:
      type: object
      additionalProperties: false
      required:
        - offer_version_id
        - accepted_terms
      properties:
        offer_version_id:
          type: string
          format: uuid
        accepted_terms:
          type: boolean
          const: true
    PartnerDealCancelInput:
      type: object
      additionalProperties: false
      required:
        - expected_version
        - reason
      properties:
        expected_version:
          type: integer
          minimum: 1
        reason:
          type: string
          minLength: 10
          maxLength: 500
    RelationshipMappingInput:
      type: object
      additionalProperties: false
      required:
        - mapping_type
        - mapping_value
      properties:
        mapping_type:
          type: string
          enum:
            - manual_code
            - sub_id
            - promo_code
            - affiliate_id
        mapping_value:
          type: string
          minLength: 1
          maxLength: 160
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,159}$
    CasinoDealTransitionInput:
      type: object
      additionalProperties: false
      required:
        - status
        - expected_version
        - reason
      properties:
        status:
          type: string
          enum:
            - provisioning
            - active
            - rejected
            - suspended
            - ended
        expected_version:
          type: integer
          minimum: 1
        reason:
          type: string
          minLength: 10
          maxLength: 500
        provisioning_level:
          type:
            - string
            - "null"
          enum:
            - l0
            - l1
            - null
        provider_mapping:
          oneOf:
            - $ref: "#/components/schemas/RelationshipMappingInput"
            - type: "null"
    RelationshipDeal:
      type: object
      additionalProperties: false
      required:
        - id
        - status
        - provisioning_level
        - status_reason
        - status_changed_at
        - accepted_at
        - version
        - created_at
        - updated_at
        - partner
        - casino
        - program
        - offer
        - provider_mapping
        - allowed_actions
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - submitted
            - provisioning
            - active
            - rejected
            - cancelled
            - suspended
            - ended
        provisioning_level:
          type:
            - string
            - "null"
          enum:
            - l0
            - l1
            - null
        status_reason:
          type: string
          minLength: 10
          maxLength: 500
        status_changed_at:
          type: string
          format: date-time
        accepted_at:
          type: string
          format: date-time
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        partner:
          $ref: "#/components/schemas/RelationshipParty"
        casino:
          $ref: "#/components/schemas/RelationshipParty"
        program:
          $ref: "#/components/schemas/RelationshipProgramSnapshot"
        offer:
          $ref: "#/components/schemas/RelationshipOfferSnapshot"
        provider_mapping:
          oneOf:
            - $ref: "#/components/schemas/RelationshipProviderMapping"
            - type: "null"
        allowed_actions:
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
              - cancel
              - approve
              - reject
              - activate
              - suspend
              - resume
              - end
    RelationshipDealPage:
      type: object
      additionalProperties: false
      required:
        - items
        - next_cursor
        - limit
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/RelationshipDeal"
        next_cursor:
          type:
            - string
            - "null"
          format: uuid
        limit:
          type: integer
          minimum: 1
          maximum: 100
    TrackingDestination:
      type: object
      additionalProperties: false
      required:
        - id
        - program_id
        - code
        - description
        - status
        - current_published_version_id
        - version
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        program_id:
          type: string
          format: uuid
        code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        description:
          type: string
          minLength: 3
          maxLength: 500
        status:
          type: string
          enum:
            - draft
            - published
            - paused
            - archived
        current_published_version_id:
          type:
            - string
            - "null"
          format: uuid
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TrackingParameterTemplate:
      type: object
      additionalProperties:
        type: string
        minLength: 1
        maxLength: 160
        pattern: ^(\{(?:click_id|provider_account)\}|[A-Za-z0-9._:@/-]{1,160})$
      minProperties: 1
      maxProperties: 20
      propertyNames:
        pattern: ^[A-Za-z][A-Za-z0-9_]{0,63}$
      not:
        type: object
        additionalProperties:
          type: string
          pattern: ^[A-Za-z0-9._:@/-]{1,160}$
    TrackingAllowedHostname:
      type: string
      minLength: 3
      maxLength: 253
      pattern: ^(?=.{1,253}$)(?![0-9.]+$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))+$
    TrackingDestinationVersionCreateInput:
      type: object
      additionalProperties: false
      required:
        - target_url
        - allowed_hosts
        - parameter_template
        - valid_from
      properties:
        target_url:
          type: string
          format: uri
          minLength: 12
          maxLength: 2048
          pattern: ^https://
        allowed_hosts:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            $ref: "#/components/schemas/TrackingAllowedHostname"
        parameter_template:
          $ref: "#/components/schemas/TrackingParameterTemplate"
        valid_from:
          type: string
          format: date-time
        valid_until:
          type:
            - string
            - "null"
          format: date-time
    TrackingDestinationCreateInput:
      type: object
      additionalProperties: false
      required:
        - program_id
        - code
        - description
        - initial_version
      properties:
        program_id:
          type: string
          format: uuid
        code:
          type: string
          minLength: 3
          maxLength: 63
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        description:
          type: string
          minLength: 3
          maxLength: 500
        initial_version:
          $ref: "#/components/schemas/TrackingDestinationVersionCreateInput"
    TrackingDestinationVersion:
      type: object
      additionalProperties: false
      required:
        - id
        - destination_id
        - version_number
        - target_url
        - allowed_hosts
        - parameter_template
        - valid_from
        - created_at
      properties:
        id:
          type: string
          format: uuid
        destination_id:
          type: string
          format: uuid
        version_number:
          type: integer
          minimum: 1
        target_url:
          type: string
          format: uri
          maxLength: 2048
          pattern: ^https://
        allowed_hosts:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            $ref: "#/components/schemas/TrackingAllowedHostname"
        parameter_template:
          $ref: "#/components/schemas/TrackingParameterTemplate"
        valid_from:
          type: string
          format: date-time
        valid_until:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
    TrackingDestinationDetail:
      type: object
      additionalProperties: false
      required:
        - destination
        - current_published_version
      properties:
        destination:
          $ref: "#/components/schemas/TrackingDestination"
        current_published_version:
          oneOf:
            - $ref: "#/components/schemas/TrackingDestinationVersion"
            - type: "null"
    TrackingDestinationTransitionInput:
      type: object
      additionalProperties: false
      required:
        - status
        - reason
        - expected_version
      properties:
        status:
          type: string
          enum:
            - published
            - paused
            - archived
        published_version_id:
          type:
            - string
            - "null"
          format: uuid
        reason:
          type: string
          minLength: 10
          maxLength: 500
        expected_version:
          type: integer
          minimum: 1
    TrackingDestinationPage:
      type: object
      additionalProperties: false
      required:
        - items
        - next_cursor
        - limit
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/TrackingDestination"
        next_cursor:
          type:
            - string
            - "null"
          format: uuid
        limit:
          type: integer
          minimum: 1
          maximum: 100
    TrackingDestinationVersionPage:
      type: object
      additionalProperties: false
      required:
        - items
        - next_cursor
        - limit
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/TrackingDestinationVersion"
        next_cursor:
          type:
            - string
            - "null"
          format: uuid
        limit:
          type: integer
          minimum: 1
          maximum: 100
    TrackingLinkCreateInput:
      type: object
      additionalProperties: false
      required:
        - destination_id
      properties:
        destination_id:
          type: string
          format: uuid
        label:
          type: string
          minLength: 1
          maxLength: 120
    TrackingLinkTransitionInput:
      type: object
      additionalProperties: false
      required:
        - action
        - reason
        - expected_version
      properties:
        action:
          type: string
          enum:
            - pause
            - reactivate
            - revoke
        reason:
          type: string
          minLength: 10
          maxLength: 500
        expected_version:
          type: integer
          minimum: 1
    TrackingLink:
      type: object
      additionalProperties: false
      required:
        - id
        - deal_id
        - destination_id
        - token
        - public_url
        - label
        - resolved_destination_version_id
        - status
        - version
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        deal_id:
          type: string
          format: uuid
        destination_id:
          type: string
          format: uuid
        token:
          type: string
          minLength: 43
          maxLength: 43
          pattern: ^[A-Za-z0-9_-]{43}$
        public_url:
          type: string
          format: uri
          maxLength: 512
          pattern: ^https://[^?#]+/r/[A-Za-z0-9_-]{43}$
        label:
          type:
            - string
            - "null"
          maxLength: 120
        resolved_destination_version_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - active
            - inactive
            - revoked
        version:
          type: integer
          minimum: 1
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TrackingLinkPage:
      type: object
      additionalProperties: false
      required:
        - items
        - next_cursor
        - limit
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/TrackingLink"
        next_cursor:
          type:
            - string
            - "null"
          format: uuid
        limit:
          type: integer
          minimum: 1
          maximum: 100
    LoginInput:
      type: object
      additionalProperties: false
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
          maxLength: 254
        password:
          type: string
          minLength: 1
          maxLength: 72
          writeOnly: true
    ActiveMembershipSelectionInput:
      type: object
      additionalProperties: false
      required:
        - active_membership_id
      properties:
        active_membership_id:
          type: string
          format: uuid
          description: ID opaco de una membresía activa devuelta por /me.
    RuntimeUser:
      type: object
      additionalProperties: false
      required:
        - id
        - email
        - roles
        - principal_mode
        - tenant_id
        - tenant_slug
        - tenant_kind
        - creator_enabled
        - active_membership_id
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        roles:
          type: array
          minItems: 1
          maxItems: 1
          items:
            type: string
            enum:
              - partner
              - casino
              - convray_admin
              - super_admin
        principal_mode:
          type: string
          enum:
            - tenant_member
            - platform_admin
        tenant_id:
          type:
            - string
            - "null"
          format: uuid
        tenant_slug:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 80
        tenant_kind:
          type:
            - string
            - "null"
          enum:
            - partner
            - casino
            - null
        partner_status:
          type: string
          enum:
            - pending_verification
            - active
            - suspended
            - closed
        creator_enabled:
          type: boolean
        active_membership_id:
          type:
            - string
            - "null"
          format: uuid
        memberships:
          type: array
          uniqueItems: true
          items:
            $ref: "#/components/schemas/PublicMembership"
    PublicMembership:
      type: object
      additionalProperties: false
      required:
        - id
        - role
        - status
        - tenant_id
        - tenant_slug
        - tenant_display_name
        - tenant_kind
        - creator_enabled
      properties:
        id:
          type: string
          format: uuid
        role:
          type: string
          enum:
            - owner
            - member
        status:
          type: string
          const: active
        tenant_id:
          type: string
          format: uuid
        tenant_slug:
          type: string
          minLength: 1
          maxLength: 80
        tenant_display_name:
          type: string
          minLength: 1
          maxLength: 120
        tenant_kind:
          type: string
          enum:
            - partner
            - casino
        partner_status:
          type: string
          enum:
            - pending_verification
            - active
            - suspended
            - closed
        creator_enabled:
          type: boolean
    TenantSummary:
      type: object
      additionalProperties: false
      required:
        - id
        - kind
        - slug
        - display_name
      properties:
        id:
          type: string
          format: uuid
        kind:
          type: string
          enum:
            - partner
            - casino
        slug:
          type: string
          minLength: 1
          maxLength: 80
        display_name:
          type: string
          minLength: 1
          maxLength: 120
    PartnerTenantSummary:
      allOf:
        - $ref: "#/components/schemas/TenantSummary"
        - type: object
          properties:
            kind:
              type: string
              const: partner
    CasinoTenantSummary:
      allOf:
        - $ref: "#/components/schemas/TenantSummary"
        - type: object
          properties:
            kind:
              type: string
              const: casino
    AuthResponse:
      type: object
      additionalProperties: false
      required:
        - user
        - access_token
        - token_type
        - expires_in
      properties:
        user:
          $ref: "#/components/schemas/RuntimeUser"
        access_token:
          type: string
          description: JWT corto con aud=convray-admin ligado a la sesión devuelta.
        token_type:
          type: string
          const: Bearer
        expires_in:
          type: integer
          const: 900
    FieldError:
      type: object
      additionalProperties: false
      required:
        - field
        - code
        - message
      properties:
        field:
          type: string
        code:
          type: string
        message:
          type: string
    ProblemBase:
      type: object
      description: Problem Details conforme a RFC 9457; no incluye stack traces, nombres de tablas, secretos ni PII completa.
      additionalProperties: false
      required:
        - type
        - title
        - status
        - detail
        - instance
        - code
        - request_id
      properties:
        type:
          type: string
          format: uri-reference
        title:
          type: string
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri-reference
        code:
          type: string
        request_id:
          type: string
          minLength: 1
        trace_id:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    InvalidCredentialsProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 401
            code:
              type: string
              enum:
                - invalid_credentials
                - invalid_casino_invitation
    InvalidSessionProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 401
            code:
              type: string
              enum:
                - invalid_session
                - viewer_session_invalid
    PermissionDeniedProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 403
            code:
              type: string
              const: permission_denied
    PartnerNotActiveProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            type:
              type: string
              const: https://api.convray.com/problems/partner-not-active
            title:
              type: string
              const: Partner no activo
            status:
              type: integer
              const: 403
            detail:
              type: string
              const: El partner debe estar activo para aplicar a una oferta.
            code:
              type: string
              const: partner_not_active
    ManagedExclusiveCasinoProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            type:
              type: string
              const: https://api.convray.com/problems/managed-exclusive-casino
            title:
              type: string
              const: Casino administrado
            status:
              type: integer
              const: 403
            detail:
              type: string
              const: Este partner opera exclusivamente con el casino asignado por su organización.
            code:
              type: string
              const: managed_exclusive_casino
    PublicCasinoRegistrationForbiddenProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 403
            code:
              type: string
              const: public_casino_registration_forbidden
    PublicRegistrationClosedProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            type:
              type: string
              const: https://api.convray.com/problems/public-registration-closed
            title:
              type: string
              const: El registro público está cerrado
            status:
              type: integer
              const: 403
            detail:
              type: string
              const: Convray está operando un piloto privado. Las cuentas existentes pueden iniciar sesión normalmente.
            code:
              type: string
              const: public_registration_closed
    CSRFRejectedProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 403
            code:
              type: string
              const: csrf_rejected
    ResourceNotFoundProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 404
            code:
              type: string
              pattern: ^(resource|[a-z][a-z0-9_]*_resource|[a-z][a-z0-9_]*)_not_found$
    ValidationFailedProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 422
            code:
              type: string
              const: validation_failed
    RateLimitedProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 429
            code:
              type: string
              const: rate_limited
    AuditUnavailableProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 503
            code:
              type: string
              const: audit_unavailable
    IdempotencyConflictProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 409
            code:
              type: string
              const: idempotency_key_conflict
    PartnerRegistrationConflictProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 409
            code:
              type: string
              enum:
                - email_taken
                - partner_slug_taken
    RelationshipRuleViolationProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 422
            code:
              type: string
              const: validation_failed
    TrackingLinkNotFoundProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 404
            code:
              type: string
              const: tracking_link_not_found
    TrackingRedirectUnavailableProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 503
            code:
              type: string
              enum:
                - tracking_destination_unavailable
                - click_persistence_unavailable
    YouTubeForbiddenProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 403
            code:
              type: string
              const: youtube_forbidden
    YouTubeNotConfiguredProblem:
      allOf:
        - $ref: "#/components/schemas/ProblemBase"
        - type: object
          properties:
            status:
              type: integer
              const: 503
            code:
              type: string
              const: youtube_not_configured
  responses:
    KickForbidden:
      description: Solo el owner streamer activo puede administrar este canal
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ProblemBase"
    KickUnavailable:
      description: Kick, la verificación RSA o la auditoría no están disponibles
      content:
        application/problem+json:
          schema:
            oneOf:
              - $ref: "#/components/schemas/ProblemBase"
              - $ref: "#/components/schemas/AuditUnavailableProblem"
    YouTubeForbidden:
      description: Solo el owner streamer activo puede administrar esta conexión
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/YouTubeForbiddenProblem"
    YouTubeUnavailable:
      description: Las credenciales OAuth no están configuradas o la auditoría no está disponible
      content:
        application/problem+json:
          schema:
            oneOf:
              - $ref: "#/components/schemas/YouTubeNotConfiguredProblem"
              - $ref: "#/components/schemas/AuditUnavailableProblem"
    InvalidCredentials:
      description: Credenciales o token de invitación inválidos, sin enumerar cuentas
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/InvalidCredentialsProblem"
          example:
            type: https://api.convray.com/problems/invalid-credentials
            title: No se pudo iniciar sesión
            status: 401
            detail: El email o la contraseña no son válidos.
            instance: /api/v1/auth/sessions
            code: invalid_credentials
            request_id: req_01
    InvalidSession:
      description: Sesión administrativa o viewer ausente, inválida, expirada o revocada
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/InvalidSessionProblem"
          example:
            type: https://api.convray.com/problems/invalid-session
            title: La sesión no es válida
            status: 401
            detail: Inicia sesión nuevamente.
            instance: /api/v1/me
            code: invalid_session
            request_id: req_01
    PermissionDenied:
      description: Actor administrativo autenticado sin permiso para la acción
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/PermissionDeniedProblem"
          example:
            type: https://api.convray.com/problems/permission-denied
            title: Acción no permitida
            status: 403
            detail: No tienes permiso para realizar esta acción.
            instance: /api/v1/auth/sessions/current
            code: permission_denied
            request_id: req_01
    PermissionOrCSRFRejected:
      description: Actor sin permiso o Origin del navegador rechazado
      content:
        application/problem+json:
          schema:
            oneOf:
              - $ref: "#/components/schemas/PermissionDeniedProblem"
              - $ref: "#/components/schemas/CSRFRejectedProblem"
    CSRFRejected:
      description: El Origin del navegador no coincide con el frontend autorizado
      headers:
        Cache-Control:
          $ref: "#/components/headers/NoStore"
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/CSRFRejectedProblem"
          example:
            type: https://api.convray.com/problems/csrf-rejected
            title: Solicitud rechazada
            status: 403
            detail: El origen de la solicitud no está autorizado.
            instance: /api/v1/auth/sessions/refresh
            code: csrf_rejected
            request_id: req_01
    RegistrationForbidden:
      description: Alta pública cerrada, registro casino prohibido u Origin del navegador rechazado
      headers:
        Cache-Control:
          $ref: "#/components/headers/NoStore"
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
      content:
        application/problem+json:
          schema:
            oneOf:
              - $ref: "#/components/schemas/PublicRegistrationClosedProblem"
              - $ref: "#/components/schemas/PublicCasinoRegistrationForbiddenProblem"
              - $ref: "#/components/schemas/CSRFRejectedProblem"
    ResourceNotFound:
      description: |
        El recurso no existe o pertenece a un tenant/deal no visible. La
        respuesta no permite distinguir ambos casos.
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ResourceNotFoundProblem"
          example:
            type: https://api.convray.com/problems/resource-not-found
            title: Recurso no encontrado
            status: 404
            detail: No se encontró el recurso solicitado.
            instance: /api/v1/auth/sessions/current
            code: resource_not_found
            request_id: req_01
    PartnerRegistrationConflict:
      description: Conflicto de identidad, contexto, consumo o idempotencia del onboarding
      headers:
        Cache-Control:
          $ref: "#/components/headers/NoStore"
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/PartnerRegistrationConflictProblem"
          examples:
            emailTaken:
              value:
                type: https://api.convray.com/problems/email-taken
                title: No se pudo crear la cuenta
                status: 409
                detail: Ya existe una cuenta para ese email.
                instance: /api/v1/partner-registrations
                code: email_taken
                request_id: req_01
            partnerSlugTaken:
              value:
                type: https://api.convray.com/problems/partner-slug-taken
                title: No se pudo crear la cuenta
                status: 409
                detail: El identificador partner no está disponible.
                instance: /api/v1/partner-registrations
                code: partner_slug_taken
                request_id: req_01
    ValidationFailed:
      description: El payload no cumple el contrato
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ValidationFailedProblem"
    VersionConflict:
      description: Estado, revisión, términos, saldo o idempotencia incompatibles
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ProblemBase"
    IdempotencyConflict:
      description: La Idempotency-Key ya fue usada con un digest distinto
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/IdempotencyConflictProblem"
          example:
            type: https://api.convray.com/problems/idempotency-key-conflict
            title: La clave de idempotencia ya fue utilizada
            status: 409
            detail: Usa una nueva Idempotency-Key para un payload diferente.
            instance: /api/v1/partner/deals/541941f5-b6e5-4d18-8f76-9c81889661fc/tracking-links
            code: idempotency_key_conflict
            request_id: req_01
    RelationshipRuleViolation:
      description: Validación o restriction activa impiden crear el deal
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/RelationshipRuleViolationProblem"
    TrackingLinkNotFound:
      description: Token inválido o link no activo, sin distinguir el motivo
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/TrackingLinkNotFoundProblem"
          example:
            type: https://api.convray.com/problems/tracking-link-not-found
            title: Link no disponible
            status: 404
            detail: No se encontró un link de tracking activo.
            instance: /r/invalid-token
            code: tracking_link_not_found
            request_id: req_01
    TrackingRedirectUnavailable:
      description: La configuración validada o la persistencia durable no están disponibles
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/TrackingRedirectUnavailableProblem"
          example:
            type: https://api.convray.com/problems/click-persistence-unavailable
            title: El redirect no está disponible temporalmente
            status: 503
            detail: No se pudo confirmar el click de forma durable.
            instance: /r/AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8
            code: click_persistence_unavailable
            request_id: req_01
    RateLimited:
      description: Límite temporal excedido
      headers:
        Cache-Control:
          $ref: "#/components/headers/PrivateNoStore"
        Retry-After:
          description: Segundos hasta permitir otro intento
          schema:
            type: integer
            minimum: 1
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/RateLimitedProblem"
    AuditUnavailable:
      description: No se pudo persistir evidencia; la operación no continuó
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/AuditUnavailableProblem"
    FairnessUnavailable:
      description: Fairness o auditoría no disponibles; la ronda no fue confirmada
      content:
        application/problem+json:
          schema:
            oneOf:
              - $ref: "#/components/schemas/ProblemBase"
              - $ref: "#/components/schemas/AuditUnavailableProblem"
