openapi: 3.1.0
info:
  title: PlayerOS API
  version: '2026-06-05'
  summary: Programmatic access to the PlayerOS casino marketing platform.
  description: |-
    PlayerOS is a casino marketing and player-engagement platform. This API gives
    programmatic access to your player database, multi-channel campaigns (email, SMS,
    AI voice, web push, and the branded Player Inbox), journey automation, interactive
    games, and TCPA / CCPA / GDPR compliance tooling.

    ## Authentication for AI agents & integrations

    PlayerOS uses **property-scoped API keys**. Each key belongs to a single property
    (tenant) and carries `read` and/or `write` scopes.

    1. Sign in at https://playeros.ai and open **Settings → API Keys**.
    2. Click **Generate Key**, give it a name (e.g. "Acme AI agent"), and copy the
       secret. It starts with `pk_` and is shown **only once**.
    3. Send it on every request as the `X-Api-Key` header:

    ```bash
    curl https://api.playeros.ai/api/players \
      -H "X-Api-Key: pk_your_key_here"
    ```

    Keys can be revoked at any time from the same screen, or programmatically via
    `POST /api/functions/revoke-api-key`. Interactive dashboard sessions may instead
    present a Cognito JWT as `Authorization: Bearer <token>`.

    ## Scopes

    | Scope   | Grants                                                              |
    |---------|--------------------------------------------------------------------|
    | `read`  | List and retrieve resources (players, campaigns, content, stats).  |
    | `write` | Create, update, send, and delete resources.                        |

    Request the narrowest scope set your integration needs. Keys created from the
    dashboard default to `["read","write"]`; pass an explicit `scopes` array to
    `create-api-key` to issue a read-only key.

    ## Base URL

    `https://api.playeros.ai` — every endpoint lives under `/api`.

    ## Multi-tenancy

    Every API key is bound to one property, so requests are scoped automatically.
    A user/JWT with access to multiple properties selects one with the
    `X-Property-Id: <uuid>` header.

    ## Rate limits

    Each API key carries a configurable rate-limit value (default 100). Every response
    includes advisory rate-limit headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
    and `X-RateLimit-Reset` (Unix seconds). Authentication endpoints (`/api/auth/login`,
    password reset) are additionally throttled per IP and per account; throttled
    requests return `429` with a `retry_after` hint.

    ## Versioning & deprecation

    The API is **date-versioned**. Every response carries an `API-Version` header
    (current: `2026-06-05`). Changes are additive wherever possible. When an endpoint
    or field is deprecated, responses carry a `Deprecation` header and, where a removal
    date is set, a `Sunset` header (RFC 8594); deprecations are announced before removal.

    ## Webhooks & signature verification

    PlayerOS POSTs signed event callbacks to your registered `webhook_endpoints`. Each
    delivery includes `X-PlayerOS-Event`, `X-PlayerOS-Timestamp` (Unix seconds), and
    `X-PlayerOS-Signature: v1=<hmac>`. Verify it by computing
    `v1=` + HMAC-SHA256(your endpoint's signing secret, `"{timestamp}.{raw_body}"`) (hex)
    and comparing in constant time. Reject deliveries whose timestamp is outside a few
    minutes to prevent replay.

    ## Conventions

    Most write operations are `POST /api/functions/{name}`. Table data uses RESTful
    CRUD at `/api/{table}` with PostgREST-style filters
    (`?email.ilike=%jane%&status.eq=active&order=created_at.desc&limit=50`). Operators:
    `eq, neq, gt, gte, lt, lte, like, ilike, in, is, not`.

    ## Idempotency

    Mutating requests (POST/PUT/PATCH/DELETE) accept an `Idempotency-Key` header. Send a
    unique key (e.g. a UUID) per logical operation; if the same key is replayed within 24
    hours, the original response is returned (with `Idempotency-Replayed: true`) instead of
    performing the write again. Safe to retry network failures without duplicating effects.

    ## Errors & retries

    Errors return a JSON `{ "error": "..." }` body. `4xx` are client errors — fix the
    request before retrying. `5xx` (e.g. `500`, `503`) are transient — retry with
    exponential backoff. `429` includes a `retry_after` hint and an `X-RateLimit-Reset`
    header; back off until then. Every response carries an `API-Version` header.

    ## Async jobs

    Batch operations (e.g. email validation, CSV import) are processed asynchronously:
    they create a job row you poll for status — `GET /api/validation_jobs/{id}` or
    `GET /api/import_jobs/{id}` — until `status` is `completed` or `failed`.

    ## Model Context Protocol (MCP)

    An MCP server (Streamable HTTP) is available at `https://api.playeros.ai/api/mcp`
    for AI agents. Discovery: https://playeros.ai/.well-known/mcp.json. Authenticate with
    the same property-scoped API key (`X-Api-Key` or `Authorization: Bearer pk_…`).
  termsOfService: https://playeros.ai/terms
  contact:
    name: PlayerOS
    url: https://playeros.ai/api-docs
    email: hello@playeros.ai
  license:
    name: Proprietary
    url: https://playeros.ai/terms
  x-logo:
    url: https://playeros.ai/pwa-icon-512.png
    altText: PlayerOS
servers:
  - url: https://api.playeros.ai
    description: Production
  - url: https://3l21yjs0h6.execute-api.us-east-1.amazonaws.com
    description: Legacy API Gateway (rollback only)
externalDocs:
  description: API documentation & agent authentication guide
  url: https://playeros.ai/api-docs
x-scopes:
  read: List and retrieve resources.
  write: Create, update, send, and delete resources.
tags:
  - name: Authentication
    description: Obtain and verify session tokens.
  - name: API Keys
    description: Issue, list, and revoke property-scoped API keys for agents and integrations.
  - name: Players
    description: The unified, gaming-specific player database.
  - name: Campaigns
    description: Create and send email / SMS campaigns.
  - name: Compliance
    description: Consent capture and pre-send compliance checks (TCPA).
  - name: Data Privacy
    description: CCPA / GDPR data export and deletion.
  - name: Webhooks
    description: Subscribe to PlayerOS events.
security:
  - ApiKeyAuth: []
  - BearerAuth: []
paths:
  /api/auth/login:
    post:
      tags:
        - Authentication
      summary: Authenticate a user and receive a JWT
      operationId: login
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /api/auth/me:
    post:
      tags:
        - Authentication
      summary: Get the current authenticated user
      operationId: getCurrentUser
      security:
        - BearerAuth: []
      x-required-scope: read
      responses:
        '200':
          description: Current user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/functions/create-api-key:
    post:
      tags:
        - API Keys
      summary: Create a property-scoped API key
      description: >-
        Returns the raw secret **once** (`pk_…`). Store it securely — only a hash is retained.
        Caller must be a platform admin or a property admin/manager.
      operationId: createApiKey
      security:
        - BearerAuth: []
      x-required-scope: write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
      responses:
        '200':
          description: Key created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateApiKeyResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/functions/list-api-keys:
    post:
      tags:
        - API Keys
      summary: List API keys for a property
      description: >-
        Returns key metadata only (name, prefix, scopes, status, last-used). Secrets are never
        returned.
      operationId: listApiKeys
      security:
        - BearerAuth: []
      x-required-scope: read
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - property_id
              properties:
                property_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/functions/revoke-api-key:
    post:
      tags:
        - API Keys
      summary: Revoke an API key
      description: Deactivates the key immediately. Any integration using it stops working.
      operationId: revokeApiKey
      security:
        - BearerAuth: []
      x-required-scope: write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - key_id
              properties:
                key_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Revoked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Key not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/players:
    get:
      tags:
        - Players
      summary: List players
      description: >-
        Returns players for the authenticated property. Supports PostgREST-style filters, ordering,
        and pagination via query parameters, e.g.
        `?email.ilike=%jane%&status.eq=active&order=created_at.desc&limit=50`.
      operationId: listPlayers
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/PropertyId'
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 1000
          description: Max rows to return.
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
          description: Rows to skip.
        - name: order
          in: query
          schema:
            type: string
            example: created_at.desc
          description: Column and direction, e.g. `created_at.desc`.
        - name: email
          in: query
          schema:
            type: string
          description: Filter, e.g. `email.ilike=%jane%` (pass as `email.ilike`).
      responses:
        '200':
          description: Players
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Player'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
        - Players
      summary: Create a player
      operationId: createPlayer
      x-required-scope: write
      parameters:
        - $ref: '#/components/parameters/PropertyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlayerInput'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Player'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/players/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags:
        - Players
      summary: Get a player by ID
      operationId: getPlayer
      x-required-scope: read
      responses:
        '200':
          description: Player
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Player'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      tags:
        - Players
      summary: Partially update a player
      operationId: updatePlayer
      x-required-scope: write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlayerInput'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Player'
    delete:
      tags:
        - Players
      summary: Delete a player
      operationId: deletePlayer
      x-required-scope: write
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
  /api/functions/send-campaign:
    post:
      tags:
        - Campaigns
      summary: Send an email campaign
      description: >-
        Enqueues an email campaign to filtered recipients for async SQS delivery. Every recipient
        passes a pre-send compliance check (suppression list, DNC, active consent).
      operationId: sendCampaign
      x-required-scope: write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/ApiVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - campaignId
                - propertyId
              properties:
                campaignId:
                  type: string
                  format: uuid
                propertyId:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Queued
          headers:
            API-Version:
              $ref: '#/components/headers/ApiVersion'
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/ServerError'
  /api/functions/validate-batch:
    post:
      tags:
        - Players
      summary: Start a batch email-validation job (async)
      description: >-
        Validates a batch of email addresses asynchronously. Returns `202 Accepted` with a job id;
        poll `GET /api/validation_jobs/{id}` until `status` is `completed` or `failed`.
      operationId: startValidationJob
      x-required-scope: write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - property_id
              properties:
                property_id:
                  type: string
                  format: uuid
                scope:
                  type: string
                  enum:
                    - never_validated
                    - stale
                    - all
      responses:
        '202':
          description: Job accepted
          headers:
            Location:
              description: URL of the job-status resource.
              schema:
                type: string
                format: uri
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AsyncJob'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/validation_jobs/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags:
        - Players
      summary: Get async job status
      description: >-
        Poll the status of an asynchronous job (email validation, CSV import). Returns the job with
        its current `status` and progress.
      operationId: getValidationJob
      x-required-scope: read
      responses:
        '200':
          description: Job status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AsyncJob'
        '404':
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/functions/send-sms-campaign:
    post:
      tags:
        - Campaigns
      summary: Send an SMS campaign
      description: >-
        Sends an SMS campaign via Telnyx. TCPA pre-check automatically blocks suppressed and DNC
        numbers.
      operationId: sendSmsCampaign
      x-required-scope: write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - campaignId
                - propertyId
              properties:
                campaignId:
                  type: string
                  format: uuid
                propertyId:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
  /api/functions/consent-record:
    post:
      tags:
        - Compliance
      summary: Record a consent event
      description: Stores a consent event with full audit trail (text, version, source, IP, user agent).
      operationId: recordConsent
      x-required-scope: write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConsentRecordInput'
      responses:
        '200':
          description: Recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
  /api/functions/compliance-check:
    post:
      tags:
        - Compliance
      summary: Pre-send compliance check
      description: >-
        Validates one or many phone numbers against the suppression list, DNC list, and active
        consent before sending.
      operationId: complianceCheck
      x-required-scope: read
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - propertyId
                - channel
              properties:
                propertyId:
                  type: string
                  format: uuid
                channel:
                  type: string
                  enum:
                    - sms
                    - email
                    - voice
                    - push
                phone:
                  type: string
                  example: '+15551234567'
                  description: Single-number check.
                phones:
                  type: array
                  items:
                    type: string
                  description: Bulk check.
      responses:
        '200':
          description: Result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComplianceResult'
  /api/functions/data-export:
    post:
      tags:
        - Data Privacy
      summary: Export a player's data (CCPA / GDPR)
      description: >-
        Returns all data held for a player across players, consent records, campaign sends, game
        plays, inbox messages, journey enrollments, suppression and DNC entries, and social shares.
      operationId: exportPlayerData
      x-required-scope: read
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - propertyId
                - playerId
              properties:
                propertyId:
                  type: string
                  format: uuid
                playerId:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Export
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/functions/data-deletion:
    post:
      tags:
        - Data Privacy
      summary: Delete a player's data (right to erasure)
      description: >-
        Cascade-deletes a player's data and anonymizes analytics-bearing rows (campaign sends, game
        plays). Writes an audit log entry.
      operationId: deletePlayerData
      x-required-scope: write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - propertyId
                - playerId
              properties:
                propertyId:
                  type: string
                  format: uuid
                playerId:
                  type: string
                  format: uuid
                reason:
                  type: string
                requestedBy:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
  /api/webhook_endpoints:
    get:
      tags:
        - Webhooks
      summary: List webhook endpoints
      description: >-
        PlayerOS delivers signed event callbacks (campaign, player, consent, and game events) to
        registered endpoints with retry and HMAC signature verification. Manage subscriptions via
        standard CRUD on the `webhook_endpoints` table.
      operationId: listWebhookEndpoints
      x-required-scope: read
      parameters:
        - $ref: '#/components/parameters/PropertyId'
      responses:
        '200':
          description: Endpoints
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WebhookEndpoint'
    post:
      tags:
        - Webhooks
      summary: Register a webhook endpoint
      operationId: createWebhookEndpoint
      x-required-scope: write
      parameters:
        - $ref: '#/components/parameters/PropertyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEndpointInput'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: >-
        Property-scoped API key (`pk_…`). Generate one in **Settings → API Keys** on
        https://playeros.ai. Each key is bound to a single property and carries `read` and/or
        `write` scopes (see `x-scopes`). This is the recommended authentication method for AI agents
        and server-to-server integrations.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        Cognito-issued user JWT for interactive dashboard sessions. Obtain via `POST
        /api/auth/login`.
  parameters:
    PropertyId:
      name: X-Property-Id
      in: header
      required: false
      schema:
        type: string
        format: uuid
      description: >-
        Selects the property (tenant) for the request. Optional for API keys (already
        property-bound); required for JWT users with access to multiple properties.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
      description: >-
        A unique key (e.g. a UUID) that makes a mutating request safely retryable. If the same key
        is replayed within 24 hours, the original response is returned (with an
        `Idempotency-Replayed: true` header) instead of performing the write again.
    ApiVersion:
      name: API-Version
      in: header
      required: false
      schema:
        type: string
        default: '2026-06-05'
      description: >-
        Date-based API version to pin (e.g. `2026-06-05`). Defaults to the current version, which is
        echoed in the `API-Version` response header. Breaking changes are announced via
        `Deprecation` and `Sunset` (RFC 8594) response headers before removal.
  responses:
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Authenticated but not permitted for this property or scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooManyRequests:
      description: >-
        Rate limited — retry after the `retry_after` seconds (see also the `X-RateLimit-Reset`
        header).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ServerError:
      description: >-
        Transient server error. Safe to retry with exponential backoff; idempotent and read requests
        can be retried freely, and writes can be retried safely with an `Idempotency-Key` header.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  headers:
    ApiVersion:
      description: Date-based API version of this response.
      schema:
        type: string
        example: '2026-06-05'
    RateLimitLimit:
      description: Per-key request rate-limit ceiling.
      schema:
        type: integer
        example: 100
    RateLimitRemaining:
      description: Approximate requests remaining in the current window.
      schema:
        type: integer
        example: 99
    RateLimitReset:
      description: Unix time (seconds) at which the rate-limit window resets.
      schema:
        type: integer
        example: 1717000000
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message.
        retry_after:
          type: integer
          description: Seconds to wait before retrying (on 429).
      required:
        - error
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
    AsyncJob:
      type: object
      description: An asynchronous job. Poll the job-status endpoint until `status` is `completed` or `failed`.
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - queued
            - processing
            - completed
            - failed
        type:
          type: string
          example: email_validation
        total:
          type: integer
          nullable: true
        processed:
          type: integer
          nullable: true
        status_url:
          type: string
          format: uri
          description: Poll this URL for status.
        created_at:
          type: string
          format: date-time
    LoginRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
          example: user@casino.com
        password:
          type: string
          format: password
    LoginResponse:
      type: object
      properties:
        token:
          type: string
          description: 'JWT to send as `Authorization: Bearer <token>`.'
        user:
          $ref: '#/components/schemas/User'
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        role:
          type: string
          example: authenticated
    CreateApiKeyRequest:
      type: object
      required:
        - property_id
        - name
      properties:
        property_id:
          type: string
          format: uuid
          description: Property the key is scoped to.
        name:
          type: string
          example: Acme AI agent
          description: Human label for the key.
        scopes:
          type: array
          items:
            type: string
            enum:
              - read
              - write
          default:
            - read
            - write
          description: Permissions granted to the key.
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: Optional expiry; null = never.
    CreateApiKeyResponse:
      type: object
      properties:
        success:
          type: boolean
        api_key:
          type: string
          example: pk_AbC123…
          description: The raw secret — shown ONCE. Store it securely.
        key:
          $ref: '#/components/schemas/ApiKey'
    ApiKey:
      type: object
      description: API key metadata. The secret itself is never returned after creation.
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        key_prefix:
          type: string
          example: pk_AbC123
          description: First 12 characters, for identification.
        scopes:
          type: array
          items:
            type: string
            enum:
              - read
              - write
        rate_limit:
          type: integer
          example: 100
          description: Configured request rate limit for the key.
        is_active:
          type: boolean
        expires_at:
          type: string
          format: date-time
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    Player:
      type: object
      description: A player record with gaming-specific fields.
      properties:
        id:
          type: string
          format: uuid
        property_id:
          type: string
          format: uuid
        player_id:
          type: string
          description: Operator's external player identifier.
        email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          nullable: true
        first_name:
          type: string
          nullable: true
        last_name:
          type: string
          nullable: true
        tier:
          type: string
          nullable: true
          description: Loyalty tier / status.
        adt:
          type: number
          nullable: true
          description: Average daily theoretical.
        coin_in:
          type: number
          nullable: true
        status:
          type: string
          example: active
        created_at:
          type: string
          format: date-time
    PlayerInput:
      type: object
      properties:
        player_id:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        tier:
          type: string
        adt:
          type: number
        coin_in:
          type: number
        status:
          type: string
    ConsentRecordInput:
      type: object
      required:
        - propertyId
        - playerId
        - channel
        - consentGiven
      properties:
        propertyId:
          type: string
          format: uuid
        playerId:
          type: string
          format: uuid
        channel:
          type: string
          enum:
            - sms
            - email
            - voice
            - push
        consentGiven:
          type: boolean
        consentSource:
          type: string
          example: web_form
        consentText:
          type: string
          example: I agree to receive SMS marketing
        consentVersion:
          type: string
          example: v2.0
        ipAddress:
          type: string
          example: 192.168.1.1
    ComplianceResult:
      type: object
      properties:
        allowed:
          type: boolean
          description: Whether the send is permitted.
        reasons:
          type: array
          items:
            type: string
          description: Why a number was blocked (suppressed, dnc, no_consent).
        results:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Per-number results for a bulk check.
    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
        property_id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
          example:
            - campaign.sent
            - player.created
            - consent.revoked
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
    WebhookEndpointInput:
      type: object
      required:
        - url
        - events
      properties:
        url:
          type: string
          format: uri
          example: https://example.com/hooks/playeros
        events:
          type: array
          items:
            type: string
          example:
            - campaign.sent
            - player.created
