{
  "openapi": "3.1.0",
  "info": {
    "title": "OneSocial API",
    "version": "1.0.0",
    "summary": "Schedule and publish social posts across Bluesky, Telegram, Discord, and Mastodon, plus analytics, inbox, contacts, broadcasts, sequences, automations, and a paid Ads Manager across Meta, Google, TikTok, LinkedIn, Pinterest, and X.",
    "description": "One REST API (and a parity MCP server at /api/mcp) to connect social accounts, schedule/publish posts, track analytics, manage a unified inbox (comments/mentions/reviews/DMs), maintain a contacts CRM with custom fields, run broadcasts/sequences/comment-to-DM automations, and run a paid Ads Manager (ad accounts, campaigns, ads, audiences, tracking tags) across Meta, Google, TikTok, LinkedIn, Pinterest, and X. All endpoints are versioned under /api/v1 (except the D1 media file server at /api/v1/media/file/{tenantId}/{filename}, which is a plain unauthenticated static-file GET), authenticate via an `sk_`-prefixed API key as an HTTP Bearer token, and respond with the envelope `{ data }` for single resources or `{ data, page, limit, total }` for lists. Errors are `{ error: { code, message } }` with a matching HTTP status. Scopes are a closed allowlist (see `@/lib/scopes`): a key needs the exact scope string for an action (or `*`/`admin` for full access) — an empty scopes array grants nothing. A handful of read endpoints (comments, mentions, reviews, broadcasts, sequences, automations lists) do not yet enforce their `:read` scope — any authenticated key can call them; this is called out per-operation below. Ad-network credentials are entirely optional server-side config (`<NETWORK>_ADS_*` env vars); every Ads Manager endpoint is fully exercisable with zero ad-network credentials configured — unconfigured live calls fail closed and the local DB state is persisted regardless (see the AdAccounts/Ads/AdCampaigns/AdAudiences/TrackingTags operations below for specifics).",
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "/api",
      "description": "Relative to whichever origin this spec was fetched from."
    },
    {
      "url": "http://localhost:3000/api",
      "description": "Local development."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Profiles",
      "description": "Per-tenant containers for accounts and queue slots."
    },
    {
      "name": "Accounts",
      "description": "Connected social accounts and account groups."
    },
    {
      "name": "Connect",
      "description": "OAuth and direct-credential flows for connecting an account to a platform."
    },
    {
      "name": "Posts",
      "description": "Create, schedule, publish, and retry posts across platforms."
    },
    {
      "name": "Queue",
      "description": "Recurring auto-schedule slots."
    },
    {
      "name": "Media",
      "description": "Pass-through media storage (no transcoding)."
    },
    {
      "name": "Webhooks",
      "description": "Outbound event subscriptions and delivery history."
    },
    {
      "name": "ApiKeys",
      "description": "sk_ API key lifecycle."
    },
    {
      "name": "User",
      "description": "The currently authenticated actor."
    },
    {
      "name": "Usage",
      "description": "Per-tenant usage counters."
    },
    {
      "name": "Analytics",
      "description": "Per-day post/account metric rollups, daily series, and best-time-to-post suggestions."
    },
    {
      "name": "Comments",
      "description": "Inbound comments on connected accounts' native posts, and replies to them."
    },
    {
      "name": "Mentions",
      "description": "Inbound @-mentions across connected accounts."
    },
    {
      "name": "Reviews",
      "description": "Platform reviews (e.g. Google Business style) and replies to them."
    },
    {
      "name": "Messages",
      "description": "DM conversations and messages across connected accounts."
    },
    {
      "name": "Contacts",
      "description": "CRM contact records, their channels, and bulk import."
    },
    {
      "name": "CustomFields",
      "description": "Tenant-defined custom field schemas for Contact.customFields."
    },
    {
      "name": "Broadcasts",
      "description": "One-shot campaign messages sent to many contacts."
    },
    {
      "name": "Sequences",
      "description": "Multi-step drip campaigns with delay/message/broadcast steps."
    },
    {
      "name": "Automations",
      "description": "Comment-to-DM automation rules and their trigger logs."
    },
    {
      "name": "AdAccounts",
      "description": "Per-network connected ad accounts (Meta, Google, TikTok, LinkedIn, Pinterest, X)."
    },
    {
      "name": "AdCampaigns",
      "description": "Ad campaigns: objective, budget, and pause/resume state."
    },
    {
      "name": "Ads",
      "description": "Individual ads, including boosted posts, lead forms, targeting search, and conversion forwarding."
    },
    {
      "name": "AdAudiences",
      "description": "Custom/lookalike/saved audiences and hashed-member uploads."
    },
    {
      "name": "TrackingTags",
      "description": "Pixels, Conversions API tags, and gtag tags, plus their recorded events."
    }
  ],
  "paths": {
    "/v1/profiles": {
      "get": {
        "tags": [
          "Profiles"
        ],
        "summary": "List profiles",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of profiles.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Profile"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Profiles"
        ],
        "summary": "Create a profile",
        "description": "Requires scope `profiles:write`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Profile created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/profiles/{id}": {
      "get": {
        "tags": [
          "Profiles"
        ],
        "summary": "Get a profile",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Profile id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Profiles"
        ],
        "summary": "Update a profile",
        "description": "Requires scope `profiles:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Profile id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Profiles"
        ],
        "summary": "Delete a profile",
        "description": "Requires scope `profiles:write`. Cascades to the profile's accounts and queue slots.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Profile id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/accounts": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List accounts",
        "description": "Accounts are created via the connect flow, not directly.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "profileId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AccountStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Account"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/accounts/{id}": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get an account",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Accounts"
        ],
        "summary": "Update an account",
        "description": "Requires scope `accounts:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "profileId": {
                    "type": "string",
                    "description": "Move the account to a different profile owned by this tenant."
                  },
                  "username": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "displayName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Accounts"
        ],
        "summary": "Disconnect an account",
        "description": "Requires scope `accounts:write`. Soft-disconnect: clears stored credentials and flips status to disconnected; PostTarget history is preserved.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Disconnected account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/accounts/{id}/health": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get account health",
        "description": "DB-derived health snapshot (does not make a live call to the platform): connection status, whether we still hold encrypted credentials, and the last recorded error.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Health snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AccountHealth"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/account-groups": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List account groups",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "profileId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of account groups.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AccountGroup"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Create an account group",
        "description": "Requires scope `account-groups:write`. Bundles accounts under a profile for bulk targeting.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "profileId",
                  "name"
                ],
                "properties": {
                  "profileId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "accountIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account group created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AccountGroup"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/account-groups/{id}": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get an account group",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Account group id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The account group.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AccountGroup"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Accounts"
        ],
        "summary": "Update an account group",
        "description": "Requires scope `account-groups:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Account group id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "accountIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated account group.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AccountGroup"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Accounts"
        ],
        "summary": "Delete an account group",
        "description": "Requires scope `account-groups:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Account group id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/connect/authorize": {
      "post": {
        "tags": [
          "Connect"
        ],
        "summary": "Start connecting an account",
        "description": "Requires scope `accounts:connect`. For platform=mastodon this dynamically registers an OAuth app with the given instance and returns a real authUrl; for every other platform it returns mode='manual' with the fields to collect and POST to /connect/manual.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platform",
                  "profileId"
                ],
                "properties": {
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "profileId": {
                    "type": "string"
                  },
                  "instance": {
                    "type": "string",
                    "description": "Mastodon instance host (e.g. \"mastodon.social\"). Required only when platform=mastodon."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Connect flow details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "For platform=mastodon, mode is 'oauth' and authUrl is a real Mastodon authorization URL to redirect the user to (completes at GET /connect/callback). For every other platform, mode is 'manual' — collect `fields` from the user and POST them to /connect/manual.",
                      "properties": {
                        "platform": {
                          "$ref": "#/components/schemas/Platform"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "oauth",
                            "manual"
                          ]
                        },
                        "authUrl": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri"
                        },
                        "instance": {
                          "type": "string"
                        },
                        "manualEndpoint": {
                          "type": "string",
                          "examples": [
                            "/api/v1/connect/manual"
                          ]
                        },
                        "fields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/connect/manual": {
      "post": {
        "tags": [
          "Connect"
        ],
        "summary": "Connect an account with direct credentials",
        "description": "Requires scope `accounts:connect`. Bypasses OAuth: validates the given credentials via the platform's PublisherAdapter.connectAccount() and persists them AES-256-GCM encrypted. Upserts on (tenantId, profileId, platform, platformAccountId).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platform",
                  "profileId",
                  "credentials"
                ],
                "description": "Shape of `credentials` is discriminated by `platform`.",
                "properties": {
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "profileId": {
                    "type": "string"
                  },
                  "credentials": {
                    "oneOf": [
                      {
                        "type": "object",
                        "title": "BlueskyCredentials",
                        "required": [
                          "identifier",
                          "appPassword"
                        ],
                        "properties": {
                          "identifier": {
                            "type": "string",
                            "description": "Bluesky handle or DID."
                          },
                          "appPassword": {
                            "type": "string"
                          },
                          "service": {
                            "type": "string",
                            "format": "uri",
                            "description": "Defaults to https://bsky.social."
                          }
                        }
                      },
                      {
                        "type": "object",
                        "title": "TelegramCredentials",
                        "required": [
                          "botToken",
                          "chatId"
                        ],
                        "properties": {
                          "botToken": {
                            "type": "string"
                          },
                          "chatId": {
                            "type": "string"
                          }
                        }
                      },
                      {
                        "type": "object",
                        "title": "DiscordCredentials",
                        "required": [
                          "webhookUrl"
                        ],
                        "properties": {
                          "webhookUrl": {
                            "type": "string",
                            "format": "uri"
                          }
                        }
                      },
                      {
                        "type": "object",
                        "title": "MastodonCredentials",
                        "required": [
                          "instance",
                          "accessToken"
                        ],
                        "properties": {
                          "instance": {
                            "type": "string"
                          },
                          "accessToken": {
                            "type": "string"
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing account reconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "New account connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/connect/callback": {
      "get": {
        "tags": [
          "Connect"
        ],
        "summary": "Mastodon OAuth callback",
        "description": "Redirect target for the Mastodon OAuth flow started by POST /connect/authorize. Not called with a Bearer sk_ key — tenant/profile authorization is instead proven by successfully decrypting the `state` query param, which was minted server-side after the original caller passed authenticate()+requireScope('accounts:connect').",
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "error",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Existing account reconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "New account connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/posts": {
      "get": {
        "tags": [
          "Posts"
        ],
        "summary": "List posts",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PostStatus"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of posts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Post"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Posts"
        ],
        "summary": "Create (and optionally schedule/publish) a post",
        "description": "Requires scope `posts:write`. One PostTarget is created per accountId. If `publishNow` is true the post is claimed by the next cron tick immediately; if `scheduledFor` is set it publishes at that time; otherwise it is saved as a draft. Publishing always goes through the scheduler (claimDuePosts/publishPost) — never directly through adapters.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content",
                  "accountIds"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1
                  },
                  "accountIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "description": "Accounts (owned by this tenant) to publish this post to — one PostTarget is created per account."
                  },
                  "mediaAssetIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Previously uploaded MediaAsset ids."
                  },
                  "scheduledFor": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Omit together with publishNow=false to save as a draft."
                  },
                  "publishNow": {
                    "type": "boolean",
                    "default": false
                  },
                  "source": {
                    "type": "string",
                    "default": "api"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Post created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Post"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/posts/{id}": {
      "get": {
        "tags": [
          "Posts"
        ],
        "summary": "Get a post",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The post, including its targets and media.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Post"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Posts"
        ],
        "summary": "Update a post",
        "description": "Requires scope `posts:write`. Only posts in status draft or scheduled may be updated.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "description": "Only posts in status draft or scheduled may be updated.",
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1
                  },
                  "accountIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  },
                  "mediaAssetIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "scheduledFor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated post.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Post"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "tags": [
          "Posts"
        ],
        "summary": "Delete a post",
        "description": "Requires scope `posts:write`. Cascades to its PostTarget and PostMedia rows.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/posts/{id}/retry": {
      "post": {
        "tags": [
          "Posts"
        ],
        "summary": "Retry a post's failed targets",
        "description": "Requires scope `posts:write`. Calls retryPost() from the scheduler — only re-attempts PostTarget rows currently in status failed; published targets are never re-sent.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Post after retry, with updated target statuses.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Post"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/posts/validate": {
      "post": {
        "tags": [
          "Posts"
        ],
        "summary": "Pre-flight validate content against target platforms",
        "description": "Any authenticated API key (no extra scope). Read-only: reports whether `content` fits each target platform's character limit and whether the referenced `accountId`s are valid/connected, without creating anything.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content",
                  "platforms"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1
                  },
                  "platforms": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "platform"
                      ],
                      "properties": {
                        "platform": {
                          "$ref": "#/components/schemas/Platform"
                        },
                        "accountId": {
                          "type": "string",
                          "description": "When given, also checked for tenant ownership, platform match, and isConnected."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-platform validation results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "valid": {
                          "type": "boolean",
                          "description": "True iff every entry in `results` is ok."
                        },
                        "results": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "platform": {
                                "$ref": "#/components/schemas/Platform"
                              },
                              "accountId": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "length": {
                                "type": "integer"
                              },
                              "limit": {
                                "type": "integer",
                                "description": "Platform character limit."
                              },
                              "ok": {
                                "type": "boolean"
                              },
                              "error": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/queue": {
      "get": {
        "tags": [
          "Queue"
        ],
        "summary": "List queue slots",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "profileId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of recurring queue slots.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/QueueSlot"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Queue"
        ],
        "summary": "Create a queue slot",
        "description": "Requires scope `queue:write`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "profileId",
                  "dayOfWeek",
                  "timeOfDay"
                ],
                "properties": {
                  "profileId": {
                    "type": "string"
                  },
                  "dayOfWeek": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 6
                  },
                  "timeOfDay": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1439
                  },
                  "timezone": {
                    "type": "string",
                    "default": "UTC"
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Queue slot created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QueueSlot"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/queue/preview": {
      "get": {
        "tags": [
          "Queue"
        ],
        "summary": "Preview upcoming queue slot occurrences",
        "description": "Any authenticated API key (no extra scope). Read-only projection of a profile's enabled QueueSlots into their next N upcoming UTC occurrences; does not read or write any Post.",
        "parameters": [
          {
            "name": "profileId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "count",
            "in": "query",
            "description": "Defaults to 10, capped at 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "ISO 8601 instant to project from. Defaults to now.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Upcoming occurrences, soonest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "queueSlotId": {
                            "type": "string"
                          },
                          "dayOfWeek": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 6
                          },
                          "timeOfDay": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 1439
                          },
                          "timezone": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/queue/{id}": {
      "get": {
        "tags": [
          "Queue"
        ],
        "summary": "Get a queue slot",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Queue slot id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The queue slot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QueueSlot"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Queue"
        ],
        "summary": "Update a queue slot",
        "description": "Requires scope `queue:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Queue slot id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "dayOfWeek": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 6
                  },
                  "timeOfDay": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1439
                  },
                  "timezone": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated queue slot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QueueSlot"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Queue"
        ],
        "summary": "Delete a queue slot",
        "description": "Requires scope `queue:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Queue slot id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/media/upload": {
      "post": {
        "tags": [
          "Media"
        ],
        "summary": "Upload media",
        "description": "Requires scope `media:write`. Accepts multipart/form-data (field `file`) or a JSON body with `url` (fetched pass-through and re-hosted) or `dataBase64`. Bytes are stored as-is — no transcoding. 25MB max.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "altText": {
                    "type": "string"
                  },
                  "width": {
                    "type": "integer"
                  },
                  "height": {
                    "type": "integer"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Alternative to multipart/form-data: provide either `url` (fetched pass-through and re-hosted) or `dataBase64`.",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "dataBase64": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mimeType": {
                    "type": "string"
                  },
                  "altText": {
                    "type": "string"
                  },
                  "width": {
                    "type": "integer"
                  },
                  "height": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "MediaAsset created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MediaAsset"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/media/presign": {
      "post": {
        "tags": [
          "Media"
        ],
        "summary": "Pre-negotiate an upload slot",
        "description": "Requires scope `media:write`. Returns a short-lived, tenant-bound token plus the URL/method/field name to POST bytes to (currently /v1/media/upload — swappable for a real object-storage presigned URL later without changing callers).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string"
                  },
                  "mimeType": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Presign details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "uploadUrl": {
                          "type": "string",
                          "format": "uri"
                        },
                        "method": {
                          "type": "string",
                          "examples": [
                            "POST"
                          ]
                        },
                        "fieldName": {
                          "type": "string",
                          "examples": [
                            "file"
                          ]
                        },
                        "token": {
                          "type": "string",
                          "description": "Opaque, AES-256-GCM-encrypted upload token bound to this tenant/key."
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/validate/media-url": {
      "post": {
        "tags": [
          "Media"
        ],
        "summary": "Validate a remote media URL",
        "description": "Read-only pre-flight check (any valid API key, no extra scope): confirms the URL is reachable, resolves its mime type/size, without downloading or storing it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "valid": {
                          "type": "boolean"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "mimeType": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "sizeBytes": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "type": {
                          "$ref": "#/components/schemas/MediaType"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of webhooks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook",
        "description": "Requires scope `webhooks:write`. The response is the ONLY place `secret` (used to compute X-OneSocial-Signature) is ever returned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WebhookEvent"
                    },
                    "minItems": 1
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookCreated"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Get a webhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Webhook id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Update a webhook",
        "description": "Requires scope `webhooks:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Webhook id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WebhookEvent"
                    },
                    "minItems": 1
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook",
        "description": "Requires scope `webhooks:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Webhook id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhooks/{id}/logs": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List a webhook's delivery history",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Webhook id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated WebhookDelivery rows for this webhook, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhooks/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test delivery",
        "description": "Requires scope `webhooks:write`. Sends a one-off `webhook.test` delivery to the given webhook regardless of its subscribed events list.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "webhookId"
                ],
                "properties": {
                  "webhookId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delivery attempt result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookDelivery"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/api-keys": {
      "get": {
        "tags": [
          "ApiKeys"
        ],
        "summary": "List API keys",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of API keys (never includes keyHash).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "ApiKeys"
        ],
        "summary": "Create an API key",
        "description": "Requires scope `api-keys:write`. The raw sk_ value is returned ONCE in this response and is never retrievable again.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 50,
                    "default": [],
                    "description": "Empty array grants full access."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ApiKeyCreated"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/api-keys/{id}": {
      "delete": {
        "tags": [
          "ApiKeys"
        ],
        "summary": "Revoke an API key",
        "description": "Requires scope `api-keys:write`. Sets revokedAt; the key immediately stops authenticating.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "API key id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/user": {
      "get": {
        "tags": [
          "User"
        ],
        "summary": "Get the current actor",
        "description": "Returns the tenant and API key identity behind the bearer sk_ token used on this request.",
        "responses": {
          "200": {
            "description": "Current actor identity.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "tenantId": {
                          "type": "string"
                        },
                        "scopes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "tenant": {
                          "$ref": "#/components/schemas/Tenant"
                        },
                        "apiKey": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "prefix": {
                              "type": "string"
                            },
                            "scopes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "lastUsedAt": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time"
                            },
                            "createdAt": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Get usage summary",
        "description": "Account and post counters for the authenticated tenant. `from`/`to` default to the current UTC calendar month.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "object",
                          "properties": {
                            "from": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "to": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        },
                        "accounts": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "connected": {
                              "type": "integer"
                            },
                            "byPlatform": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "posts": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "scheduled": {
                              "type": "integer"
                            },
                            "published": {
                              "type": "integer"
                            },
                            "partial": {
                              "type": "integer"
                            },
                            "failed": {
                              "type": "integer"
                            },
                            "createdThisPeriod": {
                              "type": "integer"
                            },
                            "publishedThisPeriod": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/analytics": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Get an analytics rollup",
        "description": "Requires scope `analytics:read`. Sums the typed PostMetric counters across `since`/`until` (default: trailing 30 days).",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The flat counters (impressions/reach/.../saves) are duplicated at the top level (mirroring GET /v1/usage's flat+nested convention) and inside `totals`.",
                      "properties": {
                        "impressions": {
                          "type": "integer"
                        },
                        "reach": {
                          "type": "integer"
                        },
                        "engagements": {
                          "type": "integer"
                        },
                        "likes": {
                          "type": "integer"
                        },
                        "comments": {
                          "type": "integer"
                        },
                        "shares": {
                          "type": "integer"
                        },
                        "clicks": {
                          "type": "integer"
                        },
                        "saves": {
                          "type": "integer"
                        },
                        "period": {
                          "type": "object",
                          "properties": {
                            "since": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "until": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        },
                        "totals": {
                          "type": "object",
                          "properties": {
                            "impressions": {
                              "type": "integer"
                            },
                            "reach": {
                              "type": "integer"
                            },
                            "engagements": {
                              "type": "integer"
                            },
                            "likes": {
                              "type": "integer"
                            },
                            "comments": {
                              "type": "integer"
                            },
                            "shares": {
                              "type": "integer"
                            },
                            "clicks": {
                              "type": "integer"
                            },
                            "saves": {
                              "type": "integer"
                            }
                          }
                        },
                        "byPlatform": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "platform": {
                                "$ref": "#/components/schemas/Platform"
                              },
                              "impressions": {
                                "type": "integer"
                              },
                              "reach": {
                                "type": "integer"
                              },
                              "engagements": {
                                "type": "integer"
                              },
                              "likes": {
                                "type": "integer"
                              },
                              "comments": {
                                "type": "integer"
                              },
                              "shares": {
                                "type": "integer"
                              },
                              "clicks": {
                                "type": "integer"
                              },
                              "saves": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "snapshotCount": {
                          "type": "integer",
                          "minimum": 0,
                          "description": "Number of PostMetric rows contributing to this summary."
                        },
                        "filters": {
                          "type": "object",
                          "properties": {
                            "accountId": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "postId": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "platform": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/Platform"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/analytics/daily": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Get analytics grouped by day",
        "description": "Requires scope `analytics:read`. Same filters as GET /analytics, grouped by PostMetric.date for charting.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily analytics points, ascending by date.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "impressions": {
                            "type": "integer"
                          },
                          "reach": {
                            "type": "integer"
                          },
                          "engagements": {
                            "type": "integer"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          },
                          "shares": {
                            "type": "integer"
                          },
                          "clicks": {
                            "type": "integer"
                          },
                          "saves": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/analytics/best-time": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Get the best time to post",
        "description": "Requires scope `analytics:read`. Buckets PostMetric rows by weekday x hour and ranks by engagement, to suggest optimal posting slots.",
        "parameters": [
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked weekday x hour slots, best first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "dayOfWeek": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 6,
                            "description": "0=Sunday .. 6=Saturday"
                          },
                          "hour": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 23
                          },
                          "score": {
                            "type": "number",
                            "description": "Aggregate engagement score for this weekday x hour bucket."
                          },
                          "sampleSize": {
                            "type": "integer",
                            "minimum": 0,
                            "description": "Number of PostMetric rows contributing to this bucket."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/comments": {
      "get": {
        "tags": [
          "Comments"
        ],
        "summary": "List comments",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/CommentStatus"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          {
            "name": "postId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parentId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring match against comment text.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of comments, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Comment"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/comments/{id}": {
      "get": {
        "tags": [
          "Comments"
        ],
        "summary": "Get a comment",
        "description": "Includes its threaded replies (Comment rows with parentId set) as `replies`, oldest first.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Comment id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The comment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Comment"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "replies": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/Comment"
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Comments"
        ],
        "summary": "Delete/hide a comment",
        "description": "Requires scope `comments:write`. Marks the comment status = deleted; does not remove the row.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Comment id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted comment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Comment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/comments/{id}/reply": {
      "post": {
        "tags": [
          "Comments"
        ],
        "summary": "Reply to a comment",
        "description": "Requires scope `comments:write`. Persists the reply as a new Comment row (isOwn=true, parentId set to the original), flips the original's status to replied, and dispatches `comment.replied`. 409 if the original comment is already status=deleted.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Comment id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The original comment (now status=replied) and the new reply comment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "comment": {
                          "$ref": "#/components/schemas/Comment"
                        },
                        "reply": {
                          "$ref": "#/components/schemas/Comment"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/mentions": {
      "get": {
        "tags": [
          "Mentions"
        ],
        "summary": "List mentions",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint). Read-only — mentions have no reply concept.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "isRead",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring match against mention text.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of mentions, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Mention"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/reviews": {
      "get": {
        "tags": [
          "Reviews"
        ],
        "summary": "List reviews",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ReviewStatus"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          {
            "name": "rating",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring match against review text.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of reviews, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Review"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/reviews/{id}": {
      "get": {
        "tags": [
          "Reviews"
        ],
        "summary": "Get a review",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Review id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The review.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Review"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/reviews/{id}/reply": {
      "post": {
        "tags": [
          "Reviews"
        ],
        "summary": "Reply to a review",
        "description": "Requires scope `reviews:write`. Sets reply/repliedAt and status=replied, and dispatches `review.replied`. Re-posting to an already-replied review overwrites the stored reply (edit-in-place; there is a single `reply` field, not a thread).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Review id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated review.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Review"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/messages": {
      "get": {
        "tags": [
          "Messages"
        ],
        "summary": "List conversations",
        "description": "Requires scope `messages:read`. Despite the path, this lists Conversation rows (message threads), sorted by lastMessageAt.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ConversationStatus"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          {
            "name": "contactId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unread",
            "in": "query",
            "description": "true = unreadCount > 0, false = unreadCount == 0.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of conversations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Conversation"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/conversations/{id}": {
      "get": {
        "tags": [
          "Messages"
        ],
        "summary": "Get a conversation",
        "description": "Requires scope `messages:read`. Includes its messages, oldest first.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Conversation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation with messages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Conversation"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/conversations/{id}/mark-read": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Mark a conversation read",
        "description": "Requires scope `messages:write`. Sets unreadCount=0 and isRead=true on its inbound messages.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Conversation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The updated conversation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Conversation"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/messages/send": {
      "post": {
        "tags": [
          "Messages"
        ],
        "summary": "Send a DM",
        "description": "Requires scope `messages:write`. Sends via the platform adapter and records an outbound Message; pass `conversationId` to append to an existing thread, or `accountId`+`contactId` to start a new one.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "description": "Provide either `conversationId` to append to an existing thread, or `contactId` + `accountId` to start/continue one with that contact on that account.",
                "properties": {
                  "conversationId": {
                    "type": "string",
                    "description": "Existing Conversation to append to."
                  },
                  "contactId": {
                    "type": "string",
                    "description": "Required (with accountId) when conversationId is omitted."
                  },
                  "accountId": {
                    "type": "string",
                    "description": "Required (with contactId) when conversationId is omitted."
                  },
                  "platform": {
                    "$ref": "#/components/schemas/Platform",
                    "description": "Optional assertion that must match the account's platform."
                  },
                  "text": {
                    "type": "string",
                    "minLength": 1
                  },
                  "attachments": {
                    "type": [
                      "object",
                      "array",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The sent message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Message"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/messages/{id}": {
      "patch": {
        "tags": [
          "Messages"
        ],
        "summary": "Edit a message",
        "description": "Requires scope `messages:write`. Only own outbound messages may be edited; sets editedAt.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Message id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Message"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Messages"
        ],
        "summary": "Delete a message",
        "description": "Requires scope `messages:write`. Soft-delete: sets deletedAt and status=deleted; thread ordering is preserved.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Message id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Message"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/contacts": {
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "description": "Requires scope `contacts:read`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring match against first/last name or email.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Contacts"
        ],
        "summary": "Create a contact",
        "description": "Requires scope `contacts:write`. `customFields` is validated against this tenant's CustomFieldDef rows via validateCustomFields() before persistence.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "lastName": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 320
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "avatarUrl": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 5000
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 100
                    },
                    "maxItems": 200
                  },
                  "customFields": {
                    "type": "object",
                    "description": "Validated against this tenant's CustomFieldDef rows before persistence. Use POST /contacts/{id}/channels to attach delivery channels after creation."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/contacts/bulk-create": {
      "post": {
        "tags": [
          "Contacts"
        ],
        "summary": "Bulk-create contacts",
        "description": "Requires scope `contacts:write`. Up to 1000 contacts per call; each is validated (including customFields) before a single createMany.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "contacts"
                ],
                "properties": {
                  "contacts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "firstName": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "lastName": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "email": {
                          "type": "string",
                          "format": "email",
                          "maxLength": 320
                        },
                        "phone": {
                          "type": "string",
                          "maxLength": 50
                        },
                        "avatarUrl": {
                          "type": "string",
                          "format": "uri",
                          "maxLength": 2000
                        },
                        "notes": {
                          "type": "string",
                          "maxLength": 5000
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 100
                          },
                          "maxItems": 200
                        },
                        "customFields": {
                          "type": "object",
                          "description": "Validated against this tenant's CustomFieldDef rows before persistence. Use POST /contacts/{id}/channels to attach delivery channels after creation."
                        }
                      }
                    },
                    "minItems": 1,
                    "maxItems": 1000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bulk-create result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "contacts": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Contact"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/contacts/{id}": {
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "Get a contact",
        "description": "Requires scope `contacts:read`. Includes channels.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Contacts"
        ],
        "summary": "Update a contact",
        "description": "Requires scope `contacts:write`. `customFields`, if present, is re-validated in full against this tenant's CustomFieldDef rows.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "firstName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "lastName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "email"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "avatarUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "customFields": {
                    "type": "object",
                    "description": "Validated against this tenant's CustomFieldDef rows before persistence."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Contacts"
        ],
        "summary": "Delete a contact",
        "description": "Requires scope `contacts:write`. Cascades to its channels, broadcast recipient rows, and sequence enrollments.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/contacts/{id}/channels": {
      "post": {
        "tags": [
          "Contacts"
        ],
        "summary": "Add a channel to a contact",
        "description": "Requires scope `contacts:write`. Rejects with 409 if this exact (platform, externalId) is already attached to the contact — unique per (contactId, platform, externalId).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platform",
                  "externalId"
                ],
                "properties": {
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "externalId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "accountId": {
                    "type": "string",
                    "description": "When given, must be a tenant account on the same platform."
                  },
                  "handle": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Channel added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ContactChannel"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/custom-fields": {
      "get": {
        "tags": [
          "CustomFields"
        ],
        "summary": "List custom field definitions",
        "description": "Requires scope `custom-fields:read`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of custom field defs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomFieldDef"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "CustomFields"
        ],
        "summary": "Create a custom field definition",
        "description": "Requires scope `custom-fields:write`. Unique per tenant on `key`; 409 if that key already exists.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key",
                  "label"
                ],
                "properties": {
                  "key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "type": {
                    "$ref": "#/components/schemas/CustomFieldType"
                  },
                  "required": {
                    "type": "boolean",
                    "default": false
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Allowed values when type = select."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Custom field def created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CustomFieldDef"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/custom-fields/{id}": {
      "get": {
        "tags": [
          "CustomFields"
        ],
        "summary": "Get a custom field definition",
        "description": "Requires scope `custom-fields:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Custom field def id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The custom field def.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CustomFieldDef"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "CustomFields"
        ],
        "summary": "Update a custom field definition",
        "description": "Requires scope `custom-fields:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Custom field def id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "description": "`key` is immutable after creation (it's used verbatim inside stored Contact.customFields JSON); every other field, including `type`, may be changed.",
                "properties": {
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "type": {
                    "$ref": "#/components/schemas/CustomFieldType"
                  },
                  "required": {
                    "type": "boolean"
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Must be a non-empty array when the resulting type is 'select'."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated custom field def.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CustomFieldDef"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "CustomFields"
        ],
        "summary": "Delete a custom field definition",
        "description": "Requires scope `custom-fields:write`. Does not retroactively strip the key from existing Contact.customFields JSON.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Custom field def id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/broadcasts": {
      "get": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "List broadcasts",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/BroadcastStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of broadcasts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Broadcast"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Create a broadcast",
        "description": "Requires scope `broadcasts:write`. Created in status=draft (or scheduled, when `scheduledFor` is given); optionally seeds BroadcastRecipient rows from `contactIds`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "message"
                ],
                "description": "`platform` is required only when `accountId` is omitted (it's otherwise inferred from the account).",
                "properties": {
                  "accountId": {
                    "type": "string"
                  },
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1
                  },
                  "contactIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Optional initial recipient set. Contacts with no matching ContactChannel for the resolved platform are skipped (see `skippedContactIds` in the response) rather than failing the request."
                  },
                  "scheduledFor": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When set, the broadcast is created in status=scheduled instead of draft."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Broadcast created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Broadcast"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "skippedContactIds": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "contactIds that had no matching ContactChannel for the resolved platform, so were not seeded as recipients."
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/broadcasts/{id}": {
      "get": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Get a broadcast",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint). Includes its recipients.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Broadcast id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The broadcast.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Broadcast"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "recipients": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/BroadcastRecipient"
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Update a broadcast",
        "description": "Requires scope `broadcasts:write`. Only broadcasts in status draft or scheduled may be updated. Passing `contactIds` deletes all existing BroadcastRecipient rows for this broadcast and re-creates them (there is no separate add-recipients endpoint).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Broadcast id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "description": "Only broadcasts in status draft or scheduled may be updated. Passing `contactIds` fully replaces the existing recipient set (there is no separate add-recipients endpoint).",
                "properties": {
                  "accountId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1
                  },
                  "contactIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces all existing BroadcastRecipient rows for this broadcast."
                  },
                  "scheduledFor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "A non-null value moves status to scheduled; null moves it back to draft."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated broadcast.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Broadcast"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "skippedContactIds": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Present when `contactIds` was provided: entries with no matching ContactChannel for the resolved platform."
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Cancel a broadcast",
        "description": "Requires scope `broadcasts:write`. Despite the HTTP method this does NOT delete the row: only broadcasts in status draft or scheduled may be cancelled; it marks their pending BroadcastRecipient rows status=skipped and sets the broadcast status=cancelled. There is no separate /cancel endpoint.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Broadcast id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled broadcast.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Broadcast"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/broadcasts/{id}/send": {
      "post": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Send a broadcast now",
        "description": "Requires scope `broadcasts:write`. Sets status=sending for the /api/cron/engage broadcasts pass to pick up immediately.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Broadcast id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The broadcast, now sending.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Broadcast"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/broadcasts/{id}/schedule": {
      "post": {
        "tags": [
          "Broadcasts"
        ],
        "summary": "Schedule a broadcast",
        "description": "Requires scope `broadcasts:write`. Sets status=scheduled and scheduledFor for the cron pass to claim once due.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Broadcast id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scheduledFor"
                ],
                "properties": {
                  "scheduledFor": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Must be in the future."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The scheduled broadcast.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Broadcast"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/sequences": {
      "get": {
        "tags": [
          "Sequences"
        ],
        "summary": "List sequences",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/SequenceStatus"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Platform"
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of sequences.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Sequence"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Sequences"
        ],
        "summary": "Create a sequence",
        "description": "Requires scope `sequences:write`. Created in status=draft (unless `status` is given) with the given inline `steps` (defaults to an empty array).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "accountId": {
                    "type": "string"
                  },
                  "platform": {
                    "$ref": "#/components/schemas/Platform"
                  },
                  "status": {
                    "$ref": "#/components/schemas/SequenceStatus",
                    "description": "Defaults to draft."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "type"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "delay",
                            "message",
                            "broadcast"
                          ]
                        },
                        "content": {
                          "type": "string"
                        },
                        "delayHours": {
                          "type": "number"
                        }
                      }
                    },
                    "maxItems": 50,
                    "description": "Defaults to an empty array. `delay` steps require delayHours; `message`/`broadcast` steps require content."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sequence created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Sequence"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/sequences/{id}": {
      "get": {
        "tags": [
          "Sequences"
        ],
        "summary": "Get a sequence",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sequence id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sequence.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Sequence"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Sequences"
        ],
        "summary": "Update a sequence",
        "description": "Requires scope `sequences:write`. Sets `status` to active/paused/draft here — there are no separate activate/pause endpoints.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sequence id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "description": "Only draft/active/paused transitions are supported for `status` (set it to `active` or `paused` here; there are no separate activate/pause endpoints).",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "accountId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "platform": {
                    "anyOf": [
                      {
                        "$ref": "#/components/schemas/Platform"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "status": {
                    "$ref": "#/components/schemas/SequenceStatus"
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "type"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "delay",
                            "message",
                            "broadcast"
                          ]
                        },
                        "content": {
                          "type": "string"
                        },
                        "delayHours": {
                          "type": "number"
                        }
                      }
                    },
                    "maxItems": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated sequence.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Sequence"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "tags": [
          "Sequences"
        ],
        "summary": "Delete a sequence",
        "description": "Requires scope `sequences:write`. Hard-deletes the row (there is no cancelled status for sequences); rejected with 409 while any enrollment is still status=active.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sequence id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/sequences/{id}/enroll": {
      "post": {
        "tags": [
          "Sequences"
        ],
        "summary": "Enroll a contact in a sequence",
        "description": "Requires scope `sequences:write`. Upserts a single SequenceEnrollment row on (sequenceId, contactId) with status=active, currentStep=0, and nextRunAt derived from the first step. There is no separate unenroll endpoint or bulk form — call this once per contactId, and set the enrollment's status via a client-side record if you need to track unenrollment (the API itself has no route for it yet).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sequence id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "contactId"
                ],
                "description": "Enrolls one contact per call. Re-enrolling a contact whose enrollment is status=completed or unenrolled restarts it at step 0; any other existing status is a 409 conflict.",
                "properties": {
                  "contactId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An existing completed/unenrolled enrollment was reset and restarted at step 0.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceEnrollment"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "New enrollment created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceEnrollment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The contact already has an active or paused enrollment in this sequence.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/automations/comment-to-dm": {
      "get": {
        "tags": [
          "Automations"
        ],
        "summary": "List comment-to-DM automations",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of automations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CommentAutomation"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Automations"
        ],
        "summary": "Create a comment-to-DM automation",
        "description": "Requires scope `automations:write`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accountId",
                  "keywords",
                  "dmMessage"
                ],
                "description": "`platform` is always derived from `accountId` and is not accepted here. `name` defaults to \"Comment automation for <account>\" when omitted.",
                "properties": {
                  "accountId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  },
                  "keywords": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  },
                  "matchType": {
                    "type": "string",
                    "enum": [
                      "any",
                      "all",
                      "exact"
                    ],
                    "default": "any"
                  },
                  "dmMessage": {
                    "type": "string",
                    "minLength": 1
                  },
                  "replyMessage": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Automation created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommentAutomation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/automations/comment-to-dm/{id}": {
      "get": {
        "tags": [
          "Automations"
        ],
        "summary": "Get a comment-to-DM automation",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The automation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommentAutomation"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Automations"
        ],
        "summary": "Update a comment-to-DM automation",
        "description": "Requires scope `automations:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "description": "`accountId`, when given, re-derives `platform` from the new account.",
                "properties": {
                  "accountId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "keywords": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  },
                  "matchType": {
                    "type": "string",
                    "enum": [
                      "any",
                      "all",
                      "exact"
                    ]
                  },
                  "dmMessage": {
                    "type": "string",
                    "minLength": 1
                  },
                  "replyMessage": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated automation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommentAutomation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Automations"
        ],
        "summary": "Delete a comment-to-DM automation",
        "description": "Requires scope `automations:write`. Cascades to its trigger logs.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/automations/comment-to-dm/{id}/logs": {
      "get": {
        "tags": [
          "Automations"
        ],
        "summary": "List an automation's trigger logs",
        "description": "Any authenticated API key (read-scope enforcement not yet applied to this endpoint).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated CommentAutomationLog rows, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CommentAutomationLog"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/media/file/{tenantId}/{filename}": {
      "get": {
        "tags": [
          "Media"
        ],
        "summary": "Serve an uploaded media file from disk",
        "description": "Not bearer-authenticated on purpose — URLs returned by POST /v1/media/upload are embedded directly in outbound posts/DMs and rendered in <img>/<video> tags, so they must be fetchable by browsers and third-party platforms that can't attach an Authorization header. Access control relies on `filename` being an unguessable random UUID. Storage lives outside `public/` (fixes uploaded media 404ing under `next start`); both path segments are validated against a strict filesystem-safe pattern and resolved against the uploads root, rejecting any traversal attempt. Content-Type is derived from a small extension allowlist (falls back to application/octet-stream); responses always carry X-Content-Type-Options: nosniff and a restrictive Content-Security-Policy. Returns 404 (never 403) on a miss or a rejected/malformed path, so the endpoint never reveals filesystem shape.",
        "security": [],
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "description": "Owning tenant id, as returned in the media asset's url.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filename",
            "in": "path",
            "required": true,
            "description": "Random UUID-based stored filename (with extension), as returned in the media asset's url.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The raw file bytes, with a Content-Type derived from the file extension.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ad-accounts": {
      "get": {
        "tags": [
          "AdAccounts"
        ],
        "summary": "List ad accounts",
        "description": "Requires scope `ad-accounts:read`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "network",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AdNetwork"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of ad accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdAccount"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "AdAccounts"
        ],
        "summary": "Connect an ad account",
        "description": "Requires scope `ad-accounts:write`. `credentials` is validated live via the network adapter's `connectAdAccount()` when that network is configured server-side (`<NETWORK>_ADS_*` env vars present); otherwise it is stored as given. Always AES-256-GCM encrypted at rest and never returned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "network",
                  "credentials"
                ],
                "properties": {
                  "network": {
                    "$ref": "#/components/schemas/AdNetwork"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "externalAccountId": {
                    "type": "string",
                    "description": "Network-native ad account id, when already known."
                  },
                  "credentials": {
                    "type": "object",
                    "description": "Network-native credentials/tokens JSON. Validated live via the adapter's connectAdAccount() when the network is configured server-side, else stored as-is. AES-256-GCM encrypted at rest; never returned by any response."
                  },
                  "meta": {
                    "type": "object",
                    "description": "Non-secret connection config."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ad account connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdAccount"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/ad-accounts/{id}": {
      "get": {
        "tags": [
          "AdAccounts"
        ],
        "summary": "Get an ad account",
        "description": "Requires scope `ad-accounts:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ad account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdAccount"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "AdAccounts"
        ],
        "summary": "Update an ad account",
        "description": "Requires scope `ad-accounts:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "status": {
                    "$ref": "#/components/schemas/AdAccountStatus"
                  },
                  "meta": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated ad account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdAccount"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "AdAccounts"
        ],
        "summary": "Disconnect an ad account",
        "description": "Requires scope `ad-accounts:write`. Sets status=disconnected; cascades to its campaigns, ads, audiences, and tracking tags.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ad-campaigns": {
      "get": {
        "tags": [
          "AdCampaigns"
        ],
        "summary": "List ad campaigns",
        "description": "Requires scope `ad-campaigns:read`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "adAccountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "network",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AdNetwork"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AdStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of ad campaigns.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdCampaign"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "AdCampaigns"
        ],
        "summary": "Create an ad campaign",
        "description": "Requires scope `ad-campaigns:write`. `adAccountId` must belong to the caller's tenant. Created in status=draft.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "adAccountId",
                  "name",
                  "objective"
                ],
                "properties": {
                  "adAccountId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "objective": {
                    "$ref": "#/components/schemas/AdObjective"
                  },
                  "dailyBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "lifetimeBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endDate": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ad campaign created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdCampaign"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ad-campaigns/{id}": {
      "get": {
        "tags": [
          "AdCampaigns"
        ],
        "summary": "Get an ad campaign",
        "description": "Requires scope `ad-campaigns:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ad campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdCampaign"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "AdCampaigns"
        ],
        "summary": "Update an ad campaign",
        "description": "Requires scope `ad-campaigns:write`. Status transitions go through `POST /v1/ad-campaigns/{id}/status`, not this endpoint.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "description": "Status transitions go through the dedicated pause/resume actions, not this endpoint.",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "objective": {
                    "$ref": "#/components/schemas/AdObjective"
                  },
                  "dailyBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "lifetimeBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "startDate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "endDate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated ad campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdCampaign"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "AdCampaigns"
        ],
        "summary": "Delete an ad campaign",
        "description": "Requires scope `ad-campaigns:write`. Detaches (does not delete) any ads currently assigned to it.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ad-campaigns/{id}/status": {
      "post": {
        "tags": [
          "AdCampaigns"
        ],
        "summary": "Pause or resume an ad campaign",
        "description": "Requires scope `ad-campaigns:write`. Body `{ action: \"pause\" | \"resume\" }`. `pause` is only valid from status=active and `resume` only from status=paused; any other current status is a 400. Dispatches to the network adapter (best-effort when configured), sets status accordingly, and emits `ad.status_changed`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "pause",
                      "resume"
                    ],
                    "description": "`pause` an active campaign or `resume` a paused one."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated ad campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdCampaign"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads": {
      "get": {
        "tags": [
          "Ads"
        ],
        "summary": "List ads",
        "description": "Requires scope `ads:read`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "adAccountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaignId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "network",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AdNetwork"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AdStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of ads.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Ad"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Ads"
        ],
        "summary": "Create an ad",
        "description": "Requires scope `ads:write`. `adAccountId` (and `campaignId`, when given) must belong to the caller's tenant, and `campaignId`'s adAccountId must match. Created in status=draft unless `status` is given.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "adAccountId",
                  "name"
                ],
                "properties": {
                  "adAccountId": {
                    "type": "string"
                  },
                  "campaignId": {
                    "type": "string",
                    "description": "Must belong to the same tenant (and, when both given, the same adAccountId)."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "creative": {
                    "type": "object"
                  },
                  "targeting": {
                    "type": "object"
                  },
                  "dailyBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "lifetimeBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "status": {
                    "$ref": "#/components/schemas/AdStatus"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ad created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ad"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads/{id}": {
      "get": {
        "tags": [
          "Ads"
        ],
        "summary": "Get an ad",
        "description": "Requires scope `ads:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ad.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ad"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Ads"
        ],
        "summary": "Update an ad",
        "description": "Requires scope `ads:write`. Only permitted while the ad's current status is draft or paused. Emits `ad.status_changed` when `status` changes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "description": "Only permitted while the ad's current status is draft or paused.",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "status": {
                    "$ref": "#/components/schemas/AdStatus"
                  },
                  "creative": {
                    "type": "object"
                  },
                  "targeting": {
                    "type": "object"
                  },
                  "dailyBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "lifetimeBudget": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units (cents)."
                  },
                  "startDate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "endDate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated ad.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ad"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "tags": [
          "Ads"
        ],
        "summary": "Delete an ad",
        "description": "Requires scope `ads:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads/{id}/analytics": {
      "get": {
        "tags": [
          "Ads"
        ],
        "summary": "Get an ad's daily analytics",
        "description": "Requires scope `ads:read`. Reads from AdMetric.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "metrics",
            "in": "query",
            "description": "Comma-separated subset of metric names to include in `extra`; omit for the default set.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Per-day metric rows.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdMetric"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads/boost-post": {
      "post": {
        "tags": [
          "Ads"
        ],
        "summary": "Boost an organic post into a paid ad",
        "description": "Requires scope `ads:write`. `postId` must belong to the caller's tenant (404, not leaked, on mismatch). Creates an `Ad` with `postId` set.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "postId",
                  "adAccountId",
                  "budget"
                ],
                "properties": {
                  "postId": {
                    "type": "string",
                    "description": "Must belong to the caller's tenant."
                  },
                  "adAccountId": {
                    "type": "string"
                  },
                  "campaignId": {
                    "type": "string"
                  },
                  "budget": {
                    "type": "object",
                    "description": "At least one of daily/total, minor units (cents).",
                    "properties": {
                      "daily": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Minor units (cents)."
                      },
                      "total": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Minor units (cents)."
                      }
                    }
                  },
                  "targeting": {
                    "type": "object"
                  },
                  "content": {
                    "type": "string",
                    "description": "Fallback copy when the post can't be referenced natively."
                  },
                  "mediaUrls": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ad created from the boosted post.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ad"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads/create-lead-form": {
      "post": {
        "tags": [
          "Ads"
        ],
        "summary": "Create a lead form",
        "description": "Requires scope `ads:write`. `adAccountId` must belong to the caller's tenant.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "adAccountId",
                  "name",
                  "fields"
                ],
                "properties": {
                  "adAccountId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "fields": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "key",
                        "label",
                        "type"
                      ],
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "required": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lead form created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LeadForm"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads/lead-forms/{id}/submissions": {
      "post": {
        "tags": [
          "Ads"
        ],
        "summary": "Submit a lead against a lead form",
        "description": "Requires scope `ads:write`. The lead form `{id}` must belong to the caller's tenant (404, not leaked, on mismatch). `data` is validated against the form's field specs — required fields must be present and unknown keys are rejected. Optional `adId` links the lead to a tenant-owned ad. Creates a `Lead` and emits the `lead.received` webhook.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lead form id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "description": "Submitted field values keyed by the lead form's field keys. Required fields must be present; unknown keys are rejected.",
                    "additionalProperties": true
                  },
                  "adId": {
                    "type": "string",
                    "description": "Optional id of a tenant-owned ad that generated this lead."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lead recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Lead"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads/search-targeting": {
      "post": {
        "tags": [
          "Ads"
        ],
        "summary": "Search network targeting suggestions",
        "description": "Requires scope `ads:write`. `adAccountId` must belong to the caller's tenant. Returns a cached echo of the query when the account's network has no credentials configured, so this endpoint is exercisable without ad creds.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "adAccountId",
                  "query"
                ],
                "properties": {
                  "adAccountId": {
                    "type": "string"
                  },
                  "query": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Targeting suggestions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TargetingSuggestion"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ads/send-conversions": {
      "post": {
        "tags": [
          "Ads"
        ],
        "summary": "Send conversion events",
        "description": "Requires scope `ads:write`. `trackingTagId` must belong to the caller's tenant (404, not leaked, on mismatch). Any `userData.email`/`userData.phone` in an event's payload is SHA-256 hashed before persist and before being forwarded. Persists one `AdConversion` per event and forwards them to the network CAPI via the adapter when configured (`forwarded` reflects the outcome; unconfigured networks persist with `forwarded=false`).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "trackingTagId",
                  "events"
                ],
                "properties": {
                  "trackingTagId": {
                    "type": "string",
                    "description": "Must belong to the caller's tenant."
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "eventName"
                      ],
                      "properties": {
                        "eventName": {
                          "type": "string"
                        },
                        "occurredAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "payload": {
                          "type": "object",
                          "description": "Event data (value, currency, etc.). `userData.email`/`userData.phone`, when present, are SHA-256 hashed server-side before persist and before being forwarded to the network CAPI — raw PII is never stored.",
                          "properties": {
                            "value": {
                              "type": "number"
                            },
                            "currency": {
                              "type": "string"
                            },
                            "userData": {
                              "type": "object",
                              "properties": {
                                "email": {
                                  "type": "string",
                                  "format": "email"
                                },
                                "phone": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Conversions recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdConversion"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ad-audiences": {
      "get": {
        "tags": [
          "AdAudiences"
        ],
        "summary": "List ad audiences",
        "description": "Requires scope `ad-audiences:read`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "adAccountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AdAudienceType"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of ad audiences.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdAudience"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "AdAudiences"
        ],
        "summary": "Create an ad audience",
        "description": "Requires scope `ad-audiences:write`. `adAccountId` must belong to the caller's tenant. Created in status=building.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "adAccountId",
                  "name",
                  "type"
                ],
                "properties": {
                  "adAccountId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "type": {
                    "$ref": "#/components/schemas/AdAudienceType"
                  },
                  "spec": {
                    "type": "object",
                    "description": "Rule/spec JSON for saved or lookalike audiences (source audience, ratio, filters)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ad audience created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdAudience"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ad-audiences/{id}": {
      "get": {
        "tags": [
          "AdAudiences"
        ],
        "summary": "Get an ad audience",
        "description": "Requires scope `ad-audiences:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad audience id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ad audience.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdAudience"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "AdAudiences"
        ],
        "summary": "Update an ad audience",
        "description": "Requires scope `ad-audiences:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad audience id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "spec": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated ad audience.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdAudience"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "AdAudiences"
        ],
        "summary": "Delete an ad audience",
        "description": "Requires scope `ad-audiences:write`. Cascades to its members.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad audience id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/ad-audiences/{id}/upload": {
      "post": {
        "tags": [
          "AdAudiences"
        ],
        "summary": "Upload audience members",
        "description": "Requires scope `ad-audiences:write`. `id` must belong to the caller's tenant (404, not leaked, on mismatch). Raw email/phone are SHA-256 hashed immediately; only the hash is ever persisted (`AdAudienceMember.hashedValue`) or handed to the network adapter. `memberCount` is bumped by the number of newly-accepted (previously unseen) hashes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Ad audience id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "members"
                ],
                "description": "Raw email/phone are SHA-256 hashed server-side immediately; the plaintext is never persisted or forwarded.",
                "properties": {
                  "members": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "description": "Exactly one of email/phone.",
                      "properties": {
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "phone": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdAudienceUploadResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/tracking-tags": {
      "get": {
        "tags": [
          "TrackingTags"
        ],
        "summary": "List tracking tags",
        "description": "Requires scope `tracking-tags:read`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "adAccountId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/TrackingTagType"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of tracking tags.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TrackingTag"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "TrackingTags"
        ],
        "summary": "Create a tracking tag",
        "description": "Requires scope `tracking-tags:write`. `adAccountId` must belong to the caller's tenant. `credentials`, when given, is AES-256-GCM encrypted at rest and never returned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "adAccountId",
                  "name",
                  "type"
                ],
                "properties": {
                  "adAccountId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "type": {
                    "$ref": "#/components/schemas/TrackingTagType"
                  },
                  "config": {
                    "type": "object",
                    "description": "Non-secret config (gtag measurement id, allowed domains, etc.)."
                  },
                  "credentials": {
                    "type": "object",
                    "description": "Optional secret (e.g. a CAPI access token). AES-256-GCM encrypted at rest; never returned by any response."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tracking tag created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TrackingTag"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/tracking-tags/{id}": {
      "get": {
        "tags": [
          "TrackingTags"
        ],
        "summary": "Get a tracking tag",
        "description": "Requires scope `tracking-tags:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tracking tag id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tracking tag.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TrackingTag"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "TrackingTags"
        ],
        "summary": "Update a tracking tag",
        "description": "Requires scope `tracking-tags:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tracking tag id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "config": {
                    "type": "object"
                  },
                  "credentials": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated tracking tag.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TrackingTag"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "TrackingTags"
        ],
        "summary": "Delete a tracking tag",
        "description": "Requires scope `tracking-tags:write`. Cascades to its events and conversions.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tracking tag id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/tracking-tags/{id}/events": {
      "get": {
        "tags": [
          "TrackingTags"
        ],
        "summary": "List a tracking tag's events",
        "description": "Requires scope `tracking-tags:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tracking tag id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of tracking tag events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "page",
                    "limit",
                    "total"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TrackingTagEvent"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "TrackingTags"
        ],
        "summary": "Record a tracking tag event",
        "description": "Requires scope `tracking-tags:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tracking tag id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "eventName"
                ],
                "properties": {
                  "eventName": {
                    "type": "string"
                  },
                  "payload": {
                    "type": "object"
                  },
                  "externalId": {
                    "type": "string"
                  },
                  "occurredAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TrackingTagEvent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_...",
        "description": "Pass your OneSocial API key as `Authorization: Bearer sk_...`."
      }
    },
    "parameters": {
      "Page": {
        "name": "page",
        "in": "query",
        "description": "1-indexed page number.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Page size, capped at 100.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request body or query params failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key's scopes do not permit this action.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No resource with that id exists for this tenant.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with the resource's current state.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "bad_request"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Platform": {
        "type": "string",
        "enum": [
          "bluesky",
          "telegram",
          "discord",
          "mastodon"
        ],
        "description": "Supported publisher platform."
      },
      "PostStatus": {
        "type": "string",
        "enum": [
          "draft",
          "scheduled",
          "publishing",
          "published",
          "partial",
          "failed",
          "cancelled"
        ]
      },
      "TargetStatus": {
        "type": "string",
        "enum": [
          "pending",
          "publishing",
          "published",
          "failed",
          "skipped"
        ]
      },
      "AccountStatus": {
        "type": "string",
        "enum": [
          "connected",
          "disconnected",
          "error"
        ]
      },
      "MediaType": {
        "type": "string",
        "enum": [
          "image",
          "video",
          "document"
        ]
      },
      "WebhookDeliveryStatus": {
        "type": "string",
        "enum": [
          "pending",
          "success",
          "failed",
          "dead"
        ]
      },
      "WebhookEvent": {
        "type": "string",
        "enum": [
          "post.scheduled",
          "post.published",
          "post.partial",
          "post.failed",
          "post.cancelled",
          "post.platform.published",
          "post.platform.failed",
          "account.connected",
          "account.disconnected",
          "webhook.test",
          "comment.received",
          "mention.received",
          "review.new",
          "review.updated",
          "message.received",
          "message.sent",
          "conversation.started",
          "broadcast.sent",
          "sequence.enrolled",
          "sequence.completed",
          "automation.triggered"
        ]
      },
      "Tenant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "description": "Safe API key fields — `keyHash` is never returned.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string",
            "description": "First characters of the raw key, for display."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Empty array = full access."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revokedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKeyCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "required": [
              "key"
            ],
            "properties": {
              "key": {
                "type": "string",
                "description": "The raw sk_-prefixed secret key. Shown exactly once — store it now.",
                "examples": [
                  "sk_live_a1b2c3d4e5f6..."
                ]
              }
            }
          }
        ]
      },
      "Profile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Account": {
        "type": "object",
        "description": "Connected social account. `credentials` (encrypted secrets) is never returned.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "profileId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "username": {
            "type": [
              "string",
              "null"
            ]
          },
          "displayName": {
            "type": [
              "string",
              "null"
            ]
          },
          "profilePictureUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "platformAccountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/AccountStatus"
          },
          "isConnected": {
            "type": "boolean"
          },
          "meta": {
            "type": [
              "object",
              "null"
            ],
            "description": "Non-secret connection config (e.g. mastodon instance host)."
          },
          "connectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "disconnectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AccountHealth": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "status": {
            "$ref": "#/components/schemas/AccountStatus"
          },
          "isConnected": {
            "type": "boolean"
          },
          "hasCredentials": {
            "type": "boolean"
          },
          "healthy": {
            "type": "boolean"
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          },
          "connectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "disconnectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastUpdatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AccountGroup": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "profileId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "accountIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MediaAsset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/MediaType"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "mimeType": {
            "type": [
              "string",
              "null"
            ]
          },
          "sizeBytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "altText": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PostTarget": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "postId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "status": {
            "$ref": "#/components/schemas/TargetStatus"
          },
          "scheduledFor": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "publishedUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "platformPostId": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PostStatus"
          },
          "source": {
            "type": "string",
            "description": "'api' | 'dashboard' | 'mcp' | 'queue'",
            "examples": [
              "api"
            ]
          },
          "publishNow": {
            "type": "boolean"
          },
          "scheduledFor": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "targets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostTarget"
            }
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MediaAsset"
            }
          }
        }
      },
      "QueueSlot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "profileId": {
            "type": "string"
          },
          "dayOfWeek": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6,
            "description": "0=Sunday .. 6=Saturday"
          },
          "timeOfDay": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1439,
            "description": "Minutes from local midnight."
          },
          "timezone": {
            "type": "string",
            "examples": [
              "UTC"
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Webhook": {
        "type": "object",
        "description": "Webhook subscription. `secret` is never returned here.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          },
          "enabled": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "required": [
              "secret"
            ],
            "properties": {
              "secret": {
                "type": "string",
                "description": "Plaintext HMAC signing secret (whsec_...). Shown exactly once, at creation.",
                "examples": [
                  "whsec_9f1c2e..."
                ]
              }
            }
          }
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "webhookId": {
            "type": "string"
          },
          "eventId": {
            "type": "string",
            "description": "Sent as the X-OneSocial-Event-Id header."
          },
          "event": {
            "type": "string"
          },
          "payload": {
            "type": "object"
          },
          "status": {
            "$ref": "#/components/schemas/WebhookDeliveryStatus"
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "responseCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "responseBody": {
            "type": [
              "string",
              "null"
            ]
          },
          "nextAttemptAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "deliveredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CommentStatus": {
        "type": "string",
        "enum": [
          "open",
          "replied",
          "hidden",
          "deleted"
        ]
      },
      "ReviewStatus": {
        "type": "string",
        "enum": [
          "unreplied",
          "replied"
        ]
      },
      "ConversationStatus": {
        "type": "string",
        "enum": [
          "open",
          "archived",
          "closed"
        ]
      },
      "MessageDirection": {
        "type": "string",
        "enum": [
          "inbound",
          "outbound"
        ]
      },
      "MessageStatus": {
        "type": "string",
        "enum": [
          "sent",
          "delivered",
          "read",
          "failed",
          "deleted"
        ]
      },
      "CustomFieldType": {
        "type": "string",
        "enum": [
          "text",
          "number",
          "boolean",
          "date",
          "select",
          "url"
        ]
      },
      "BroadcastStatus": {
        "type": "string",
        "enum": [
          "draft",
          "scheduled",
          "sending",
          "sent",
          "cancelled"
        ]
      },
      "BroadcastRecipientStatus": {
        "type": "string",
        "enum": [
          "pending",
          "sent",
          "delivered",
          "failed",
          "skipped"
        ]
      },
      "SequenceStatus": {
        "type": "string",
        "enum": [
          "draft",
          "active",
          "paused"
        ]
      },
      "EnrollmentStatus": {
        "type": "string",
        "enum": [
          "active",
          "paused",
          "completed",
          "unenrolled"
        ]
      },
      "AutomationLogStatus": {
        "type": "string",
        "enum": [
          "matched",
          "queued",
          "sent",
          "failed",
          "skipped"
        ]
      },
      "PostMetric": {
        "type": "object",
        "description": "A per-day metric snapshot; may be account-level (postId null) and/or post-level.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "postId": {
            "type": [
              "string",
              "null"
            ]
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "UTC-midnight day bucket this snapshot summarizes."
          },
          "impressions": {
            "type": "integer",
            "minimum": 0
          },
          "reach": {
            "type": "integer",
            "minimum": 0
          },
          "engagements": {
            "type": "integer",
            "minimum": 0
          },
          "likes": {
            "type": "integer",
            "minimum": 0
          },
          "comments": {
            "type": "integer",
            "minimum": 0
          },
          "shares": {
            "type": "integer",
            "minimum": 0
          },
          "clicks": {
            "type": "integer",
            "minimum": 0
          },
          "saves": {
            "type": "integer",
            "minimum": 0
          },
          "extra": {
            "type": [
              "object",
              "null"
            ],
            "description": "Platform-native metrics that don't map to a typed column."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Platform-native comment id (idempotent ingest key)."
          },
          "externalPostId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Platform-native id of the post/media this comment sits on."
          },
          "postId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Our Post, when the comment maps to one we published."
          },
          "parentId": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorName": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorHandle": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorAvatarUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "text": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/CommentStatus"
          },
          "isOwn": {
            "type": "boolean",
            "description": "True for comments we authored (our replies)."
          },
          "likeCount": {
            "type": "integer",
            "minimum": 0
          },
          "postedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Mention": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorName": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorHandle": {
            "type": [
              "string",
              "null"
            ]
          },
          "text": {
            "type": "string"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "isRead": {
            "type": "boolean"
          },
          "mentionedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Review": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorName": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 5
          },
          "text": {
            "type": [
              "string",
              "null"
            ]
          },
          "reply": {
            "type": [
              "string",
              "null"
            ],
            "description": "Our reply text, once posted."
          },
          "repliedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "$ref": "#/components/schemas/ReviewStatus"
          },
          "reviewedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ]
          },
          "contactId": {
            "type": [
              "string",
              "null"
            ]
          },
          "participantName": {
            "type": [
              "string",
              "null"
            ]
          },
          "participantHandle": {
            "type": [
              "string",
              "null"
            ]
          },
          "participantAvatarUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ConversationStatus"
          },
          "unreadCount": {
            "type": "integer",
            "minimum": 0
          },
          "lastMessageAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastMessageText": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Included only on GET /conversations/{id}."
          }
        }
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "conversationId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ]
          },
          "direction": {
            "$ref": "#/components/schemas/MessageDirection"
          },
          "text": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/MessageStatus"
          },
          "attachments": {
            "type": [
              "object",
              "array",
              "null"
            ],
            "description": "Media/link attachments as platform-native JSON."
          },
          "isRead": {
            "type": "boolean"
          },
          "editedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "deletedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Soft-delete: keeps thread ordering intact."
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "firstName": {
            "type": [
              "string",
              "null"
            ]
          },
          "lastName": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatarUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "customFields": {
            "type": "object",
            "description": "Keys/values validated against the tenant's CustomFieldDef rows at write time."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactChannel"
            }
          }
        }
      },
      "ContactChannel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "contactId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "accountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalId": {
            "type": "string",
            "description": "Platform-native identity for this contact (handle, PSID, chat id) — delivery target."
          },
          "handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CustomFieldDef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "key": {
            "type": "string",
            "description": "Machine key used inside Contact.customFields JSON."
          },
          "label": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/CustomFieldType"
          },
          "required": {
            "type": "boolean"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Allowed values when type = select."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Broadcast": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "name": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/BroadcastStatus"
          },
          "scheduledFor": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "recipientCount": {
            "type": "integer",
            "minimum": 0
          },
          "sentCount": {
            "type": "integer",
            "minimum": 0
          },
          "failedCount": {
            "type": "integer",
            "minimum": 0
          },
          "totalRecipients": {
            "type": "integer",
            "minimum": 0,
            "description": "Live BroadcastRecipient row count (may exceed recipientCount after add-recipients calls)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BroadcastRecipient": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "broadcastId": {
            "type": "string"
          },
          "contactId": {
            "type": [
              "string",
              "null"
            ]
          },
          "channelExternalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Snapshot delivery target so a later contact/channel edit can't retarget an in-flight send."
          },
          "status": {
            "$ref": "#/components/schemas/BroadcastRecipientStatus"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SequenceStep": {
        "type": "object",
        "required": [
          "id",
          "type"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "delay",
              "message",
              "broadcast"
            ]
          },
          "content": {
            "type": "string"
          },
          "delayHours": {
            "type": "number"
          }
        }
      },
      "Sequence": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "platform": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Platform"
              },
              {
                "type": "null"
              }
            ]
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SequenceStatus"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceStep"
            },
            "description": "Ordered steps, stored inline as JSON (no separate step model)."
          },
          "enrollmentCount": {
            "type": "integer",
            "minimum": 0
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SequenceEnrollment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "sequenceId": {
            "type": "string"
          },
          "contactId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/EnrollmentStatus"
          },
          "currentStep": {
            "type": "integer",
            "minimum": 0,
            "description": "Index into Sequence.steps."
          },
          "nextRunAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the current step becomes due."
          },
          "enrolledAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CommentAutomation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "name": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Keywords that, when matched in an inbound comment, queue a DM."
          },
          "matchType": {
            "type": "string",
            "enum": [
              "any",
              "all",
              "exact"
            ],
            "default": "any"
          },
          "dmMessage": {
            "type": "string"
          },
          "replyMessage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional public reply posted alongside the DM."
          },
          "triggerCount": {
            "type": "integer",
            "minimum": 0
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CommentAutomationLog": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "automationId": {
            "type": "string"
          },
          "commentId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The inbound comment that triggered this rule."
          },
          "contactId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact the DM was queued/sent to."
          },
          "matchedKeyword": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/AutomationLogStatus"
          },
          "conversationId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Resulting DM conversation, once queued/sent."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AdNetwork": {
        "type": "string",
        "enum": [
          "meta",
          "google",
          "tiktok",
          "linkedin",
          "pinterest",
          "x"
        ],
        "description": "Ad network. Independent of the organic `Platform` enum's legacy `twitter` value."
      },
      "AdStatus": {
        "type": "string",
        "enum": [
          "draft",
          "pending_review",
          "active",
          "paused",
          "archived",
          "rejected",
          "completed"
        ],
        "description": "Status for Ad, AdCampaign, and LeadForm. Campaign pause = active->paused, resume = paused->active; other transitions are rejected."
      },
      "AdObjective": {
        "type": "string",
        "enum": [
          "awareness",
          "traffic",
          "engagement",
          "leads",
          "app_promotion",
          "sales"
        ]
      },
      "AdAccountStatus": {
        "type": "string",
        "enum": [
          "connected",
          "disconnected",
          "error"
        ]
      },
      "AdAudienceType": {
        "type": "string",
        "enum": [
          "custom",
          "lookalike",
          "saved"
        ]
      },
      "AdAudienceStatus": {
        "type": "string",
        "enum": [
          "building",
          "ready",
          "error"
        ]
      },
      "TrackingTagType": {
        "type": "string",
        "enum": [
          "pixel",
          "conversion_api",
          "gtag"
        ]
      },
      "AdAccount": {
        "type": "object",
        "description": "Connected ad account. `credentials` (AES-256-GCM encrypted network tokens) is never returned.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "network": {
            "$ref": "#/components/schemas/AdNetwork"
          },
          "name": {
            "type": "string"
          },
          "externalAccountId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Network-native ad account id (e.g. Meta act_123, Google customer id)."
          },
          "status": {
            "$ref": "#/components/schemas/AdAccountStatus"
          },
          "isConnected": {
            "type": "boolean"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta": {
            "type": [
              "object",
              "null"
            ],
            "description": "Non-secret connection config (network account name, business id, etc.)."
          },
          "connectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "disconnectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AdCampaign": {
        "type": "object",
        "description": "Budgets and spend are integer minor units (cents).",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "adAccountId": {
            "type": "string"
          },
          "network": {
            "$ref": "#/components/schemas/AdNetwork"
          },
          "name": {
            "type": "string"
          },
          "objective": {
            "$ref": "#/components/schemas/AdObjective"
          },
          "status": {
            "$ref": "#/components/schemas/AdStatus"
          },
          "dailyBudget": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units (cents)."
          },
          "lifetimeBudget": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units (cents)."
          },
          "spend": {
            "type": "integer",
            "minimum": 0,
            "description": "Accumulated spend, minor units (cents)."
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "endDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Network-native campaign id, once created live."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Ad": {
        "type": "object",
        "description": "Budgets and spend are integer minor units (cents). Editable via PATCH only while status is draft or paused.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "adAccountId": {
            "type": "string"
          },
          "campaignId": {
            "type": [
              "string",
              "null"
            ]
          },
          "network": {
            "$ref": "#/components/schemas/AdNetwork"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/AdStatus"
          },
          "creative": {
            "type": [
              "object",
              "null"
            ],
            "description": "Creative payload (headline, body, media urls, cta, link) as network-native JSON."
          },
          "targeting": {
            "type": [
              "object",
              "null"
            ],
            "description": "Targeting spec as network-native JSON."
          },
          "dailyBudget": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units (cents)."
          },
          "lifetimeBudget": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units (cents)."
          },
          "spend": {
            "type": "integer",
            "minimum": 0,
            "description": "Accumulated spend, minor units (cents)."
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "endDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "postId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when this ad was created by boosting an existing organic Post."
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Network-native ad id, once created live."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AdMetric": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "adId": {
            "type": "string"
          },
          "network": {
            "$ref": "#/components/schemas/AdNetwork"
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "UTC-midnight day bucket."
          },
          "impressions": {
            "type": "integer",
            "minimum": 0
          },
          "clicks": {
            "type": "integer",
            "minimum": 0
          },
          "spend": {
            "type": "integer",
            "minimum": 0,
            "description": "Minor units (cents)."
          },
          "conversions": {
            "type": "integer",
            "minimum": 0
          },
          "reach": {
            "type": "integer",
            "minimum": 0
          },
          "extra": {
            "type": [
              "object",
              "null"
            ],
            "description": "Network-native metrics that don't map to a typed column."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AdAudience": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "adAccountId": {
            "type": "string"
          },
          "network": {
            "$ref": "#/components/schemas/AdNetwork"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/AdAudienceType"
          },
          "status": {
            "$ref": "#/components/schemas/AdAudienceStatus"
          },
          "memberCount": {
            "type": "integer",
            "minimum": 0
          },
          "spec": {
            "type": [
              "object",
              "null"
            ],
            "description": "Rule/spec JSON for saved or lookalike audiences (source audience, ratio, filters)."
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Network-native audience id, once created live."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AdAudienceUploadResult": {
        "type": "object",
        "description": "Raw email/phone are SHA-256 hashed server-side before persist; the plaintext never touches the DB.",
        "properties": {
          "audienceId": {
            "type": "string"
          },
          "received": {
            "type": "integer",
            "minimum": 0
          },
          "accepted": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of new, previously-unseen hashed members persisted."
          },
          "memberCount": {
            "type": "integer",
            "minimum": 0,
            "description": "The audience's updated total member count."
          }
        }
      },
      "TrackingTag": {
        "type": "object",
        "description": "`credentials` (AES-256-GCM encrypted, e.g. a CAPI access token) is never returned.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "adAccountId": {
            "type": "string"
          },
          "network": {
            "$ref": "#/components/schemas/AdNetwork"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/TrackingTagType"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Network-native pixel/tag id."
          },
          "config": {
            "type": [
              "object",
              "null"
            ],
            "description": "Non-secret config (gtag measurement id, allowed domains, etc.)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TrackingTagEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "tagId": {
            "type": "string"
          },
          "eventName": {
            "type": "string",
            "description": "'pageview' | 'conversion' | 'purchase' | custom event name."
          },
          "payload": {
            "type": [
              "object",
              "null"
            ]
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional dedup / event id."
          },
          "occurredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LeadForm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "adAccountId": {
            "type": "string"
          },
          "network": {
            "$ref": "#/components/schemas/AdNetwork"
          },
          "name": {
            "type": "string"
          },
          "fields": {
            "type": "array",
            "description": "Ordered field specs.",
            "items": {
              "type": "object",
              "required": [
                "key",
                "label",
                "type"
              ],
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                }
              }
            }
          },
          "status": {
            "$ref": "#/components/schemas/AdStatus"
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Network-native lead form id, once created live."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Lead": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "leadFormId": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "description": "Submitted field values keyed by field key.",
            "additionalProperties": true
          },
          "adId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional link to the ad that generated this lead."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AdConversion": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenantId": {
            "type": "string"
          },
          "trackingTagId": {
            "type": "string"
          },
          "adAccountId": {
            "type": [
              "string",
              "null"
            ]
          },
          "eventName": {
            "type": "string",
            "description": "e.g. \"Purchase\", \"Lead\"."
          },
          "payload": {
            "type": [
              "object",
              "null"
            ],
            "description": "Event data forwarded to the network CAPI (value, currency, hashed user_data, etc.)."
          },
          "forwarded": {
            "type": "boolean",
            "description": "Whether the network CAPI forward succeeded (false until attempted / when no creds)."
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ]
          },
          "occurredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TargetingSuggestion": {
        "type": "object",
        "description": "Returned as a cached echo of the query when the network has no credentials configured.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "e.g. \"interest\" | \"geo\" | \"demographic\" | \"behavior\"."
          },
          "audienceSize": {
            "type": [
              "integer",
              "null"
            ]
          },
          "raw": {
            "type": [
              "object",
              "null"
            ]
          }
        }
      }
    }
  }
}