{
  "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\nprogrammatic access to your player database, multi-channel campaigns (email, SMS,\nAI voice, web push, and the branded Player Inbox), journey automation, interactive\ngames, and TCPA / CCPA / GDPR compliance tooling.\n\n## Authentication for AI agents & integrations\n\nPlayerOS uses **property-scoped API keys**. Each key belongs to a single property\n(tenant) and carries `read` and/or `write` scopes.\n\n1. Sign in at https://playeros.ai and open **Settings → API Keys**.\n2. Click **Generate Key**, give it a name (e.g. \"Acme AI agent\"), and copy the\n   secret. It starts with `pk_` and is shown **only once**.\n3. Send it on every request as the `X-Api-Key` header:\n\n```bash\ncurl https://api.playeros.ai/api/players \\\n  -H \"X-Api-Key: pk_your_key_here\"\n```\n\nKeys can be revoked at any time from the same screen, or programmatically via\n`POST /api/functions/revoke-api-key`. Interactive dashboard sessions may instead\npresent a Cognito JWT as `Authorization: Bearer <token>`.\n\n## Scopes\n\n| Scope   | Grants                                                              |\n|---------|--------------------------------------------------------------------|\n| `read`  | List and retrieve resources (players, campaigns, content, stats).  |\n| `write` | Create, update, send, and delete resources.                        |\n\nRequest the narrowest scope set your integration needs. Keys created from the\ndashboard default to `[\"read\",\"write\"]`; pass an explicit `scopes` array to\n`create-api-key` to issue a read-only key.\n\n## Base URL\n\n`https://api.playeros.ai` — every endpoint lives under `/api`.\n\n## Multi-tenancy\n\nEvery API key is bound to one property, so requests are scoped automatically.\nA user/JWT with access to multiple properties selects one with the\n`X-Property-Id: <uuid>` header.\n\n## Rate limits\n\nEach API key carries a configurable rate-limit value (default 100). Every response\nincludes advisory rate-limit headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`,\nand `X-RateLimit-Reset` (Unix seconds). Authentication endpoints (`/api/auth/login`,\npassword reset) are additionally throttled per IP and per account; throttled\nrequests return `429` with a `retry_after` hint.\n\n## Versioning & deprecation\n\nThe API is **date-versioned**. Every response carries an `API-Version` header\n(current: `2026-06-05`). Changes are additive wherever possible. When an endpoint\nor field is deprecated, responses carry a `Deprecation` header and, where a removal\ndate is set, a `Sunset` header (RFC 8594); deprecations are announced before removal.\n\n## Webhooks & signature verification\n\nPlayerOS POSTs signed event callbacks to your registered `webhook_endpoints`. Each\ndelivery includes `X-PlayerOS-Event`, `X-PlayerOS-Timestamp` (Unix seconds), and\n`X-PlayerOS-Signature: v1=<hmac>`. Verify it by computing\n`v1=` + HMAC-SHA256(your endpoint's signing secret, `\"{timestamp}.{raw_body}\"`) (hex)\nand comparing in constant time. Reject deliveries whose timestamp is outside a few\nminutes to prevent replay.\n\n## Conventions\n\nMost write operations are `POST /api/functions/{name}`. Table data uses RESTful\nCRUD at `/api/{table}` with PostgREST-style filters\n(`?email.ilike=%jane%&status.eq=active&order=created_at.desc&limit=50`). Operators:\n`eq, neq, gt, gte, lt, lte, like, ilike, in, is, not`.\n\n## Idempotency\n\nMutating requests (POST/PUT/PATCH/DELETE) accept an `Idempotency-Key` header. Send a\nunique key (e.g. a UUID) per logical operation; if the same key is replayed within 24\nhours, the original response is returned (with `Idempotency-Replayed: true`) instead of\nperforming the write again. Safe to retry network failures without duplicating effects.\n\n## Errors & retries\n\nErrors return a JSON `{ \"error\": \"...\" }` body. `4xx` are client errors — fix the\nrequest before retrying. `5xx` (e.g. `500`, `503`) are transient — retry with\nexponential backoff. `429` includes a `retry_after` hint and an `X-RateLimit-Reset`\nheader; back off until then. Every response carries an `API-Version` header.\n\n## Async jobs\n\nBatch operations (e.g. email validation, CSV import) are processed asynchronously:\nthey create a job row you poll for status — `GET /api/validation_jobs/{id}` or\n`GET /api/import_jobs/{id}` — until `status` is `completed` or `failed`.\n\n## Model Context Protocol (MCP)\n\nAn MCP server (Streamable HTTP) is available at `https://api.playeros.ai/api/mcp`\nfor AI agents. Discovery: https://playeros.ai/.well-known/mcp.json. Authenticate with\nthe 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"
            ]
          }
        }
      }
    }
  }
}
