openapi: 3.1.0
info:
  title: ESP Engine API
  version: 0.1.0-draft
  license:
    name: Proprietary
  summary: Public API of the email sending engine (transactional first).
  description: |
    Draft contract for Stage 1 (engine). Source of truth: implement code only after
    changing this file (API-first). Requirement IDs refer to docs/TZ_CHECKLIST.md.

    Status of endpoints:
    - implemented: system endpoints (/healthz, /readyz, /metrics), /v1/domains*, /v1/api-keys* (E1),
      POST /v1/email/send (E2);
    - planned: the rest of /v1 (phases E3–E6).

    SMTP relay (TX-03, not part of this HTTP contract): ports 587 (STARTTLS), 465 (implicit TLS)
    and 2525 (STARTTLS); AUTH PLAIN or LOGIN after TLS with any username and an API key as the
    password. Optional headers `X-ESP-Tags` (comma-separated), `X-ESP-Metadata` (JSON object of
    strings), `X-ESP-Stream` (transactional|broadcast) and `X-ESP-Sandbox` (true) are removed before
    delivery. See docs/sending.md.

    Conventions:
    - Auth: `Authorization: Bearer <api_key>` (keys look like `sk_live_...` / `sk_test_...`).
      Each operation lists the required scope. Suspended accounts get `403`.
    - Rate limit: token bucket per account. Every authenticated response carries
      `X-RateLimit-Limit` and `X-RateLimit-Remaining`; `429` adds `Retry-After`.
      One send request costs one token regardless of the number of recipients (docs/adr/0003-rate-limits.md).
    - All IDs are ULIDs. All timestamps are RFC 3339 UTC.
    - Errors use a single envelope, see `ErrorResponse`.
    - List endpoints use cursor pagination (`cursor`, `limit`), response has `next_cursor`.
servers:
  - url: http://localhost:18080
    description: Local dev stack (docker compose)
security:
  - ApiKey: []

tags:
  - name: System
    description: Health probes and metrics (no auth).
  - name: Email
    description: Sending transactional and broadcast emails.
  - name: Messages
    description: Status and history of individual messages.
  - name: Events
    description: Delivery and engagement events.
  - name: Stats
    description: Aggregated delivery statistics.
  - name: Domains
    description: Sender domains, DKIM keys and DNS verification.
  - name: Templates
    description: Stored, versioned email templates.
  - name: Suppressions
    description: Addresses that must not receive email.
  - name: Webhooks
    description: Event delivery to client endpoints.
  - name: ApiKeys
    description: API key management.
  - name: Lists
    description: Contact lists for campaigns.
  - name: Campaigns
    description: Bulk (broadcast) campaigns to a contact list.
  - name: Forms
    description: Subscription forms builder, popup, and embed widgets (FRM-01).
  - name: Subscriptions
    description: Public submission, Double opt-in confirmation, and consent tracking (FRM-02, FRM-03).
  - name: Automations
    description: Trigger-based automated email workflows and sequences (AUT-01..05).
  - name: Deliverability
    description: Account reputation dashboard, provider breakdowns, warnings, and DNSBL blacklist monitoring (DLV-01, DLV-02).
  - name: Auth
    description: User authentication, registration, password management, and sessions (ACC-03, ACC-05).
  - name: Team
    description: Team member management, roles, and invitations (ACC-04).
  - name: AuditLogs
    description: Audit trail and security activity logging (ACC-06).
  - name: Media
    description: Image and media asset library, CDN delivery (EDT-04).
  - name: IPPools
    description: Outbound IP pools, dedicated IPs, and stream routing (MTA-05).
  - name: Status
    description: Public system status page, uptime, and incident management (OPS-05).
  - name: Helpdesk
    description: Support ticketing, client conversations, and impersonation (ADM-03).
  - name: Subaccounts
    description: Isolated subaccounts for agencies and multi-brand organizations (ACC-07).
  - name: SMS
    description: Multi-provider SMS messaging channel, alphanumeric sender IDs, and delivery DLR webhooks (CHN-01).
  - name: Telegram
    description: Telegram Bot messaging channel, deep linking subscriber acquisition, and broadcast notifications (CHN-02).
  - name: Integrations
    description: CRM and No-Code integrations (amoCRM, Bitrix24, RetailCRM, Albato, ApiX-Drive, Make, Zapier) [INT-03].
  - name: Migrations
    description: Competitor platform migration (Unisender, SendPulse, Mailchimp) for lists, contacts and templates [INT-04].
  - name: AIEmailAssistant
    description: AI Assistant for email editor, subject lines, tone rewriting, open rate prediction and dynamic prompt templates (EDT-07).
  - name: Mailboxes
    description: Virtual corporate mailboxes (IMAP/LMTP Roundcube/Dovecot) [BOX-01].

paths:
  # ---------------- System (implemented in E0) ----------------
  /healthz:
    get:
      tags: [System]
      summary: Liveness probe
      security: []
      operationId: getHealthz
      responses:
        "200":
          description: Process is alive
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HealthReport" }
  /readyz:
    get:
      tags: [System]
      summary: Readiness probe (checks dependencies)
      security: []
      operationId: getReadyz
      responses:
        "200":
          description: All dependencies are reachable
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HealthReport" }
        "503":
          description: At least one dependency is unavailable
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HealthReport" }
  /metrics:
    get:
      tags: [System]
      summary: Prometheus metrics
      security: []
      operationId: getMetrics
      responses:
        "200":
          description: Prometheus text exposition format
          content:
            text/plain:
              schema: { type: string }

  /v1/system/shaping/tsa:
    get:
      tags: [System]
      summary: Get Traffic Shaping Automation (TSA) runtime status
      description: |
        Requirement MTA-07. Returns the status of the KumoMTA TSA daemon:
        daemon health, active ready-queue and scheduled-queue suspensions,
        dynamic throttle overrides applied in response to 421/450 responses.
      operationId: getTSAStatus
      security: []
      responses:
        "200":
          description: TSA runtime status
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TSAStatusResponse" }
        "503":
          description: TSA daemon is unavailable
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /v1/system/shaping/test:
    post:
      tags: [System]
      summary: Test provider SMTP response against adaptive shaping rules
      description: |
        Requirement MTA-07. Evaluates an SMTP response (e.g. 421 rate limited)
        against configured automation rules to predict throttling actions.
      operationId: testShapingResponse
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TestShapingRequest" }
      responses:
        "200":
          description: Evaluation result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TestShapingResponse" }


  # ---------------- Email sending (E2) ----------------
  /v1/send:
    post:
      tags: [Email]
      summary: Send an email (alias for /v1/email/send)
      description: Alias route for standard ESP SDKs and integrations. Same behavior and contract as POST /v1/email/send.
      operationId: sendEmailAlias
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SendEmailRequest" }
      responses:
        "202":
          description: Accepted for delivery
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SendEmailResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "422": { $ref: "#/components/responses/Unprocessable" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }

  /v1/public/leads:
    post:
      tags: [System]
      summary: Submit a public landing contact or migration lead request
      description: Public endpoint for landing page consultations and closed-beta access requests (152-ФЗ compliant).
      security: []
      operationId: submitPublicLead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, contact, consent_152fz]
              properties:
                name: { type: string }
                contact: { type: string }
                company: { type: string }
                current_service: { type: string }
                monthly_volume: { type: string }
                source_page: { type: string }
                consent_152fz: { type: boolean }
                website: { type: string, description: "Anti-spam honeypot; must remain empty." }
      responses:
        "200":
          description: Lead saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }
                  lead_id: { type: string }
                  message: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }

  /v1/webhooks/{webhook_id}/test:
    parameters:
      - { name: webhook_id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Webhooks]
      summary: Send a test ping event to webhook endpoint
      operationId: testWebhook
      responses:
        "200":
          description: Test event queued
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }
                  message: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/email/send:
    post:
      tags: [Email]
      summary: Send an email to one or more recipients
      description: |
        Requirements TX-01, TX-02, TX-04..TX-06, TX-08, DOM-03. Required scope: `send`.

        - Every recipient (`to`, `cc`, `bcc`) gets its own `message_id`; delivery, events and
          bounces are tracked per message.
        - Batch sending (TX-02): up to 500 `to` recipients, each with its own `variables` and
          `metadata`; every `to` recipient receives a separate copy addressed only to them.
          `cc`/`bcc` are allowed only when `to` has exactly one recipient.
        - A `202` response guarantees the message is durably stored (S3 + JetStream, KPI-07).
        - The `from` domain must be a verified sender domain of the account (`403` otherwise),
          except in sandbox mode.
        - Sandbox (TX-08): requests made with a `test` key, or with `sandbox: true`, go through
          the whole pipeline but are never delivered to real mailboxes.
        - Limits (TX-05): message size ≤ 20 MB after base64 decoding (body ≤ 30 MB), `413` otherwise;
          executable attachment types (`.exe`, `.bat`, `.js`, …) are rejected.
        - Stored templates (TX-07): pass `template_id` to use a saved template; variables are substituted.
          Not supported yet: `send_at` (TX-10) — rejected with `400`.
      operationId: sendEmail
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SendEmailRequest" }
      responses:
        "202":
          description: Accepted for delivery
          headers:
            Idempotent-Replayed:
              description: "`true` when the response is a replay of an earlier request with the same `Idempotency-Key`"
              schema: { type: string, enum: ["true"] }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SendEmailResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/Conflict" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "503": { $ref: "#/components/responses/Unavailable" }

  # ---------------- Messages & events (E4, E6, TX-10, TX-12) ----------------
  /v1/messages:
    get:
      tags: [Messages]
      summary: List messages and search message activity log (TX-12)
      description: |
        Returns recent messages with their current delivery status.
        Supports search by subject, recipient email or message_id.
      operationId: listMessages
      parameters:
        - name: to
          in: query
          schema: { type: string, format: email }
          description: Filter by recipient email
        - name: status
          in: query
          schema: { $ref: "#/components/schemas/MessageStatus" }
          description: Filter by message delivery status
        - name: query
          in: query
          schema: { type: string }
          description: Search in subject, recipient, or message ID
        - name: from_date
          in: query
          schema: { type: string, format: date-time }
        - name: to_date
          in: query
          schema: { type: string, format: date-time }
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Messages page
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/MessageSummary" } }
                  next_cursor: { type: [string, "null"] }

  /v1/messages/{message_id}:
    get:
      tags: [Messages]
      summary: Get message status and its event history (TX-12)
      operationId: getMessage
      parameters:
        - $ref: "#/components/parameters/MessageId"
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Message
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Message" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/messages/{message_id}/cancel:
    parameters:
      - $ref: "#/components/parameters/MessageId"
    post:
      tags: [Messages]
      summary: Cancel a scheduled message before sending (TX-10)
      description: |
        Allowed only for messages with status `scheduled`. Once dispatched or delivered,
        the message cannot be cancelled (409 Conflict).
      operationId: cancelScheduledMessage
      responses:
        "200":
          description: Cancelled
          content:
            application/json:
              schema:
                type: object
                required: [status, message_id]
                properties:
                  status: { type: string, example: "cancelled" }
                  message_id: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409":
          description: Message is not in a cancellable scheduled state
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /v1/messages/{message_id}/raw:
    parameters:
      - $ref: "#/components/parameters/MessageId"
    get:
      tags: [Messages]
      summary: Download raw message RFC 822 EML (TX-14)
      description: |
        Returns the exact raw RFC 822 / MIME format of the sent email from object storage
        as long as the retention period has not expired.
      operationId: getMessageRaw
      responses:
        "200":
          description: Raw RFC 822 message file
          content:
            message/rfc822:
              schema:
                type: string
                format: binary
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "410":
          description: Message retention period has expired

  /v1/messages/{message_id}/content:
    parameters:
      - $ref: "#/components/parameters/MessageId"
    get:
      tags: [Messages]
      summary: Get parsed message body and headers (TX-14)
      description: Returns parsed HTML, Text and headers for message preview in UI.
      operationId: getMessageContent
      responses:
        "200":
          description: Parsed message content
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/MessageContent" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "410":
          description: Message retention period has expired

  /v1/messages/{message_id}/resend:
    parameters:
      - $ref: "#/components/parameters/MessageId"
    post:
      tags: [Messages]
      summary: Resend message (TX-14)
      description: Re-queues the message for delivery, optionally to an updated recipient.
      operationId: resendMessage
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                to:
                  type: string
                  format: email
                  description: Optional new recipient email. If omitted, sends to original recipient.
      responses:
        "202":
          description: Message accepted for redelivery
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ResendResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "410":
          description: Cannot resend expired message

  /v1/streams:
    get:
      tags: [Streams]
      summary: List message streams (TX-15)
      description: Returns all active transactional and broadcast message streams for the account.
      operationId: listStreams
      responses:
        "200":
          description: List of message streams
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/MessageStream" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Streams]
      summary: Create custom message stream (TX-15)
      operationId: createStream
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateStreamInput" }
      responses:
        "201":
          description: Stream created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MessageStream" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/streams/{stream_id}:
    parameters:
      - name: stream_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Streams]
      summary: Get message stream details (TX-15)
      operationId: getStream
      responses:
        "200":
          description: Stream details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MessageStream" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Streams]
      summary: Update message stream (TX-15)
      operationId: updateStream
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateStreamInput" }
      responses:
        "200":
          description: Updated stream
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MessageStream" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Streams]
      summary: Archive message stream (TX-15)
      operationId: deleteStream
      responses:
        "204":
          description: Stream archived
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403":
          description: Default streams cannot be deleted
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/streams/{stream_id}/stats:
    parameters:
      - name: stream_id
        in: path
        required: true
        schema: { type: string }
      - name: from
        in: query
        schema: { type: string, format: date-time }
      - name: to
        in: query
        schema: { type: string, format: date-time }
    get:
      tags: [Streams]
      summary: Get stream reputation and delivery metrics (TX-15)
      operationId: getStreamStats
      responses:
        "200":
          description: Stream statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/StreamStats" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/settings/retention:
    get:
      tags: [Settings]
      summary: Get default message retention days (TX-14)
      operationId: getRetentionSettings
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
                properties:
                  message_retention_days: { type: integer, example: 7 }
    patch:
      tags: [Settings]
      summary: Update default message retention days (TX-14)
      operationId: updateRetentionSettings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message_retention_days]
              properties:
                message_retention_days: { type: integer, minimum: 0, maximum: 365, example: 14 }
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
                properties:
                  message_retention_days: { type: integer, example: 14 }

  /v1/events:
    get:
      tags: [Events]
      summary: Query events (EVT-09)
      operationId: listEvents
      parameters:
        - { name: type, in: query, schema: { $ref: "#/components/schemas/EventType" } }
        - { name: message_id, in: query, schema: { type: string } }
        - { name: email, in: query, schema: { type: string, format: email } }
        - { name: tag, in: query, schema: { type: string } }
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
        - name: is_machine
          in: query
          schema: { type: boolean }
          description: "Filter by machine/bot events (Apple Mail Privacy Protection, Google proxy, link security scanners)"
        - name: stream
          in: query
          schema: { type: string, enum: [transactional, broadcast] }
          description: "Filter by stream type (transactional or broadcast)"
        - name: mailbox_provider
          in: query
          schema: { type: string }
          description: "Filter by mailbox provider (gmail, yandex, mailru, microsoft, rambler, yahoo, other)"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Events page
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Event" } }
                  next_cursor: { type: [string, "null"] }
    post:
      tags: [Events]
      summary: Ingest product & behavioral event (cart, checkout, signup) with deduplication (AUT-02)
      description: |
        Scope: `send`. Ingests external product events with idempotent deduplication by `external_event_id`.
        Automatically triggers active workflows and advances waiting `wait_for_event` nodes.
      operationId: recordEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [event_type, external_event_id]
              properties:
                event_type: { type: string, example: "cart.abandoned" }
                external_event_id: { type: string, example: "evt_98765" }
                source: { type: string, example: "store_checkout" }
                occurred_at: { type: string, format: date-time }
                contact:
                  type: object
                  properties:
                    external_contact_id: { type: string, example: "usr_555" }
                    email: { type: string, format: email, example: "alex@example.ru" }
                    phone: { type: string, example: "+79991234567" }
                    max_id: { type: string, description: "ID пользователя в Мессенджере MAX", example: "max_u_123" }
                    telegram_id: { type: string, example: "123456789" }
                    first_name: { type: string, example: "Алексей" }
                    last_name: { type: string, example: "Смирнов" }
                    attributes: { type: object, additionalProperties: true }
                payload: { type: object, additionalProperties: true }
      responses:
        "201":
          description: Event accepted and recorded
          content:
            application/json:
              schema:
                type: object
                properties:
                  event_id: { type: string }
                  event_type: { type: string }
                  is_duplicate: { type: boolean }
                  status: { type: string, example: "accepted" }
                  received_at: { type: string, format: date-time }
        "200":
          description: Duplicate event acknowledged
          content:
            application/json:
              schema:
                type: object
                properties:
                  event_id: { type: string }
                  event_type: { type: string }
                  is_duplicate: { type: boolean }
                  status: { type: string, example: "accepted" }
                  received_at: { type: string, format: date-time }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/events/export:
    get:
      tags: [Events]
      summary: Export events to CSV stream (EVT-10)
      description: |
        Scope: `read_events`. Streams filtered events directly in CSV format with UTF-8 BOM.
        Supports filtering by date range, event type, message ID, email, tag, campaign ID, and bot/machine events.
      operationId: exportEventsCSV
      parameters:
        - { name: type, in: query, schema: { $ref: "#/components/schemas/EventType" } }
        - { name: message_id, in: query, schema: { type: string } }
        - { name: email, in: query, schema: { type: string, format: email } }
        - { name: campaign_id, in: query, schema: { type: string } }
        - { name: tag, in: query, schema: { type: string } }
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
        - name: is_machine
          in: query
          schema: { type: boolean }
          description: "Filter by machine/bot events"
        - name: stream
          in: query
          schema: { type: string, enum: [transactional, broadcast] }
          description: "Filter by stream type (transactional or broadcast)"
        - name: mailbox_provider
          in: query
          schema: { type: string }
          description: "Filter by mailbox provider (gmail, yandex, mailru, microsoft, rambler, yahoo, other)"
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500000, default: 50000 }
          description: Maximum number of events to export
      responses:
        "200":
          description: CSV event file stream
          content:
            text/csv:
              schema:
                type: string
                format: binary
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/events/export-jobs:
    get:
      tags: [Events]
      summary: List event export background jobs (EVT-10)
      description: "Scope: `read_events`."
      operationId: listEventExportJobs
      parameters:
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: List of export jobs
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/EventExportJob" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      tags: [Events]
      summary: Create an asynchronous event export job (EVT-10)
      description: "Scope: `read_events`. Enqueues a background job to export events to S3 in CSV or JSONL format."
      operationId: createEventExportJob
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateEventExportJobRequest" }
      responses:
        "201":
          description: Job created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EventExportJob" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/events/export-jobs/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Events]
      summary: Get status of an event export job (EVT-10)
      description: "Scope: `read_events`."
      operationId: getEventExportJob
      responses:
        "200":
          description: Job status
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EventExportJob" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/events/export-jobs/{id}/download:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Events]
      summary: Download completed event export file (EVT-10)
      description: "Scope: `read_events`."
      operationId: downloadEventExportJob
      responses:
        "200":
          description: Exported data file
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409":
          description: Job is not completed yet
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /v1/events/stream-sinks:
    get:
      tags: [Events]
      summary: List event streaming sinks for S3/ClickHouse (EVT-10)
      description: "Scope: `read_events`."
      operationId: listEventStreamSinks
      responses:
        "200":
          description: List of active and configured event stream sinks
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/EventStreamSink" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      tags: [Events]
      summary: Create an event streaming sink to S3 or ClickHouse (EVT-10)
      description: |
        Scope: `read_events`. Configures real-time streaming of events directly into customer S3 bucket or ClickHouse table.
      operationId: createEventStreamSink
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateEventStreamSinkRequest" }
      responses:
        "201":
          description: Stream sink created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EventStreamSink" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/events/stream-sinks/{sink_id}:
    parameters:
      - name: sink_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Events]
      summary: Get event stream sink details (EVT-10)
      description: "Scope: `read_events`."
      operationId: getEventStreamSink
      responses:
        "200":
          description: Stream sink details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EventStreamSink" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Events]
      summary: Update event stream sink (EVT-10)
      description: "Scope: `read_events`."
      operationId: updateEventStreamSink
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateEventStreamSinkRequest" }
      responses:
        "200":
          description: Stream sink updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EventStreamSink" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Events]
      summary: Delete event stream sink (EVT-10)
      description: "Scope: `read_events`."
      operationId: deleteEventStreamSink
      responses:
        "204": { description: Sink deleted }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/events/stream-sinks/test:
    post:
      tags: [Events]
      summary: Test connection to customer S3 or ClickHouse sink (EVT-10)
      description: "Scope: `read_events`."
      operationId: testEventStreamSinkConnection
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateEventStreamSinkRequest" }
      responses:
        "200":
          description: Connection test successful
          content:
            application/json:
              schema:
                type: object
                required: [success, message]
                properties:
                  success: { type: boolean, example: true }
                  message: { type: string, example: "Connection established successfully" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/stats:
    get:
      tags: [Stats]
      summary: Aggregated delivery statistics (ANL-02)
      operationId: getStats
      parameters:
        - { name: from, in: query, required: true, schema: { type: string, format: date-time } }
        - { name: to, in: query, required: true, schema: { type: string, format: date-time } }
        - name: group_by
          in: query
          schema: { type: string, enum: [hour, day, tag, domain, mailbox_provider, stream] }
        - { name: tag, in: query, schema: { type: string } }
        - name: stream
          in: query
          schema: { type: string, enum: [transactional, broadcast] }
          description: "Filter stats by stream type (transactional or broadcast)"
        - name: mailbox_provider
          in: query
          schema: { type: string }
          description: "Filter stats by recipient mailbox provider (gmail, yandex, mailru, microsoft, rambler, yahoo, other)"
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Stats
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/StatsRow" } }

  /v1/quota:
    get:
      tags: [Stats]
      summary: Account sending quota and reputation (ABS-01, BIL-01)
      description: |
        Returns current sending limits (hourly, daily, monthly), usage counters, warmup tier,
        and account reputation status.
      operationId: getQuota
      responses:
        "200":
          description: Quota details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QuotaInfo" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  # ---------------- Billing (BIL-02, BIL-03, BIL-04) ----------------
  /v1/billing/plans:
    get:
      tags: [Billing]
      summary: List available subscription plans and limits (BIL-02)
      description: Returns all plans (Free, Starter, Pro, Enterprise, PAYG) with pricing, message quotas, contact limits, and features.
      operationId: listBillingPlans
      responses:
        "200":
          description: List of available plans
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/BillingPlan" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/account:
    get:
      tags: [Billing]
      summary: Get account billing details, balance and subscription status (BIL-03, BIL-04)
      description: Returns active plan, balance in RUB, subscription expiration, auto-renew status, usage counters, and legal requisites.
      operationId: getBillingAccount
      responses:
        "200":
          description: Account billing details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BillingAccountInfo" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/requisites:
    patch:
      tags: [Billing]
      summary: Update company legal requisites for corporate invoicing and EDO (BIL-03)
      description: Sets INN, KPP, company name, bank details, and EDO operator (Diadoc/SBIS) for issuing formal invoices and closure acts.
      operationId: updateBillingRequisites
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CompanyRequisitesInput" }
      responses:
        "200":
          description: Updated requisites
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CompanyRequisites" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/topup:
    post:
      tags: [Billing]
      summary: Initiate balance top-up via YooKassa, CloudPayments or SBP (BIL-03)
      description: Creates a payment session and returns confirmation URL to redirect the user for card or SBP payment.
      operationId: topupBalance
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TopupRequest" }
      responses:
        "200":
          description: Payment session created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TopupResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/subscribe:
    post:
      tags: [Billing]
      summary: Change plan or renew subscription (BIL-02, BIL-04)
      description: Upgrades/downgrades current account plan, deducts funds from balance or creates an online checkout session.
      operationId: subscribePlan
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SubscribePlanRequest" }
      responses:
        "200":
          description: Plan activated or payment URL returned
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SubscribePlanResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/invoices:
    get:
      tags: [Billing]
      summary: List billing invoices and payment history (BIL-03)
      description: Returns invoices, top-ups and corporate bank payment orders.
      operationId: listBillingInvoices
      responses:
        "200":
          description: Invoices history
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/BillingInvoice" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Billing]
      summary: Create corporate invoice for legal entities (счёт на оплату) (BIL-03)
      description: Generates a formal invoice with bank details, QR-code, and closure documents via EDO.
      operationId: createBankInvoice
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateBankInvoiceRequest" }
      responses:
        "201":
          description: Invoice created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BillingInvoice" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/invoices/{invoice_id}/html:
    get:
      tags: [Billing]
      summary: Get printable HTML invoice / payment order (BIL-03)
      description: Returns formatted Russian payment invoice (счет на оплату с факсимиле) with bank requisites.
      operationId: getInvoiceHtml
      parameters:
        - name: invoice_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Printable HTML document
          content:
            text/html:
              schema: { type: string }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/billing/webhooks/yookassa:
    post:
      tags: [Billing]
      summary: YooKassa webhook endpoint (BIL-03)
      description: Receives asynchronous notifications about payment successes and refunds from YooKassa.
      operationId: yookassaWebhook
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Webhook received and processed
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/billing/webhooks/cloudpayments:
    post:
      tags: [Billing]
      summary: CloudPayments webhook endpoint (pay/check/fail) (BIL-03)
      description: Receives payment notifications from CloudPayments.
      operationId: cloudpaymentsWebhook
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Webhook processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  code: { type: integer, example: 0 }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/billing/promo-codes:
    get:
      tags: [Billing]
      summary: List active promo codes (BIL-05)
      description: Returns available promo codes, types, and discounts.
      operationId: listPromoCodes
      parameters:
        - name: active_only
          in: query
          schema: { type: boolean, default: true }
      responses:
        "200":
          description: List of promo codes
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PromoCode" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Billing]
      summary: Create new promo code (BIL-05)
      description: Create a discount coupon or bonus code for promotions.
      operationId: createPromoCode
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PromoCodeInput" }
      responses:
        "201":
          description: Promo code created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PromoCode" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/promo-codes/validate:
    post:
      tags: [Billing]
      summary: Validate promo code and calculate discount (BIL-05)
      description: Verifies code validity, expiration, limits and calculates discount or bonus amount.
      operationId: validatePromoCode
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PromoCodeValidateInput" }
      responses:
        "200":
          description: Validation result with calculated discount
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PromoCodeValidateResult" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/promo-codes/redeem:
    post:
      tags: [Billing]
      summary: Redeem promo code to account balance (BIL-05)
      description: Activates bonus balance or credits onto current account.
      operationId: redeemPromoCode
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PromoCodeRedeemInput" }
      responses:
        "200":
          description: Code redeemed successfully
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PromoCodeRedeemResult" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/referrals/me:
    get:
      tags: [Billing]
      summary: Get referral program details, invite link, and stats (BIL-05)
      description: Returns personal referral link, bonus amount, and list of invited clients.
      operationId: getReferralProgramInfo
      responses:
        "200":
          description: Referral program summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ReferralProgramInfo" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/referrals/bind:
    post:
      tags: [Billing]
      summary: Bind referral code to current account (BIL-05)
      description: Links referred account to referrer and credits welcome bonus.
      operationId: bindReferralCode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [referral_code]
              properties:
                referral_code: { type: string }
      responses:
        "200":
          description: Referral bound successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  bonus_rub: { type: number }
                  message: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/partners/profile:
    get:
      tags: [Billing]
      summary: Get agency partner profile and commission stats (BIL-05)
      description: Returns agency tier, rev-share commission rate, payout balance, and banking requisites.
      operationId: getPartnerProfile
      responses:
        "200":
          description: Partner profile
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PartnerProfile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Billing]
      summary: Create or update agency partner requisites (BIL-05)
      description: Updates payout details (bank account, BIK, legal name) for commission withdrawals.
      operationId: updatePartnerProfile
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PartnerProfileInput" }
      responses:
        "200":
          description: Updated partner profile
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PartnerProfile" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/partners/clients:
    get:
      tags: [Billing]
      summary: List clients referred by agency partner (BIL-05)
      description: Returns client accounts, total spend, and accrued rev-share commission.
      operationId: listPartnerClients
      responses:
        "200":
          description: List of agency clients
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PartnerClient" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/partners/commissions:
    get:
      tags: [Billing]
      summary: List partner commission accruals ledger (BIL-05)
      description: Audit log of earned rev-share commissions from client payments.
      operationId: listPartnerCommissions
      responses:
        "200":
          description: Commission transactions
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PartnerCommission" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/partners/payouts:
    get:
      tags: [Billing]
      summary: List agency partner payout withdrawal requests (BIL-05)
      description: Returns withdrawal history, status (pending/approved/paid) and bank transfers.
      operationId: listPartnerPayouts
      responses:
        "200":
          description: List of payouts
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PartnerPayout" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Billing]
      summary: Request commission withdrawal to bank account (BIL-05)
      description: Submits a payout request from available partner commission balance.
      operationId: createPartnerPayout
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PartnerPayoutInput" }
      responses:
        "201":
          description: Payout request created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PartnerPayout" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/billing/partners/payouts/transfer-to-balance:
    post:
      tags: [Billing]
      summary: Instantly transfer partner commission to ESP service balance (BIL-05)
      description: Converts partner earnings into ESP service balance without bank commission or delay.
      operationId: transferPartnerPayoutToBalance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount_rub]
              properties:
                amount_rub: { type: number, minimum: 100 }
      responses:
        "200":
          description: Transferred successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  transferred_amount_rub: { type: number }
                  new_payout_balance_rub: { type: number }
                  new_service_balance_rub: { type: number }
        "400": { $ref: "#/components/responses/ValidationError" }
  # ---------------- Email Senders (DOM-09) ----------------
  /v1/senders:
    get:
      tags: [Senders]
      summary: List registered sender email identities (DOM-09)
      description: "Scope: `manage_domains`."
      operationId: listSenders
      responses:
        "200":
          description: List of senders
          content:
            application/json:
              schema:
                type: object
                required: [data, total]
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Sender" } }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      tags: [Senders]
      summary: Register a new sender email identity (DOM-09)
      description: "Scope: `manage_domains`."
      operationId: createSender
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateSenderInput" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Sender" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/senders/{sender_id}:
    get:
      tags: [Senders]
      summary: Get sender details (DOM-09)
      description: "Scope: `manage_domains`."
      operationId: getSender
      parameters:
        - name: sender_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Sender details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Sender" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Senders]
      summary: Update sender details (DOM-09)
      description: "Scope: `manage_domains`."
      operationId: updateSender
      parameters:
        - name: sender_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateSenderInput" }
      responses:
        "200":
          description: Updated sender
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Sender" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Senders]
      summary: Delete sender identity (DOM-09)
      description: "Scope: `manage_domains`."
      operationId: deleteSender
      parameters:
        - name: sender_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204":
          description: Sender deleted
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/senders/{sender_id}/default:
    post:
      tags: [Senders]
      summary: Set sender as default for account (DOM-09)
      description: "Scope: `manage_domains`."
      operationId: setDefaultSender
      parameters:
        - name: sender_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Sender marked as default
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Sender" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Domains (E1) ----------------
  /v1/domains:
    get:
      tags: [Domains]
      summary: List sending domains
      description: "Scope: `manage_domains`."
      operationId: listDomains
      responses:
        "200":
          description: Domains
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Domain" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/RateLimited" }
    post:
      tags: [Domains]
      summary: Add a sending domain (DOM-01)
      description: |
        Scope: `manage_domains`. Generates a DKIM key (RSA-2048) and returns the DNS records to publish.
        The name is normalised (lower case, IDN → punycode). Public suffixes (`ru`, `co.uk`) and IP
        addresses are rejected. Subdomains (`mail.example.com`) are allowed.
      operationId: createDomain
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              additionalProperties: false
              properties:
                name: { type: string, maxLength: 253, example: mail.example.com }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Domain" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/Conflict" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/domains/{domain_id}:
    parameters:
      - $ref: "#/components/parameters/DomainId"
    get:
      tags: [Domains]
      summary: Get a domain with DNS records and verification state
      description: "Scope: `manage_domains`."
      operationId: getDomain
      responses:
        "200":
          description: Domain
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Domain" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
    delete:
      tags: [Domains]
      summary: Delete a domain and its DKIM keys
      description: "Scope: `manage_domains`."
      operationId: deleteDomain
      responses:
        "204": { description: Deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/domains/{domain_id}/verify:
    post:
      tags: [Domains]
      summary: Re-check DNS records now (DOM-02)
      description: |
        Scope: `manage_domains`. Domains are also re-checked automatically in the background.
        A temporary DNS failure never downgrades a verified domain (the check is marked `error`).
      operationId: verifyDomain
      parameters:
        - $ref: "#/components/parameters/DomainId"
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Domain" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/domains/{domain_id}/unisender/sync:
    post:
      tags: [Domains]
      summary: Sync and validate domain with Unisender Go upstream relay
      description: |
        Scope: `manage_domains`. Registers domain in Unisender Go if missing and triggers DNS validation check.
      operationId: syncDomainUnisender
      parameters:
        - $ref: "#/components/parameters/DomainId"
      responses:
        "200":
          description: Updated domain with Unisender Go sync status
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Domain" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/domains/{domain_id}/sending-subdomain:
    put:
      tags: [Domains]
      summary: Set the sending subdomain and its delegation mode (CNAME or NS)
      description: |
        Scope: `manage_domains`. Sets the subdomain (default `send` -> `send.example.com`) used as the
        sender for white-label backup delivery (warm-up spillover, IP quarantine failover); the original
        address becomes Reply-To. The customer delegates it either with two CNAME records into the
        platform's technical zone (`mode: cname`, default) or with two NS records to the platform
        nameservers (`mode: ns`). The records to publish are returned in `relay_dns`. The label is rejected
        with `409` when it already has foreign DNS records (e.g. `mail.` used by mailbox hosting);
        wildcard records are ignored. The sending domain is (re)registered in the relay immediately.
        [MTA-05, MTA-11, DOM-01]
      operationId: setDomainSendingSubdomain
      parameters:
        - $ref: "#/components/parameters/DomainId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subdomain]
              properties:
                subdomain:
                  type: string
                  example: send
                  description: DNS label (a-z, 0-9, -), or `@` for the domain itself (NS mode only).
                mode:
                  type: string
                  enum: [cname, ns]
                  description: Delegation mode; omitted keeps the current one (default `cname`).
      responses:
        "200":
          description: Updated domain
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Domain" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "409":
          description: The subdomain already has foreign DNS records (details.host, details.records).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/admin/domains:
    get:
      tags: [Admin]
      summary: List all domains across all platform accounts
      description: Administrator only. Returns all registered domains with owner account information.
      operationId: adminListDomains
      responses:
        "200":
          description: All domains
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Domain" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/domains/{domain_id}/dkim/rotate:
    post:
      tags: [Domains]
      summary: Start DKIM key rotation without downtime (DOM-04)
      description: |
        Scope: `manage_domains`. Creates a new `pending` key with a new selector. Signing keeps using the
        current `active` key until the new DNS record is verified; then the new key becomes `active` and
        the old one `retired`. Keep the retired record in DNS for at least 7 days.
      operationId: rotateDomainDkim
      parameters:
        - $ref: "#/components/parameters/DomainId"
      responses:
        "200":
          description: Domain with the new pending DKIM record
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Domain" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /v1/domains/{domain_id}/dmarc:
    get:
      tags: [Domains]
      summary: Audit DMARC policy and get recommendations (DOM-07)
      description: "Scope: `manage_domains`. Analyzes the published DMARC DNS record, assesses spoofing risk, and provides concrete recommendations and suggested DNS record."
      operationId: getDomainDMARC
      parameters:
        - $ref: "#/components/parameters/DomainId"
      responses:
        "200":
          description: DMARC policy audit and recommendations
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DMARCPolicyAudit" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/domains/{domain_id}/dmarc/reports:
    post:
      tags: [Domains]
      summary: Upload DMARC aggregate XML / Gzip / Zip report (DOM-07)
      description: "Scope: `manage_domains`. Ingests an RFC 7489 rua report from inbox providers (Gmail, Mail.ru, Yandex)."
      operationId: uploadDomainDMARCReport
      parameters:
        - $ref: "#/components/parameters/DomainId"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
          application/xml:
            schema:
              type: string
          application/gzip:
            schema:
              type: string
              format: binary
          application/zip:
            schema:
              type: string
              format: binary
      responses:
        "201":
          description: Report parsed and stored
          content:
            application/json:
              schema:
                type: object
                properties:
                  report: { $ref: "#/components/schemas/DMARCReport" }
                  records_count: { type: integer }
                  message: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }
    get:
      tags: [Domains]
      summary: List DMARC reports and get aggregate stats (DOM-07)
      description: "Scope: `manage_domains`. Returns received aggregate feedback reports and summary statistics (pass rate, quarantine, reject, breakdown by org and IP)."
      operationId: listDomainDMARCReports
      parameters:
        - $ref: "#/components/parameters/DomainId"
        - name: limit
          in: query
          required: false
          schema: { type: integer, default: 20 }
        - name: offset
          in: query
          required: false
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: DMARC reports and stats
          content:
            application/json:
              schema:
                type: object
                required: [stats, reports, total]
                properties:
                  stats: { $ref: "#/components/schemas/DMARCAggregateStats" }
                  reports:
                    type: array
                    items: { $ref: "#/components/schemas/DMARCReport" }
                  total: { type: integer }
        "404": { $ref: "#/components/responses/NotFound" }


  /v1/domains/{domain_id}/bimi:
    get:
      tags: [Domains]
      summary: Get domain BIMI configuration and readiness diagnostics (DOM-08)
      description: "Scope: `manage_domains`. Returns current BIMI configuration, stored diagnostics, and DNS prerequisites."
      operationId: getDomainBIMI
      parameters:
        - in: path
          name: domain_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: BIMI configuration and readiness details
          content:
            application/json:
              schema:
                type: object
                properties:
                  domain_id: { type: string }
                  domain_name: { type: string }
                  selector: { type: string }
                  config: { $ref: "#/components/schemas/BimiConfig" }
                  live_check: { $ref: "#/components/schemas/BimiCheckResult" }
                  suggested_dns:
                    type: object
                    properties:
                      type: { type: string }
                      host: { type: string }
                      value: { type: string }
                  spf_ready: { type: boolean }
                  dkim_ready: { type: boolean }
                  dmarc_policy: { type: string }
                  dmarc_pct: { type: integer }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      tags: [Domains]
      summary: Set or update BIMI configuration (DOM-08)
      description: "Scope: `manage_domains`. Saves SVG logo URL and optional VMC certificate URL."
      operationId: setDomainBIMI
      parameters:
        - in: path
          name: domain_id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SetBimiInput" }
      responses:
        "200":
          description: BIMI configuration saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  config: { $ref: "#/components/schemas/BimiConfig" }
                  suggested_dns:
                    type: object
                    properties:
                      type: { type: string }
                      host: { type: string }
                      value: { type: string }
                  message: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }
        "422": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Domains]
      summary: Delete BIMI configuration (DOM-08)
      description: "Scope: `manage_domains`. Clears BIMI configuration from database."
      operationId: deleteDomainBIMI
      parameters:
        - in: path
          name: domain_id
          required: true
          schema: { type: string }
      responses:
        "204":
          description: BIMI configuration removed
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/domains/{domain_id}/bimi/check:
    post:
      tags: [Domains]
      summary: Trigger live BIMI diagnostics check (DOM-08)
      description: "Scope: `manage_domains`. Queries DNS, downloads SVG Tiny P.S. logo and VMC certificate, validates RFC compliance and evaluates mailbox provider support (Gmail, Apple, Yahoo)."
      operationId: checkDomainBIMI
      parameters:
        - in: path
          name: domain_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Diagnostic result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BimiCheckResult" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/domains/{domain_id}/bimi/validate-svg:
    post:
      tags: [Domains]
      summary: Validate SVG markup against strict BIMI SVG Tiny P.S. spec (DOM-08)
      description: "Scope: `manage_domains`. Validates SVG markup for 1:1 aspect ratio, absence of scripts/animations/external links, and Tiny P.S. profile."
      operationId: validateDomainBimiSVG
      parameters:
        - in: path
          name: domain_id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [svg]
              properties:
                svg: { type: string }
      responses:
        "200":
          description: SVG validation result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BimiSvgValidation" }
        "422": { $ref: "#/components/responses/ValidationError" }

  /v1/domains/{domain_id}/bimi/validate-vmc:
    post:
      tags: [Domains]
      summary: Validate PEM X.509 certificate for VMC criteria (DOM-08)
      description: "Scope: `manage_domains`. Validates PEM certificate expiration, issuer, SAN domain match, and BIMI OID."
      operationId: validateDomainBimiVMC
      parameters:
        - in: path
          name: domain_id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pem]
              properties:
                pem: { type: string }
                domain: { type: string }
      responses:
        "200":
          description: VMC validation result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BimiVmcValidation" }
        "422": { $ref: "#/components/responses/ValidationError" }

  # ---------------- Templates (E3) ----------------
  /v1/templates:
    get:
      tags: [Templates]
      summary: List templates
      operationId: listTemplates
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Templates page
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Template" } }
                  next_cursor: { type: [string, "null"] }
    post:
      tags: [Templates]
      summary: Create a template (TPL-02)
      operationId: createTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TemplateInput" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Template" }
        "400": { $ref: "#/components/responses/ValidationError" }
  /v1/templates/{template_id}:
    parameters:
      - { name: template_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Templates]
      summary: Get a template (latest version)
      operationId: getTemplate
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Template
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Template" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Templates]
      summary: Update a template (creates a new version)
      operationId: updateTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TemplateInput" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Template" }
    delete:
      tags: [Templates]
      summary: Delete a template
      operationId: deleteTemplate
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "204": { description: Deleted }
  /v1/templates/{template_id}/render:
    post:
      tags: [Templates]
      summary: Render a template with test variables (preview)
      operationId: renderTemplate
      parameters:
        - { name: template_id, in: path, required: true, schema: { type: string } }
        - { name: locale, in: query, required: false, schema: { type: string }, description: Optional target locale (e.g. en, ru) }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                locale: { type: string, description: Target language locale }
                variables: { type: object, additionalProperties: true }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Rendered content
          content:
            application/json:
              schema:
                type: object
                properties:
                  subject: { type: string }
                  html: { type: string }
                  text: { type: string }
                  locale: { type: string }
        "422": { $ref: "#/components/responses/ValidationError" }

  /v1/templates/preview-clients:
    post:
      tags: [Templates]
      summary: Real email client rendering compatibility and dark mode audit (EDT-05)
      description: |
        Simulates rendering across major email clients (Gmail, Apple Mail, Outlook Windows, Yandex, Mail.ru)
        and conducts a Litmus/Email on Acid style audit for clipping, MSO bugs, and dark mode readiness.
      operationId: previewTemplateClients
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClientPreviewInput"
      responses:
        "200":
          description: Client preview and dark mode analysis report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientPreviewReport"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/templates/{template_id}/locales:
    parameters:
      - { name: template_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Templates]
      summary: List localized versions of a template (TPL-05)
      operationId: listTemplateLocales
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: List of template locales
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/TemplateLocale" } }

  /v1/templates/{template_id}/locales/{locale}:
    parameters:
      - { name: template_id, in: path, required: true, schema: { type: string } }
      - { name: locale, in: path, required: true, schema: { type: string } }
    get:
      tags: [Templates]
      summary: Get localized version of a template (TPL-05)
      operationId: getTemplateLocale
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Template locale details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TemplateLocale" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Templates]
      summary: Create or update localized version of a template (TPL-05)
      operationId: setTemplateLocale
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TemplateLocaleInput" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Saved locale variant
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TemplateLocale" }
        "400": { $ref: "#/components/responses/ValidationError" }
    delete:
      tags: [Templates]
      summary: Delete localized version of a template (TPL-05)
      operationId: deleteTemplateLocale
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "204": { description: Locale deleted }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/templates/{template_id}/blocks:
    parameters:
      - { name: template_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Templates]
      summary: List dynamic content blocks for a template (TPL-05)
      operationId: listTemplateBlocks
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: List of dynamic blocks
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/DynamicBlock" } }
    post:
      tags: [Templates]
      summary: Create dynamic content block for a template (TPL-05)
      operationId: createTemplateBlock
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DynamicBlockInput" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "201":
          description: Created dynamic block
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DynamicBlock" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/dynamic-blocks/{block_id}:
    parameters:
      - { name: block_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Templates]
      summary: Get dynamic block details (TPL-05)
      operationId: getDynamicBlock
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Dynamic block
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DynamicBlock" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Templates]
      summary: Update dynamic block (TPL-05)
      operationId: updateDynamicBlock
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DynamicBlockInput" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Updated dynamic block
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DynamicBlock" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Templates]
      summary: Delete dynamic block (TPL-05)
      operationId: deleteDynamicBlock
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "204": { description: Dynamic block deleted }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Suppressions (E4) ----------------
  /v1/suppressions:
    get:
      tags: [Suppressions]
      summary: List suppressed addresses (SUP-03, SUP-05)
      operationId: listSuppressions
      parameters:
        - name: reason
          in: query
          schema: { $ref: "#/components/schemas/SuppressionReason" }
        - name: stream
          in: query
          schema: { $ref: "#/components/schemas/SuppressionStream" }
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Suppressions page
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Suppression" } }
                  next_cursor: { type: [string, "null"] }
    post:
      tags: [Suppressions]
      summary: Add addresses to the suppression list (SUP-03, SUP-05)
      operationId: createSuppressions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                emails: { type: array, maxItems: 1000, items: { type: string, format: email } }
                email: { type: string, format: email }
                reason: { $ref: "#/components/schemas/SuppressionReason" }
                stream: { $ref: "#/components/schemas/SuppressionStream" }
                details: { type: string }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200": { description: Added }
  /v1/suppressions/{email}:
    delete:
      tags: [Suppressions]
      summary: Remove an address from the suppression list (SUP-03, SUP-05)
      description: Removing `complaint` entries is restricted (SUP-03).
      operationId: deleteSuppression
      parameters:
        - { name: email, in: path, required: true, schema: { type: string, format: email } }
        - name: stream
          in: query
          schema: { $ref: "#/components/schemas/SuppressionStream" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "204": { description: Removed }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/suppressions/check:
    get:
      tags: [Suppressions]
      summary: Check if an email is suppressed across account and global lists (SUP-02, SUP-04, SUP-05)
      operationId: checkSuppression
      parameters:
        - name: email
          in: query
          required: true
          schema: { type: string, format: email }
        - name: stream
          in: query
          schema: { $ref: "#/components/schemas/SuppressionStream" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Check result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SuppressionCheckResult" }

  /v1/system/suppressions:
    get:
      tags: [Suppressions, System]
      summary: List platform-wide global suppressions, spam traps and honeypots (SUP-04)
      operationId: listGlobalSuppressions
      parameters:
        - name: reason
          in: query
          schema: { $ref: "#/components/schemas/GlobalSuppressionReason" }
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Global suppressions list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/GlobalSuppression" } }
                  next_cursor: { type: [string, "null"] }
    post:
      tags: [Suppressions, System]
      summary: Add an address or pattern to the platform-wide global suppression list (SUP-04)
      operationId: createGlobalSuppression
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, reason]
              properties:
                email: { type: string }
                reason: { $ref: "#/components/schemas/GlobalSuppressionReason" }
                details: { type: string }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GlobalSuppression" }
  /v1/system/suppressions/{email}:
    delete:
      tags: [Suppressions, System]
      summary: Remove an address from platform-wide global suppression list (SUP-04)
      operationId: deleteGlobalSuppression
      parameters:
        - { name: email, in: path, required: true, schema: { type: string } }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "204": { description: Removed }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Webhooks (E6) ----------------
  /v1/webhooks:
    get:
      tags: [Webhooks]
      summary: List webhook endpoints
      operationId: listWebhooks
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Webhooks
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Webhook" } }
    post:
      tags: [Webhooks]
      summary: Create a webhook endpoint (EVT-08)
      operationId: createWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookInput" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "201":
          description: Created. `signing_secret` is returned only once.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Webhook" }
  /v1/webhooks/{webhook_id}:
    parameters:
      - { name: webhook_id, in: path, required: true, schema: { type: string } }
    put:
      tags: [Webhooks]
      summary: Update a webhook endpoint
      operationId: updateWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookInput" }
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "200":
          description: Updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Webhook" }
    delete:
      tags: [Webhooks]
      summary: Delete a webhook endpoint
      operationId: deleteWebhook
      responses:
        "401": { $ref: "#/components/responses/Unauthorized" }
        "204": { description: Deleted }

  # ---------------- API keys (E1) ----------------
  /v1/api-keys:
    get:
      tags: [ApiKeys]
      summary: List active API keys (secrets are never returned)
      description: "Scope: `manage_keys`."
      operationId: listApiKeys
      responses:
        "200":
          description: Keys
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/ApiKey" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/RateLimited" }
    post:
      tags: [ApiKeys]
      summary: Create an API key (ACC-02). The secret is shown only once.
      description: |
        Scope: `manage_keys`. The new key may only receive scopes the calling key already has.
        A `test` key cannot create a `live` key. Only a SHA-256 hash of the secret is stored.
      operationId: createApiKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, scopes]
              additionalProperties: false
              properties:
                name: { type: string, minLength: 1, maxLength: 100 }
                mode: { allOf: [{ $ref: "#/components/schemas/ApiKeyMode" }], default: live }
                scopes: { type: array, minItems: 1, uniqueItems: true, items: { $ref: "#/components/schemas/Scope" } }
                ip_allowlist: { type: array, items: { type: string }, description: "Optional IP allowlist (IPv4, IPv6, CIDR) (ACC-08)" }
                rate_limit_rps: { type: integer, nullable: true, description: "Optional sub-rate limit in requests per second (ACC-11)" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/ApiKey"
                  - type: object
                    required: [secret]
                    properties:
                      secret: { type: string, example: sk_live_4f8Kd0VqZr9mXc2bT7hNwE1yLs6uPaJg3iRkQoBnD5e }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/api-keys/{key_id}:
    delete:
      tags: [ApiKeys]
      summary: Revoke an API key
      description: "Scope: `manage_keys`. Takes effect on all API nodes within 10 seconds."
      operationId: revokeApiKey
      parameters:
        - $ref: "#/components/parameters/KeyId"
      responses:
        "204": { description: Revoked }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  # ---------------- Subaccounts (ACC-07) ----------------
  /v1/subaccounts:
    get:
      tags: [Subaccounts]
      summary: List subaccounts
      description: "Scope: `manage_keys`. Lists all subaccounts of the tenant."
      operationId: listSubaccounts
      responses:
        "200":
          description: Subaccounts list
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Subaccount" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      tags: [Subaccounts]
      summary: Create a subaccount
      description: "Scope: `manage_keys`. Provisions a new subaccount."
      operationId: createSubaccount
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SubaccountInput" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Subaccount" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/subaccounts/{id}:
    get:
      tags: [Subaccounts]
      summary: Get subaccount details
      operationId: getSubaccount
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subaccount details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Subaccount" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Subaccounts]
      summary: Update subaccount
      operationId: updateSubaccount
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SubaccountUpdate" }
      responses:
        "200":
          description: Updated subaccount
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Subaccount" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Subaccounts]
      summary: Delete subaccount
      operationId: deleteSubaccount
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204": { description: Deleted }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/subaccounts/{id}/stats:
    get:
      tags: [Subaccounts]
      summary: Get subaccount performance statistics
      operationId: getSubaccountStats
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subaccount statistics
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SubaccountStats" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Lists & contacts (BC-01) ----------------
  /v1/lists:
    get:
      tags: [Lists]
      summary: List contact lists
      description: "Scope: `manage_templates`."
      operationId: listLists
      responses:
        "200":
          description: Lists
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/ContactList" } }
                  lists: { type: array, deprecated: true, items: { $ref: "#/components/schemas/ContactList" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Lists]
      summary: Create a contact list
      operationId: createList
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, minLength: 1, maxLength: 200 }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContactList" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /v1/lists/{list_id}:
    parameters:
      - $ref: "#/components/parameters/ListId"
    get:
      tags: [Lists]
      summary: Get a contact list by ID
      operationId: getList
      responses:
        "200":
          description: List
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContactList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Lists]
      summary: Update a contact list
      operationId: updateList
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, minLength: 1, maxLength: 200 }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContactList" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Lists]
      summary: Delete a contact list and its contacts
      operationId: deleteList
      responses:
        "204": { description: Deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/lists/{list_id}/contacts:
    parameters:
      - $ref: "#/components/parameters/ListId"
    get:
      tags: [Lists]
      summary: List contacts of a list
      operationId: listContacts
      parameters:
        - $ref: "#/components/parameters/Limit"
        - { name: offset, in: query, schema: { type: integer, minimum: 0, default: 0 } }
      responses:
        "200":
          description: Contacts page (newest first)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Contact" } }
                  contacts: { type: array, deprecated: true, items: { $ref: "#/components/schemas/Contact" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      tags: [Lists]
      summary: Add or update a contact (CNT-04)
      description: Upsert by email within the list; re-adding an unsubscribed contact re-activates it.
      operationId: upsertContact
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ContactInput" }
      responses:
        "201":
          description: Created or updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Contact" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/lists/{list_id}/contacts/batch:
    parameters:
      - $ref: "#/components/parameters/ListId"
    post:
      tags: [Lists]
      summary: Batch upsert contacts (CNT-04)
      description: Upsert up to 1000 contacts in a single transaction.
      operationId: batchUpsertContacts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [contacts]
              properties:
                contacts:
                  type: array
                  minItems: 1
                  maxItems: 1000
                  items: { $ref: "#/components/schemas/ContactInput" }
      responses:
        "200":
          description: Batch result
          content:
            application/json:
              schema:
                type: object
                properties:
                  inserted: { type: integer }
                  updated: { type: integer }
                  total: { type: integer }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/lists/{list_id}/contacts/export:
    parameters:
      - $ref: "#/components/parameters/ListId"
    get:
      tags: [Lists, Contacts]
      summary: Export contacts of a list to CSV (CNT-06)
      description: |
        Streams contacts of the specified list in CSV format.
        Supports filtering by `segment_id`.
      operationId: exportListContactsCSV
      parameters:
        - name: segment_id
          in: query
          required: false
          schema:
            type: string
          description: Optional segment ID within list to export only matching contacts
      responses:
        "200":
          description: CSV file stream
          content:
            text/csv:
              schema:
                type: string
                format: binary
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/lists/{list_id}/validate:
    parameters:
      - $ref: "#/components/parameters/ListId"
    post:
      tags: [Lists, Contacts]
      summary: Validate entire contact list before campaign launch (CNT-07)
      description: |
        Audits contacts in the list, identifying syntax errors, missing MX records,
        disposable email addresses, and role-based mailboxes.
      operationId: validateContactList
      parameters:
        - name: segment_id
          in: query
          required: false
          schema:
            type: string
          description: Optional segment ID to validate only matching contacts
      responses:
        "200":
          description: List validation audit report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListValidationReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/lists/{list_id}/contacts/{contact_id}:
    parameters:
      - $ref: "#/components/parameters/ListId"
      - { name: contact_id, in: path, required: true, schema: { type: string } }
    delete:
      tags: [Lists]
      summary: Delete a contact from a list
      operationId: deleteContact
      responses:
        "204": { description: Deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/contacts:
    delete:
      tags: [Contacts]
      summary: Delete a contact across all lists (152-ФЗ / GDPR, CNT-04)
      description: Purges all personal data for the email address in this account.
      operationId: purgeContactByEmail
      parameters:
        - { name: email, in: query, required: true, schema: { type: string, format: email } }
      responses:
        "204": { description: Purged }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/contacts/{contact_id}:
    parameters:
      - name: contact_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Contacts]
      summary: Get contact profile with lists and metrics (CNT-06)
      description: Retrieves contact details, list memberships, and lifetime interaction statistics.
      operationId: getContactProfile
      responses:
        "200":
          description: Contact profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactProfile"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Contacts]
      summary: Update contact profile (CNT-06)
      description: Updates phone, tags, custom attributes, or active status.
      operationId: updateContactProfile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactPatchInput"
      responses:
        "200":
          description: Updated contact profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactProfile"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/contacts/{contact_id}/activity:
    parameters:
      - name: contact_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Contacts]
      summary: Get contact interaction history timeline (CNT-06)
      description: Retrieves delivery, open, click, bounce, and unsubscribe events for this contact.
      operationId: getContactActivity
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      responses:
        "200":
          description: Timeline of events
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Event"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/contacts/{contact_id}/export:
    parameters:
      - name: contact_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Contacts]
      summary: Export complete personal data of a contact (152-ФЗ / GDPR Art. 20, LAW-04)
      description: Returns a portable JSON bundle containing contact identity, attributes, lists, consent audit, and compliance metadata.
      operationId: exportContactData
      responses:
        "200":
          description: Personal data export archive
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactDataExport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/contacts/{contact_id}/consent-audit:
    parameters:
      - name: contact_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Contacts]
      summary: Get legal consent proof audit trail for FAS (38-ФЗ, LAW-02)
      description: Returns verifiable records of consent collection timestamp, source, IP, and status history for regulatory compliance.
      operationId: getContactConsentAudit
      responses:
        "200":
          description: Consent audit trail for 38-FZ
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConsentAuditTrail"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/contacts/{contact_id}/sto:
    parameters:
      - name: contact_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Contacts]
      summary: Get Send Time Optimization profile for contact (CMP-08)
      description: Returns personalized optimal send hour (UTC and local), confidence score, hourly activity distribution, and recommended next send time.
      operationId: getContactSTO
      responses:
        "200":
          description: Contact STO profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactSTOProfile"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/contacts/{contact_id}/sto/recalculate:
    parameters:
      - name: contact_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Contacts]
      summary: Recalculate Send Time Optimization profile for contact (CMP-08)
      description: Triggers statistical aggregation of open and click events for the contact and updates their STO profile.
      operationId: recalculateContactSTO
      responses:
        "200":
          description: Updated STO profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactSTOProfile"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/contacts/sto/recalculate:
    post:
      tags: [Contacts]
      summary: Batch recalculate Send Time Optimization for contacts (CMP-08)
      description: Recalculates STO profiles for active contacts in the account or a specific list.
      operationId: batchRecalculateSTO
      parameters:
        - in: query
          name: list_id
          schema: { type: string }
          description: Optional filter by contact list
      responses:
        "200":
          description: Recalculation summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated_count: { type: integer }
                  duration_ms: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/contacts/validate:
    post:
      tags: [Contacts]
      summary: Validate email address(es) before sending (CNT-07)
      description: |
        Performs syntax check, disposable domain detection, role-based address detection,
        and DNS MX record verification.
      operationId: validateContacts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  description: Single email to validate
                emails:
                  type: array
                  items: { type: string }
                  description: Batch of emails to validate (up to 100)
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactValidationResponse"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/contacts/scoring/distribution:
    get:
      tags: [Contacts]
      summary: Get engagement scoring distribution across contacts (CNT-08)
      description: Returns breakdown of contacts by engagement tier (engaged, active, cooling, dormant, new) and average score.
      operationId: getEngagementDistribution
      parameters:
        - in: query
          name: list_id
          schema: { type: string }
          description: Optional filter by contact list
      responses:
        "200":
          description: Engagement distribution statistics
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EngagementDistribution" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/contacts/scoring/recalculate:
    post:
      tags: [Contacts]
      summary: Recalculate engagement scores for all contacts (CNT-08)
      description: Triggers RFM engagement score recalculation based on recency, open/click frequency, and streak.
      operationId: recalculateEngagementScores
      responses:
        "200":
          description: Recalculation summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated_count: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/contacts/sunset-policy:
    get:
      tags: [Contacts]
      summary: Get sunset policy settings (CNT-08)
      description: Returns configuration for auto-cleaning inactive/dormant contacts.
      operationId: getSunsetPolicy
      responses:
        "200":
          description: Sunset policy settings
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SunsetPolicy" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    put:
      tags: [Contacts]
      summary: Update sunset policy settings (CNT-08)
      description: Configure dormancy threshold days, minimum emails sent, and sunset action (unsubscribe or tag).
      operationId: saveSunsetPolicy
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SunsetPolicyInput" }
      responses:
        "200":
          description: Updated sunset policy
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SunsetPolicy" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/contacts/sunset-policy/preview:
    post:
      tags: [Contacts]
      summary: Preview dormant contacts matching sunset policy (CNT-08)
      description: Estimates number of contacts that will be affected by the sunset policy without modifying them.
      operationId: previewSunsetPolicy
      responses:
        "200":
          description: Preview results
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SunsetPreviewResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/contacts/sunset-policy/run:
    post:
      tags: [Contacts]
      summary: Immediately execute sunset policy (CNT-08)
      description: Applies configured sunset action (unsubscribe or tag) to all matching dormant contacts right now.
      operationId: runSunsetPolicy
      responses:
        "200":
          description: Execution summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SunsetRunResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/lists/{list_id}/import:
    parameters:
      - $ref: "#/components/parameters/ListId"
    post:
      tags: [Lists]
      summary: Stream import contacts from CSV (CNT-03)
      description: |
        Accepts CSV stream via raw body (`Content-Type: text/csv`) or `multipart/form-data`.
        Headers are automatically mapped (email, phone, tags, custom attributes).
        Streaming parser handles millions of rows with high throughput.
      operationId: importContactsCSV
      requestBody:
        required: true
        content:
          text/csv:
            schema: { type: string, format: binary }
          multipart/form-data:
            schema:
              type: object
              properties:
                file: { type: string, format: binary }
      responses:
        "200":
          description: Import summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ImportJob" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/lists/{list_id}/imports/{import_id}:
    parameters:
      - $ref: "#/components/parameters/ListId"
      - { name: import_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Lists]
      summary: Get CSV import job status and report
      operationId: getImportJob
      responses:
        "200":
          description: Import job
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ImportJob" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Segments (CNT-05) ----------------
  /v1/lists/{list_id}/segments:
    parameters:
      - $ref: "#/components/parameters/ListId"
    get:
      tags: [Segments]
      summary: List segments for a contact list (CNT-05)
      description: "Scope: `manage_templates`."
      operationId: listSegments
      responses:
        "200":
          description: Segments
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Segment" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      tags: [Segments]
      summary: Create a dynamic segment in a contact list
      description: "Scope: `manage_templates`."
      operationId: createSegment
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SegmentInput" }
      responses:
        "201":
          description: Created segment
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Segment" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/segments/{segment_id}:
    parameters:
      - $ref: "#/components/parameters/SegmentId"
    get:
      tags: [Segments]
      summary: Get segment details
      operationId: getSegment
      responses:
        "200":
          description: Segment details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Segment" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Segments]
      summary: Update a segment
      operationId: updateSegment
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SegmentInput" }
      responses:
        "200":
          description: Updated segment
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Segment" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Segments]
      summary: Delete a segment
      operationId: deleteSegment
      responses:
        "204": { description: Deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/segments/{segment_id}/count:
    parameters:
      - $ref: "#/components/parameters/SegmentId"
    get:
      tags: [Segments]
      summary: Calculate current matching contacts count (CNT-05)
      operationId: countSegmentContacts
      responses:
        "200":
          description: Matching contact count
          content:
            application/json:
              schema:
                type: object
                properties:
                  count: { type: integer, description: Number of contacts currently matching segment conditions }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/segments/{segment_id}/contacts:
    parameters:
      - $ref: "#/components/parameters/SegmentId"
    get:
      tags: [Segments]
      summary: List contacts currently matching the segment
      operationId: listSegmentContacts
      parameters:
        - $ref: "#/components/parameters/Limit"
        - { name: offset, in: query, schema: { type: integer, minimum: 0, default: 0 } }
      responses:
        "200":
          description: Contacts in segment
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Contact" } }
                  count: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/segments/{segment_id}/contacts/export:
    parameters:
      - $ref: "#/components/parameters/SegmentId"
    get:
      tags: [Segments, Contacts]
      summary: Export contacts of a segment to CSV (CNT-06)
      description: Streams contacts matching the segment in CSV format.
      operationId: exportSegmentContactsCSV
      responses:
        "200":
          description: CSV file stream
          content:
            text/csv:
              schema:
                type: string
                format: binary
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Campaigns (BC-02..BC-05) ----------------
  /v1/campaigns:
    get:
      tags: [Campaigns]
      summary: List campaigns
      description: "Scope: `manage_templates`. Newest first."
      operationId: listCampaigns
      responses:
        "200":
          description: Campaigns
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Campaign" } }
                  campaigns: { type: array, deprecated: true, items: { $ref: "#/components/schemas/Campaign" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Campaigns]
      summary: Create a campaign (draft)
      description: |
        The campaign inherits the mode of the API key that creates it:
        - `sk_test_` → `sandbox: true`. Messages skip domain verification, billing and
          suppressions and are never delivered externally (captured in the dev mail catcher).
        - `sk_live_` → `sandbox: false`. The `from_email` domain must be a verified sender
          domain of the account, otherwise `422` with `details.reason = domain_not_verified`.
      operationId: createCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CampaignInput" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { description: List not found, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
        "422": { description: Sender domain is not verified (live keys), content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
  /v1/campaigns/{campaign_id}:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    get:
      tags: [Campaigns]
      summary: Get a campaign with its progress
      operationId: getCampaign
      responses:
        "200":
          description: Campaign
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Campaigns]
      summary: Update draft or scheduled campaign
      description: "Scope: `manage_templates`. Allowed only when campaign is in `draft` or `scheduled` status."
      operationId: patchCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CampaignPatchInput" }
      responses:
        "200":
          description: Updated campaign
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: Campaign cannot be updated while running or completed, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
        "422": { description: Sender domain is not verified (live keys), content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
  /v1/campaigns/{campaign_id}/preflight:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    get:
      tags: [Campaigns]
      summary: Pre-flight checklist audit for campaign (CMP-04)
      description: |
        Scope: `manage_templates`. Validates links, unsubscribe header and link, HTML size (< 100 KB),
        images alt text, spam triggers, sender domain verification, and recipient count.
      operationId: getCampaignPreflight
      responses:
        "200":
          description: Preflight audit report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PreflightReport" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/campaigns/{campaign_id}/start:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    post:
      tags: [Campaigns]
      summary: Start (or resume) sending
      description: |
        Allowed from `draft`, `paused` and `paused_by_system`; the campaign becomes `scheduled`
        and is picked up by the campaign engine at `send_at` (or immediately). Sending resumes
        from the last saved position without duplicates.
        A test key cannot start a live campaign and vice versa (`403`, `details.reason = key_mode_mismatch`).
      operationId: startCampaign
      responses:
        "202":
          description: Scheduled
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { description: Blocked by anti-spam or key mode mismatch, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: Campaign is not in a startable state, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
        "422": { description: Sender domain is not verified (live), content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
  /v1/campaigns/{campaign_id}/pause:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    post:
      tags: [Campaigns]
      summary: Pause sending
      description: Allowed from `scheduled` and `sending`. The engine stops after the current batch (≤ 100 messages).
      operationId: pauseCampaign
      responses:
        "202":
          description: Paused
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: Campaign is not running, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
  /v1/campaigns/{campaign_id}/cancel:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    post:
      tags: [Campaigns]
      summary: Cancel the campaign
      description: Allowed from any non-final state. Final states are `completed`, `failed`, `cancelled`.
      operationId: cancelCampaign
      responses:
        "202":
          description: Cancelled
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: Campaign is already finished, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
  /v1/campaigns/{campaign_id}/test:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    post:
      tags: [Campaigns]
      summary: Send a test copy to up to 5 addresses
      description: |
        Scope: `send`. Renders the campaign with empty contact variables and sends it as a
        transactional message with the caller's key mode (test key → sandbox).
        Does not change campaign status or counters.
      operationId: testCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [emails]
              properties:
                emails: { type: array, minItems: 1, maxItems: 5, items: { type: string, format: email } }
      responses:
        "202":
          description: Accepted
          content:
            application/json:
              schema:
                type: object
                required: [sandbox, message_ids]
                properties:
                  sandbox: { type: boolean }
                  message_ids: { type: array, items: { type: string } }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "503": { $ref: "#/components/responses/Unavailable" }

  /v1/campaigns/{campaign_id}/click-map:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    get:
      tags: [Campaigns, Analytics]
      summary: Get link click map and statistics for a campaign (ANL-04)
      description: |
        Returns aggregated click statistics for each link in the campaign's email,
        including total clicks, unique recipient clicks, clean human clicks, and click share percentage.
      operationId: getCampaignClickMap
      responses:
        "200":
          description: Campaign link click map
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CampaignClickMap" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/campaigns/{campaign_id}/ab-test:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    get:
      tags: [Campaigns]
      summary: Get A/B test configuration and variant statistics (CMP-06)
      description: Returns A/B test settings, sample sizes, and performance metrics (opens, clicks, rates) for each variant.
      operationId: getCampaignABTest
      responses:
        "200":
          description: A/B test configuration and metrics
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ABTest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      tags: [Campaigns]
      summary: Configure A/B split test with variants (CMP-06)
      description: Setup split testing variants (subject, sender, content) and evaluation rules (sample percent, metric, wait hours).
      operationId: configureCampaignABTest
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ABTestInput" }
      responses:
        "200":
          description: A/B test configured successfully
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ABTest" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/campaigns/{campaign_id}/ab-test/choose-winner:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    post:
      tags: [Campaigns]
      summary: Manually choose winning variant for A/B test (CMP-06)
      description: Selects the winning variant manually, moving the campaign to sending_winner phase or completing the test.
      operationId: chooseCampaignABWinner
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ChooseWinnerInput" }
      responses:
        "200":
          description: Winner selected successfully
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ABTest" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/campaigns/{campaign_id}/resend-unopened:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    post:
      tags: [Campaigns]
      summary: Create follow-up campaign targeting unopeners (CMP-07)
      description: Automatically copies campaign settings with a new subject/sender and schedules send only to contacts who did not open the original campaign.
      operationId: resendCampaignUnopened
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ResendUnopenedInput" }
      responses:
        "201":
          description: Follow-up campaign created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/campaigns/{campaign_id}/preview-clients:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    post:
      tags: [Campaigns]
      summary: Analyze campaign HTML for email client rendering compatibility and dark mode (EDT-05)
      operationId: previewCampaignClients
      responses:
        "200":
          description: Client preview and dark mode analysis report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientPreviewReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/campaigns/{campaign_id}/analytics/geo:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    get:
      tags: [Campaigns, Analytics]
      summary: Get geographic engagement breakdown for campaign (ANL-05)
      description: Returns recipient activity (opens, clicks, unique subscribers) grouped by country and city based on IP geolocation.
      operationId: getCampaignGeoAnalytics
      responses:
        "200":
          description: Campaign geographic analytics report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignGeoReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/campaigns/{campaign_id}/analytics/devices:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    get:
      tags: [Campaigns, Analytics]
      summary: Get devices, operating systems and email clients breakdown (ANL-05)
      description: Parses User-Agent header from opened and clicked events to categorize devices (desktop/mobile/tablet), OS, and email clients.
      operationId: getCampaignDeviceAnalytics
      responses:
        "200":
          description: Campaign device analytics report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignDeviceReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/campaigns/{campaign_id}/analytics/conversions:
    parameters:
      - $ref: "#/components/parameters/CampaignId"
    get:
      tags: [Campaigns, Analytics]
      summary: Get e-commerce revenue and order attribution report (ANL-05)
      description: Calculates total revenue, orders count, average order value, conversion rate, and revenue per email for the campaign.
      operationId: getCampaignConversionsAnalytics
      responses:
        "200":
          description: Campaign conversions and revenue report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignConversionsReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/campaigns/compare:
    post:
      tags: [Campaigns, Analytics]
      summary: Compare up to 5 campaigns side-by-side (ANL-05)
      description: Returns side-by-side metrics matrix (recipients, open rate, click rate, CTOR, bounce rate, devices, revenue) with leader badges.
      operationId: compareCampaigns
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompareCampaignsInput"
      responses:
        "200":
          description: Campaigns side-by-side comparison report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignsComparisonReport"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/conversions:
    post:
      tags: [Analytics]
      summary: Record e-commerce order or conversion with attribution (ANL-05)
      description: Registers an e-commerce purchase or goal completion. Automatically attributes to recent campaigns if campaign_id is not specified.
      operationId: recordConversion
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateConversionInput"
      responses:
        "201":
          description: Conversion recorded successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversion"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    get:
      tags: [Analytics]
      summary: List recorded e-commerce conversions (ANL-05)
      description: Returns paginated conversions and orders for the account with optional campaign filtering.
      operationId: listConversions
      parameters:
        - in: query
          name: campaign_id
          schema: { type: string }
          description: Filter conversions by campaign ID
        - in: query
          name: limit
          schema: { type: integer, default: 50 }
        - in: query
          name: offset
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: Paginated conversions list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Conversion"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401": { $ref: "#/components/responses/Unauthorized" }

  # ---------------- Scheduled Reports and Analytics Exports (ANL-06) ----------------
  /v1/campaigns/{campaign_id}/export:
    get:
      tags: [Campaigns, Analytics]
      summary: Direct streaming download of campaign events or summary (CSV / XLSX) (ANL-06)
      description: Downloads campaign data directly in CSV or XLSX format.
      operationId: exportCampaignDirect
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema: { type: string }
        - name: format
          in: query
          schema: { type: string, enum: [csv, xlsx], default: csv }
        - name: type
          in: query
          schema: { type: string, enum: [campaign_events, campaigns_overview, conversions], default: campaign_events }
      responses:
        "200":
          description: Export binary stream
          content:
            text/csv: { schema: { type: string, format: binary } }
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- RSS-to-Email / Automated Digests (CMP-09) ----------------
  /v1/rss-feeds/validate:
    post:
      tags: [Campaigns, RSS]
      summary: Validate RSS/Atom feed URL and inspect entries (CMP-09)
      description: Fetches and parses an RSS 2.0 or Atom 1.0 feed URL, returning title, description, and preview entries.
      operationId: validateRSSFeed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [feed_url]
              properties:
                feed_url: { type: string, format: uri, example: "https://habr.com/ru/rss/all/" }
      responses:
        "200":
          description: Feed validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RSSFeedValidationResult"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/rss-feeds:
    get:
      tags: [Campaigns, RSS]
      summary: List RSS feed campaigns (CMP-09)
      description: Returns all configured RSS-to-email digest campaigns for the account.
      operationId: listRSSFeeds
      responses:
        "200":
          description: List of RSS feed campaigns
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/RSSFeed"
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Campaigns, RSS]
      summary: Create RSS feed digest campaign (CMP-09)
      description: Configures an automated RSS-to-email campaign with parsing schedule and digest templates.
      operationId: createRSSFeed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RSSFeedInput"
      responses:
        "201":
          description: RSS feed campaign created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RSSFeed"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "422": { $ref: "#/components/responses/Unprocessable" }

  /v1/rss-feeds/{id}:
    get:
      tags: [Campaigns, RSS]
      summary: Get RSS feed campaign details (CMP-09)
      operationId: getRSSFeed
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: RSS feed details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RSSFeed"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Campaigns, RSS]
      summary: Update RSS feed campaign settings (CMP-09)
      operationId: patchRSSFeed
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RSSFeedPatchInput"
      responses:
        "200":
          description: Updated RSS feed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RSSFeed"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Campaigns, RSS]
      summary: Delete RSS feed campaign (CMP-09)
      operationId: deleteRSSFeed
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204":
          description: RSS feed campaign deleted
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/rss-feeds/{id}/pause:
    post:
      tags: [Campaigns, RSS]
      summary: Pause RSS feed checking (CMP-09)
      operationId: pauseRSSFeed
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: RSS feed paused
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RSSFeed"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/rss-feeds/{id}/resume:
    post:
      tags: [Campaigns, RSS]
      summary: Resume RSS feed checking (CMP-09)
      operationId: resumeRSSFeed
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: RSS feed resumed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RSSFeed"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/rss-feeds/{id}/trigger:
    post:
      tags: [Campaigns, RSS]
      summary: Manually trigger RSS feed check and optional dispatch (CMP-09)
      operationId: triggerRSSFeed
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                dry_run:
                  type: boolean
                  default: false
                  description: "If true, only polls feed and previews items without creating/sending a campaign"
      responses:
        "200":
          description: Execution result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RSSFeedRunResult"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/rss-feeds/{id}/preview:
    post:
      tags: [Campaigns, RSS]
      summary: Render live HTML preview of RSS digest (CMP-09)
      operationId: previewRSSFeedDigest
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Rendered HTML and subject
          content:
            application/json:
              schema:
                type: object
                properties:
                  subject: { type: string }
                  html: { type: string }
                  text: { type: string }
                  items_count: { type: integer }
                  sample_posts:
                    type: array
                    items:
                      $ref: "#/components/schemas/RSSFeedItem"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/rss-feeds/{id}/runs:
    get:
      tags: [Campaigns, RSS]
      summary: List past execution runs of an RSS feed (CMP-09)
      operationId: listRSSFeedRuns
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of execution runs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/RSSFeedRun"
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/reports/scheduled:
    get:
      tags: [Reports]
      summary: List scheduled recurring email reports (ANL-06)
      description: Returns list of scheduled reports with frequency, recipients and next run times.
      operationId: listScheduledReports
      responses:
        "200":
          description: Scheduled reports list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ScheduledReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Reports]
      summary: Create a scheduled recurring email report (ANL-06)
      description: Creates a recurring report to be delivered to recipient emails on daily, weekly or monthly schedule.
      operationId: createScheduledReport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScheduledReportInput"
      responses:
        "201":
          description: Scheduled report created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScheduledReport"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/reports/scheduled/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Reports]
      summary: Get scheduled report details (ANL-06)
      operationId: getScheduledReport
      responses:
        "200":
          description: Scheduled report details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScheduledReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Reports]
      summary: Update scheduled report parameters (ANL-06)
      operationId: updateScheduledReport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScheduledReportInput"
      responses:
        "200":
          description: Updated report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScheduledReport"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Reports]
      summary: Delete scheduled report (ANL-06)
      operationId: deleteScheduledReport
      responses:
        "204": { description: Report deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/reports/scheduled/{id}/send-now:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Reports]
      summary: Trigger immediate execution and sending of scheduled report (ANL-06)
      operationId: sendScheduledReportNow
      responses:
        "200":
          description: Report sent successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }
                  message: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/reports/export:
    post:
      tags: [Reports, Analytics]
      summary: Request analytics export generation (CSV / XLSX) (ANL-06)
      description: Generates and stores a data export file.
      operationId: createAnalyticsExport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExportRequest"
      responses:
        "201":
          description: Export created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnalyticsExport"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/reports/exports:
    get:
      tags: [Reports, Analytics]
      summary: List generated analytics exports (ANL-06)
      operationId: listAnalyticsExports
      parameters:
        - in: query
          name: limit
          schema: { type: integer, default: 20 }
        - in: query
          name: offset
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of exports
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AnalyticsExport"
                  total: { type: integer }
                  limit: { type: integer }
                  offset: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/reports/exports/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Reports, Analytics]
      summary: Get export details (ANL-06)
      operationId: getAnalyticsExport
      responses:
        "200":
          description: Export details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnalyticsExport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/reports/exports/{id}/download:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Reports, Analytics]
      summary: Download export file content (ANL-06)
      operationId: downloadAnalyticsExport
      responses:
        "200":
          description: File download
          content:
            text/csv: { schema: { type: string, format: binary } }
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Anti-Spam Content Scoring (ABS-04, EDT-06) ----------------
  /v1/antispam/score:
    post:
      tags: [AntiSpam]
      summary: Automated email content spam scoring and threat intelligence check (ABS-04, EDT-06)
      description: |
        Scope: `send`. Analyzes email subject, HTML and text using SpamAssassin / rspamd heuristics
        (caps, punctuation, hidden text, image-to-text ratio, stop-words) and performs real-time
        DNSBL threat intelligence checks against Spamhaus DBL and SURBL for embedded links.
      operationId: scoreAntiSpamContent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AntiSpamScoreRequest"
      responses:
        "200":
          description: Content scoring and threat intelligence result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AntiSpamScoreResponse"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  # ---------------- ML Account Risk & Phishing Detection (ABS-06) ----------------
  /v1/security/risk/account:
    get:
      tags: [SecurityRisk]
      summary: Get account ML risk score and behavioral signals (ABS-06)
      description: Returns multi-factor account risk score (0-100), risk level (low/medium/high/critical), breakdown by behavioral, domain and KYC metrics, and AI explanation.
      operationId: getAccountRiskStatus
      responses:
        "200":
          description: Account risk status and score
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountRiskStatus"
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/security/risk/evaluate:
    post:
      tags: [SecurityRisk]
      summary: Pre-send ML risk & phishing evaluation of email content (ABS-06)
      description: Evaluates subject, body HTML and sender domain against lookalike brand spoofing, phishing keywords, suspicious redirects and behavioral volume spike.
      operationId: evaluateEmailRisk
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RiskEvaluationRequest"
      responses:
        "200":
          description: Evaluation result with action recommendation and AI explanation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RiskEvaluationResult"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/admin/security/risk/assessments:
    get:
      tags: [Admin]
      summary: List platform risk assessments and flagged accounts (ABS-06)
      description: Audit log of elevated risk events for ESP security operator.
      operationId: listRiskAssessments
      parameters:
        - name: level
          in: query
          schema: { type: string, enum: [low, medium, high, critical] }
      responses:
        "200":
          description: List of risk assessments
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AccountRiskAssessmentRecord"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/security/risk/override:
    post:
      tags: [Admin]
      summary: Override account risk rating by security operator (ABS-06)
      description: Allows operator to manually clear false positives or block compromised accounts.
      operationId: overrideAccountRisk
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [account_id, risk_level]
              properties:
                account_id: { type: string }
                risk_level: { type: string, enum: [low, medium, high, critical] }
                comment: { type: string }
      responses:
        "200":
          description: Risk rating overridden
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- Penetration Testing & Bug Bounty Program (SEC-05) ----------------
  /.well-known/security.txt:
    get:
      tags: [Security]
      summary: RFC 9116 security.txt disclosure policy (SEC-05)
      description: Returns RFC 9116 security contact information, PGP encryption link, policy, and Bug Bounty reward page.
      operationId: getSecurityTxt
      responses:
        "200":
          description: RFC 9116 security policy document
          content:
            text/plain:
              schema: { type: string }

  /v1/security/bug-bounty/policy:
    get:
      tags: [BugBounty]
      summary: Get Bug Bounty program policy, reward tiers, and scope (SEC-05)
      description: Public information on vulnerability disclosure rules, Safe Harbor guarantees, reward amounts in RUB, and in-scope targets.
      operationId: getBugBountyPolicy
      responses:
        "200":
          description: Bug Bounty program guidelines and reward tiers
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BugBountyPolicy"

  /v1/security/bug-bounty/reports:
    post:
      tags: [BugBounty]
      summary: Submit vulnerability report by ethical security researcher (SEC-05)
      description: Allows security researchers to responsibly report vulnerabilities for triage and bounty rewards.
      operationId: submitBugBountyReport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BugBountyReportInput"
      responses:
        "201":
          description: Vulnerability report registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BugBountyReport"
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/admin/security/bug-bounty/reports:
    get:
      tags: [Admin]
      summary: List reported vulnerabilities for security operations (SEC-05)
      description: Filter and manage bug bounty submissions by status and severity.
      operationId: listBugBountyReports
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [new, triaged, in_progress, resolved, rejected, duplicate] }
        - name: severity
          in: query
          schema: { type: string, enum: [critical, high, medium, low, info] }
      responses:
        "200":
          description: List of vulnerability reports
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/BugBountyReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/security/bug-bounty/reports/{id}/triage:
    post:
      tags: [Admin]
      summary: Triage vulnerability report and assign bounty payout (SEC-05)
      description: Updates report status, admin notes, and approves monetary reward in RUB.
      operationId: triageBugBountyReport
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BugBountyTriageInput"
      responses:
        "200":
          description: Report triaged and updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BugBountyReport"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/security/pentest/run:
    post:
      tags: [Admin]
      summary: Trigger automated pre-launch penetration test suite (SEC-05)
      description: Executes comprehensive automated attack suite covering multitenancy IDOR, CRLF injection, SSRF, SQLi and rate limits.
      operationId: runPentestSuite
      responses:
        "200":
          description: Automated pentest execution results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PentestRunSummary"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/security/pentest/runs:
    get:
      tags: [Admin]
      summary: List history of automated penetration test runs (SEC-05)
      description: Historical compliance records of pre-launch security audits.
      operationId: listPentestRuns
      responses:
        "200":
          description: Pentest audit records
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PentestRunRecord"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- Multi-DC & Disaster Recovery (OPS-07) ----------------
  /v1/admin/dr/status:
    get:
      tags: [Admin]
      summary: Get Multi-DC topology and cross-region replication status (OPS-07)
      description: Returns health, roles, endpoint URLs and WAL replication lag for all cluster regions.
      operationId: getMultiDCStatus
      responses:
        "200":
          description: Multi-DC cluster status and topology
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MultiDCClusterStatus"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/dr/switchover:
    post:
      tags: [Admin]
      summary: Perform planned graceful switchover to target data center (OPS-07)
      description: Initiates coordinated role switchover with traffic drain and zero data loss.
      operationId: performDCSwitchover
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DCSwitchoverInput"
      responses:
        "200":
          description: Switchover completed successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MultiDCClusterStatus"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/admin/dr/drills/run:
    post:
      tags: [Admin]
      summary: Execute automated Disaster Recovery simulation drill (OPS-07)
      description: Triggers multi-step DR failover test verifying fencing, promotion, replication catchup and E2E injection.
      operationId: runDRDrill
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DRDrillRunInput"
      responses:
        "200":
          description: Disaster Recovery drill results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DRDrillRun"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/dr/drills:
    get:
      tags: [Admin]
      summary: List history of Disaster Recovery drills and exercises (OPS-07)
      description: Audit trail of DR drills with measured RTO and RPO metrics.
      operationId: listDRDrills
      responses:
        "200":
          description: History of DR drills
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/DRDrillRun"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- Upstream External Relays (Unisender Go) ----------------
  /v1/admin/relays:
    get:
      tags: [Admin]
      summary: List upstream relays (Unisender Go, SMTP)
      operationId: listExternalRelays
      responses:
        "200":
          description: List of external relays
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ExternalRelay"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      tags: [Admin]
      summary: Create an upstream relay
      operationId: createExternalRelay
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExternalRelayInput"
      responses:
        "201":
          description: Relay created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExternalRelay"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/relays/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Admin]
      summary: Get upstream relay by ID
      operationId: getExternalRelay
      responses:
        "200":
          description: External relay details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExternalRelay"
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Admin]
      summary: Update upstream relay
      operationId: updateExternalRelay
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExternalRelayInput"
      responses:
        "200":
          description: Updated external relay
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExternalRelay"
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Admin]
      summary: Delete upstream relay
      operationId: deleteExternalRelay
      responses:
        "204":
          description: Deleted
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/relays/{id}/test:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Admin]
      summary: Test connectivity and credentials of upstream relay
      operationId: testExternalRelay
      responses:
        "200":
          description: Relay connection status
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/relays/rules:
    get:
      tags: [Admin]
      summary: List hybrid routing rules
      operationId: listRelayRules
      responses:
        "200":
          description: List of routing rules
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/RelayRoutingRule"
    post:
      tags: [Admin]
      summary: Upsert hybrid routing rule
      operationId: upsertRelayRule
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RelayRoutingRuleInput"
      responses:
        "200":
          description: Upserted routing rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RelayRoutingRule"

  /v1/admin/relays/rules/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    delete:
      tags: [Admin]
      summary: Delete hybrid routing rule
      operationId: deleteRelayRule
      responses:
        "204":
          description: Deleted
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/webhooks/external/unisender-go:
    get:
      tags: [Webhooks]
      summary: Verify Unisender Go webhook endpoint
      operationId: verifyUnisenderGoWebhook
      responses:
        "200":
          description: Endpoint active
    post:
      tags: [Webhooks]
      summary: Handle Unisender Go delivery/bounce/click event callbacks
      operationId: handleUnisenderGoWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Events received and processed

  # ---------------- AI Security Gateway (AI-01) ----------------
  /v1/ai/sanitize:
    post:
      tags: [AI Security]
      summary: Sanitize sensitive personal data (152-FZ PII Masking)
      description: Replaces email, phone, full name, bank cards, and secret tokens with reversible local tokens.
      operationId: sanitizeText
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AISanitizeRequest"
      responses:
        "200":
          description: Sanitized text and local token mapping
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AISanitizeResponse"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/ai/desanitize:
    post:
      tags: [AI Security]
      summary: Restore original personal data into LLM response (152-FZ De-anonymization)
      description: Restores original PII values back into LLM completion using the local token mapping.
      operationId: desanitizeText
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AIDesanitizeRequest"
      responses:
        "200":
          description: Reconstructed text with original PII restored
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AIDesanitizeResponse"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/ai/prompt/validate:
    post:
      tags: [AI Security]
      summary: Validate prompt against Prompt Injections, SQL DDL attacks, and PII leakage
      description: Evaluates safety, blocks jailbreak attempts and SQL commands (DROP, ALTER, TRUNCATE, DELETE), and returns sanitized prompt.
      operationId: validateAIPrompt
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AIPromptValidationRequest"
      responses:
        "200":
          description: Validation result with safety verdict and violations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AIPromptValidationResponse"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/admin/ai/circuit-breaker:
    get:
      tags: [Admin]
      summary: List AI downstream LLM Circuit Breakers status (AI-01)
      description: Returns health state (closed, half_open, open), error rates, and cooldown timers for OpenAI, Timeweb Cloud AI, and local models.
      operationId: listAICircuitBreakers
      responses:
        "200":
          description: List of circuit breaker states
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AICircuitBreaker"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai/circuit-breaker/reset:
    post:
      tags: [Admin]
      summary: Reset tripped AI Circuit Breaker to closed state (AI-01)
      description: Manually clears failure counters and re-enables traffic to the downstream LLM provider.
      operationId: resetAICircuitBreaker
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [service_name]
              properties:
                service_name:
                  type: string
                  example: llm_primary_openai
      responses:
        "200":
          description: Circuit breaker successfully reset
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai/audit:
    get:
      tags: [Admin]
      summary: Query AI Security Gateway audit logs (AI-01)
      description: Paginated audit trail of sanitizations, blocked prompt injections, forbidden SQL attempts, and token usages.
      operationId: listAIAuditLogs
      parameters:
        - name: status
          in: query
          required: false
          schema: { type: string }
        - name: limit
          in: query
          required: false
          schema: { type: integer, default: 50 }
        - name: offset
          in: query
          required: false
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of AI security audit log entries
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AIAuditLog"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- SRE & Postmaster Autopilot (AI-02) ----------------
  /v1/admin/autopilot/evaluate:
    post:
      tags: [Admin]
      summary: Run full SRE evaluation cycle (AI-02)
      description: Analyzes ISP metrics, calculates warmup adaptations (Formula V_{t+1}), checks pool utilization, and generates provisioning/delisting recommendations.
      operationId: evaluateSREAutopilot
      responses:
        "200":
          description: Evaluation result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SREEvaluationResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/autopilot/metrics:
    get:
      tags: [Admin]
      summary: Get recent ISP deliverability metric snapshots (AI-02)
      operationId: getAutopilotMetrics
      responses:
        "200":
          description: List of provider metric snapshots
          content:
            application/json:
              schema:
                type: object
                properties:
                  metrics:
                    type: array
                    items: { $ref: "#/components/schemas/ProviderMetricSnapshot" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/autopilot/warmup/recommendations:
    get:
      tags: [Admin]
      summary: Get Warmup Autopilot limit adaptations (AI-02)
      operationId: getWarmupRecommendations
      responses:
        "200":
          description: Warmup limit adjustments
          content:
            application/json:
              schema:
                type: object
                properties:
                  recommendations:
                    type: array
                    items: { $ref: "#/components/schemas/WarmupAdjustment" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/autopilot/warmup/apply:
    post:
      tags: [Admin]
      summary: Approve and apply a warmup adjustment (AI-02)
      operationId: applyWarmupAdjustment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [adjustment_id]
              properties:
                adjustment_id: { type: string }
      responses:
        "200":
          description: Adjustment applied
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WarmupAdjustment" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/autopilot/provision/order-ip:
    post:
      tags: [Admin]
      summary: Order new public IPv4 via Timeweb Cloud API (AI-02)
      description: Orders dedicated IP, configures PTR reverse DNS, and assigns to KumoMTA pool with safety guardrails (max 2 IP/day, 10k RUB budget).
      operationId: orderTimewebIP
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/OrderIPInput" }
      responses:
        "201":
          description: IP ordered and added to pool
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TimewebProvisionAction" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { description: "Daily limit or monthly budget exceeded" }

  /v1/admin/autopilot/provision/history:
    get:
      tags: [Admin]
      summary: List Timeweb Cloud IP provisioning actions (AI-02)
      operationId: getProvisionHistory
      responses:
        "200":
          description: History of provision actions
          content:
            application/json:
              schema:
                type: object
                properties:
                  history:
                    type: array
                    items: { $ref: "#/components/schemas/TimewebProvisionAction" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/autopilot/delistings:
    get:
      tags: [Admin]
      summary: List blacklist quarantine entries and delisting drafts (AI-02)
      operationId: getAutopilotDelistings
      responses:
        "200":
          description: List of delisting requests
          content:
            application/json:
              schema:
                type: object
                properties:
                  delistings:
                    type: array
                    items: { $ref: "#/components/schemas/DelistingRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/autopilot/delistings/{id}/submit:
    post:
      tags: [Admin]
      summary: Mark delisting request as submitted (AI-02)
      operationId: submitDelistingAppeal
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                notes: { type: string }
      responses:
        "200":
          description: Delisting status updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DelistingRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/autopilot/config:
    get:
      tags: [Admin]
      summary: Get Autopilot configuration (AI-02)
      operationId: getAutopilotConfig
      responses:
        "200":
          description: Autopilot config
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AutopilotConfig" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

    put:
      tags: [Admin]
      summary: Update Autopilot configuration (AI-02)
      operationId: updateAutopilotConfig
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AutopilotConfig" }
      responses:
        "200":
          description: Config updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AutopilotConfig" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- Automated Content Auditor & Anti-Abuse (AI-03) ----------------
  /v1/admin/auditor/audit:
    post:
      tags: [Admin]
      summary: Simulate or run semantic content audit on campaign (AI-03)
      description: Inspects subject, HTML, links, mandatory unsubscribe/address, and calculates phishing and spam risk scores.
      operationId: auditCampaignContent
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AuditorAuditRequest" }
      responses:
        "200":
          description: Campaign audit report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CampaignAuditReport" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/auditor/reports:
    get:
      tags: [Admin]
      summary: List campaign content audit reports (AI-03)
      operationId: listAuditorReports
      parameters:
        - name: account_id
          in: query
          required: false
          schema: { type: string }
        - name: verdict
          in: query
          required: false
          schema: { type: string, enum: [auto_approve, escalate_to_human, rejected] }
        - name: decision
          in: query
          required: false
          schema: { type: string, enum: [pending, approved, rejected] }
        - name: limit
          in: query
          required: false
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of audit reports
          content:
            application/json:
              schema:
                type: object
                properties:
                  reports:
                    type: array
                    items: { $ref: "#/components/schemas/CampaignAuditReport" }
                  count: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/auditor/reports/{id}:
    get:
      tags: [Admin]
      summary: Get details of a single campaign audit report (AI-03)
      operationId: getAuditorReport
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Audit report details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CampaignAuditReport" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/auditor/reports/{id}/approve:
    post:
      tags: [Admin]
      summary: Approve escalated campaign report (AI-03)
      operationId: approveAuditorReport
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AuditorDecisionRequest" }
      responses:
        "200":
          description: Report approved
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CampaignAuditReport" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/auditor/reports/{id}/reject:
    post:
      tags: [Admin]
      summary: Reject escalated campaign report (AI-03)
      operationId: rejectAuditorReport
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AuditorDecisionRequest" }
      responses:
        "200":
          description: Report rejected
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CampaignAuditReport" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/auditor/config:
    get:
      tags: [Admin]
      summary: Get auditor configuration and thresholds (AI-03)
      operationId: getAuditorConfig
      parameters:
        - name: account_id
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Auditor config
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuditorConfig" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    put:
      tags: [Admin]
      summary: Update auditor configuration and thresholds (AI-03)
      operationId: updateAuditorConfig
      parameters:
        - name: account_id
          in: query
          required: false
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AuditorConfig" }
      responses:
        "200":
          description: Auditor config updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuditorConfig" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- AI Support Agent L1/L2 (AI-04) ----------------
  /v1/admin/support/analyze:
    post:
      tags: [Admin]
      summary: Analyze support ticket, diagnose DNS, and generate response (AI-04)
      description: Automatic ticket categorization, 152-FZ PII sanitization, Prompt Guard, DNS diagnostics (SPF/DKIM/DMARC), registrar guide generation, and human escalation.
      operationId: analyzeSupportTicket
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SupportAnalyzeRequest" }
      responses:
        "200":
          description: Support ticket analysis and generated reply
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SupportTicketAnalysis" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/support/analyses:
    get:
      tags: [Admin]
      summary: List support ticket analyses (AI-04)
      operationId: listSupportAnalyses
      parameters:
        - name: account_id
          in: query
          required: false
          schema: { type: string }
        - name: category
          in: query
          required: false
          schema: { type: string }
        - name: action
          in: query
          required: false
          schema: { type: string, enum: [auto_replied, escalated_to_human, skipped] }
        - name: limit
          in: query
          required: false
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of analyses
          content:
            application/json:
              schema:
                type: object
                properties:
                  analyses:
                    type: array
                    items: { $ref: "#/components/schemas/SupportTicketAnalysis" }
                  count: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/support/analyses/{id}:
    get:
      tags: [Admin]
      summary: Get details of support ticket analysis (AI-04)
      operationId: getSupportAnalysis
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Support ticket analysis
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SupportTicketAnalysis" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/support/config:
    get:
      tags: [Admin]
      summary: Get support agent configuration (AI-04)
      operationId: getSupportConfig
      parameters:
        - name: account_id
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Support agent config
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SupportConfig" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    put:
      tags: [Admin]
      summary: Update support agent configuration (AI-04)
      operationId: updateSupportConfig
      parameters:
        - name: account_id
          in: query
          required: false
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SupportConfig" }
      responses:
        "200":
          description: Support agent config updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SupportConfig" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/support/diagnose-dns:
    post:
      tags: [Admin]
      summary: On-demand DNS diagnosis for a domain (AI-04)
      operationId: diagnoseSupportDomainDNS
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SupportDNSDiagnoseRequest" }
      responses:
        "200":
          description: DNS diagnostic results and registrar setup instructions
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SupportTicketAnalysis" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- Support Knowledge Base & Semantic Cache (AI-04 / AI-05) ----------------
  /v1/admin/support/kb:
    get:
      tags: [Support]
      summary: List knowledge base articles (RAG, AI-04)
      operationId: listKnowledgeArticles
      parameters:
        - name: query
          in: query
          schema: { type: string }
        - name: tag
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of articles
          content:
            application/json:
              schema:
                type: object
                properties:
                  articles:
                    type: array
                    items: { $ref: "#/components/schemas/KnowledgeArticle" }
                  count: { type: integer }
    post:
      tags: [Support]
      summary: Create knowledge base article
      operationId: createKnowledgeArticle
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/KnowledgeArticleInput" }
      responses:
        "201":
          description: Article created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/KnowledgeArticle" }

  /v1/admin/support/kb/{id}:
    put:
      tags: [Support]
      summary: Update knowledge base article
      operationId: updateKnowledgeArticle
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/KnowledgeArticleInput" }
      responses:
        "200":
          description: Article updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/KnowledgeArticle" }
    delete:
      tags: [Support]
      summary: Delete knowledge base article
      operationId: deleteKnowledgeArticle
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204": { description: Deleted }

  /v1/admin/support/cache:
    get:
      tags: [Support]
      summary: List semantic cache golden entries (0-Token, AI-04)
      operationId: listSemanticCacheEntries
      parameters:
        - name: query
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of cached Q&A pairs
          content:
            application/json:
              schema:
                type: object
                properties:
                  entries:
                    type: array
                    items: { $ref: "#/components/schemas/SemanticCacheEntry" }
                  count: { type: integer }
    post:
      tags: [Support]
      summary: Upsert semantic cache entry
      operationId: upsertSemanticCacheEntry
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SemanticCacheInput" }
      responses:
        "200":
          description: Entry saved
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SemanticCacheEntry" }

  /v1/admin/support/cache/{id}:
    delete:
      tags: [Support]
      summary: Delete semantic cache entry
      operationId: deleteSemanticCacheEntry
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204": { description: Deleted }

  /v1/admin/support/gaps:
    get:
      tags: [Support]
      summary: List support gap analysis recommendations
      operationId: listSupportGaps
      parameters:
        - name: status
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of gap recommendations
          content:
            application/json:
              schema:
                type: object
                properties:
                  recommendations:
                    type: array
                    items: { $ref: "#/components/schemas/GapRecommendation" }
                  count: { type: integer }

  /v1/admin/support/gaps/run:
    post:
      tags: [Support]
      summary: Trigger automated gap analysis on recent tickets
      operationId: runSupportGapAnalysis
      responses:
        "200":
          description: Analysis completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  count: { type: integer }
                  recommendations:
                    type: array
                    items: { $ref: "#/components/schemas/GapRecommendation" }

  /v1/admin/support/prompts:
    get:
      tags: [Support]
      summary: List prompt templates for support and studio
      operationId: listPromptTemplates
      responses:
        "200":
          description: List of templates
          content:
            application/json:
              schema:
                type: object
                properties:
                  templates:
                    type: array
                    items: { $ref: "#/components/schemas/PromptTemplate" }

  /v1/admin/support/prompts/{template_key}:
    put:
      tags: [Support]
      summary: Update prompt template
      operationId: updatePromptTemplate
      parameters:
        - name: template_key
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PromptTemplateInput" }
      responses:
        "200":
          description: Prompt updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PromptTemplate" }

  # ---------------- AI Operator & Service Health (AI-07) ----------------
  /v1/admin/ai-operator/vitality:
    get:
      tags: [Admin]
      summary: Get System Vitality Index and KPI breakdown (AI-07)
      operationId: getOperatorVitality
      responses:
        "200":
          description: System vitality metrics
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIOperatorVitality" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai-operator/feed:
    get:
      tags: [Admin]
      summary: List autonomous action feed from AI subagents (AI-07)
      operationId: listOperatorFeed
      parameters:
        - { name: limit, in: query, required: false, schema: { type: integer, default: 50 } }
        - { name: category, in: query, required: false, schema: { type: string } }
        - { name: severity, in: query, required: false, schema: { type: string } }
      responses:
        "200":
          description: Action feed items
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AIOperatorFeedItem" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai-operator/approvals:
    get:
      tags: [Admin]
      summary: List operations in 1-click approval queue (AI-07)
      operationId: listOperatorApprovals
      parameters:
        - { name: status, in: query, required: false, schema: { type: string, enum: [pending, approved, rejected] } }
      responses:
        "200":
          description: Approval queue items
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AIOperatorApprovalItem" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai-operator/approvals/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Admin]
      summary: Get details of an approval item (AI-07)
      operationId: getOperatorApproval
      responses:
        "200":
          description: Approval item
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIOperatorApprovalItem" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai-operator/approvals/{id}/decide:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Admin]
      summary: Approve or reject pending operation (AI-07)
      operationId: decideOperatorApproval
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIOperatorDecideRequest" }
      responses:
        "200":
          description: Decision executed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIOperatorApprovalItem" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "409": { $ref: "#/components/responses/Conflict" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai-operator/config:
    get:
      tags: [Admin]
      summary: Get AI Operator and Telegram bot configuration (AI-07)
      operationId: getOperatorConfig
      responses:
        "200":
          description: Operator configuration
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIOperatorConfig" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    put:
      tags: [Admin]
      summary: Update AI Operator and Telegram bot configuration (AI-07)
      operationId: updateOperatorConfig
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIOperatorConfig" }
      responses:
        "200":
          description: Updated configuration
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIOperatorConfig" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai-operator/test-alert:
    post:
      tags: [Admin]
      summary: Dispatch test alert to Telegram (AI-07)
      operationId: sendOperatorTestAlert
      responses:
        "200":
          description: Alert dispatched
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }
                  message: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/ai-operator/telegram/webhook:
    post:
      tags: [Admin]
      summary: Inbound webhook for Telegram Bot updates (AI-07)
      operationId: handleTelegramWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200": { description: OK }

  # ---------------- Creative Email Marketing Studio (AI-05) ----------------
  /v1/studio/brandbook:
    get:
      tags: [CreativeStudio]
      summary: Get account brandbook vault (AI-05)
      operationId: getBrandbook
      responses:
        "200":
          description: Account brandbook
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Brandbook" }
    put:
      tags: [CreativeStudio]
      summary: Update account brandbook vault (AI-05)
      operationId: updateBrandbook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/BrandbookInput" }
      responses:
        "200":
          description: Updated brandbook
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Brandbook" }

  /v1/studio/generate:
    post:
      tags: [CreativeStudio]
      summary: Generate email campaign AST and HTML from brief (AI-05)
      operationId: studioGenerateEmail
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/StudioGenerateRequest" }
      responses:
        "200":
          description: Generated campaign
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StudioGenerateResponse" }

  /v1/studio/patch:
    post:
      tags: [CreativeStudio]
      summary: Apply conversational or structured AST patch to email document (AI-05)
      operationId: studioPatchEmail
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/StudioPatchRequest" }
      responses:
        "200":
          description: Patched document and recompiled HTML
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StudioPatchResponse" }

  /v1/studio/generate-image:
    post:
      tags: [CreativeStudio]
      summary: Generate branded promotional hero banner SVG/image (AI-05)
      operationId: studioGenerateImage
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/StudioGenerateImageRequest" }
      responses:
        "200":
          description: Generated image asset
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StudioGenerateImageResponse" }

  /v1/studio/projects:
    get:
      tags: [CreativeStudio]
      summary: List saved studio projects (AI-05)
      operationId: listStudioProjects
      parameters:
        - name: limit
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of projects
          content:
            application/json:
              schema:
                type: object
                properties:
                  projects:
                    type: array
                    items: { $ref: "#/components/schemas/StudioProject" }
                  count: { type: integer }
    post:
      tags: [CreativeStudio]
      summary: Save email studio project (AI-05)
      operationId: createStudioProject
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/StudioProjectInput" }
      responses:
        "201":
          description: Project created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StudioProject" }

  /v1/studio/projects/{project_id}:
    get:
      tags: [CreativeStudio]
      summary: Get saved studio project (AI-05)
      operationId: getStudioProject
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Project details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StudioProject" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [CreativeStudio]
      summary: Update studio project (AI-05)
      operationId: updateStudioProject
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/StudioProjectInput" }
      responses:
        "200":
          description: Updated project
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StudioProject" }
    delete:
      tags: [CreativeStudio]
      summary: Delete studio project (AI-05)
      operationId: deleteStudioProject
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204": { description: Project deleted }

  /v1/studio/projects/{project_id}/export-campaign:
    post:
      tags: [CreativeStudio]
      summary: Export studio project directly to draft campaign (AI-05)
      operationId: exportStudioCampaign
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                from_email: { type: string, format: email }
                from_name: { type: string }
                list_id: { type: string }
      responses:
        "201":
          description: Draft campaign created
          content:
            application/json:
              schema:
                type: object
                properties:
                  campaign_id: { type: string }
                  name: { type: string }
                  status: { type: string }

  # ---------------- AI Email Assistant (EDT-07) ----------------
  /v1/ai/email/subject/generate:
    post:
      tags: [AIEmailAssistant]
      summary: Generate subject lines with strategy variants & predicted open rates (EDT-07)
      operationId: generateAISubjects
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIGenerateSubjectsRequest" }
      responses:
        "200":
          description: Subject line variants
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIGenerateSubjectsResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/ai/email/subject/score:
    post:
      tags: [AIEmailAssistant]
      summary: Score subject line and preview text to predict Open Rate (EDT-07)
      operationId: scoreAISubject
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIScoreSubjectRequest" }
      responses:
        "200":
          description: Score and prediction factors
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIScoreSubjectResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/ai/email/text/generate:
    post:
      tags: [AIEmailAssistant]
      summary: Generate email paragraph or call-to-action text from brief (EDT-07)
      operationId: generateAIText
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIGenerateTextRequest" }
      responses:
        "200":
          description: Generated text
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIGenerateTextResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/ai/email/text/rewrite:
    post:
      tags: [AIEmailAssistant]
      summary: Rewrite text with desired tone of voice (friendly, professional, urgent, concise) (EDT-07)
      operationId: rewriteAIText
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIRewriteTextRequest" }
      responses:
        "200":
          description: Rewritten text
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIRewriteTextResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/ai/email/template/generate:
    post:
      tags: [AIEmailAssistant]
      summary: Generate complete adaptive EmailDocument from natural language description (EDT-07)
      operationId: generateAIEmailTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIGenerateTemplateRequest" }
      responses:
        "200":
          description: Complete email document matching editor schema
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIGenerateTemplateResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/ai/email/blocks/capabilities:
    get:
      tags: [AIEmailAssistant]
      summary: Get extensible block schema registry for email editor and AI agent (EDT-07)
      operationId: getAIEmailBlockCapabilities
      responses:
        "200":
          description: Extensible block capabilities
          content:
            application/json:
              schema:
                type: object
                properties:
                  blocks:
                    type: array
                    items: { $ref: "#/components/schemas/AIBlockCapability" }

  /v1/admin/ai/prompts:
    get:
      tags: [Admin]
      summary: List all dynamic system prompt templates across AI agents (EDT-07, AI-04)
      operationId: listAIPromptTemplates
      parameters:
        - name: agent_type
          in: query
          schema: { type: string }
      responses:
        "200":
          description: List of prompt templates
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AIPromptTemplate" }

  /v1/admin/ai/prompts/{template_key}:
    parameters:
      - name: template_key
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Admin]
      summary: Get dynamic system prompt template details
      operationId: getAIPromptTemplate
      responses:
        "200":
          description: Prompt template details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIPromptTemplate" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Admin]
      summary: Update dynamic system prompt template, temperature and max tokens
      operationId: updateAIPromptTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateAIPromptTemplateInput" }
      responses:
        "200":
          description: Updated prompt template
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIPromptTemplate" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/ai/prompts/{template_key}/reset:
    parameters:
      - name: template_key
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Admin]
      summary: Reset prompt template to factory default
      operationId: resetAIPromptTemplate
      responses:
        "200":
          description: Reset prompt template
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIPromptTemplate" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/ai/cache-stats:
    get:
      tags: [Admin]
      summary: Get token savings and semantic cache hit stats (EDT-07)
      operationId: getAICacheStats
      responses:
        "200":
          description: AI token cache efficiency statistics
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AICacheStats" }

  # ---------------- KYC Verification (ABS-05) ----------------
  /v1/kyc/status:
    get:
      tags: [KYC]
      summary: Get account KYC verification status (ABS-05)
      description: Returns verification status for organization (INN), sender website, and phone number.
      operationId: getKYCStatus
      responses:
        "200":
          description: Current account KYC status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KYCStatus"
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/kyc/verify-company:
    post:
      tags: [KYC]
      summary: Verify organization by Russian INN (ABS-05)
      description: Validates 10-digit (legal entity) or 12-digit (individual entrepreneur) INN checksum and registers company details.
      operationId: verifyKYCCompany
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KYCVerifyCompanyRequest"
      responses:
        "200":
          description: Updated KYC status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KYCStatus"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/kyc/verify-website:
    post:
      tags: [KYC]
      summary: Verify sender website (ABS-05)
      description: Checks website availability, HTTP status, TLS certificate and correlation with verified sender domains.
      operationId: verifyKYCWebsite
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KYCVerifyWebsiteRequest"
      responses:
        "200":
          description: Website verification audit result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KYCWebsiteVerificationResult"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/kyc/phone/send-code:
    post:
      tags: [KYC]
      summary: Send SMS verification code to phone (ABS-05)
      description: Generates a 6-digit confirmation code and sends via SMS. Valid for 10 minutes.
      operationId: sendKYCPhoneCode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KYCSendPhoneCodeRequest"
      responses:
        "200":
          description: Code dispatched
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KYCSendPhoneCodeResponse"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/kyc/phone/verify-code:
    post:
      tags: [KYC]
      summary: Verify phone confirmation code (ABS-05)
      description: Validates submitted 6-digit SMS OTP code and marks phone as verified.
      operationId: verifyKYCPhoneCode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KYCVerifyPhoneCodeRequest"
      responses:
        "200":
          description: Phone confirmed and KYC status updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KYCStatus"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/admin/accounts/{account_id}/kyc:
    parameters:
      - name: account_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Admin]
      summary: Admin manual KYC review and status update (ABS-05, ADM-02)
      description: Requires Admin or Owner role. Manually verifies or rejects account KYC.
      operationId: adminUpdateAccountKYC
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminKYCUpdateRequest"
      responses:
        "200":
          description: Updated KYC status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KYCStatus"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- Subscription Forms (FRM-01..03) ----------------
  /v1/forms:
    get:
      tags: [Forms]
      summary: List subscription forms
      operationId: listForms
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: List of forms
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SubscriptionForm" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Forms]
      summary: Create subscription form (FRM-01)
      operationId: createForm
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SubscriptionFormInput" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SubscriptionForm" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/forms/{form_id}:
    parameters:
      - { name: form_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Forms]
      summary: Get subscription form
      operationId: getForm
      responses:
        "200":
          description: Form details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SubscriptionForm" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Forms]
      summary: Update subscription form
      operationId: updateForm
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SubscriptionFormInput" }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SubscriptionForm" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Forms]
      summary: Delete subscription form
      operationId: deleteForm
      responses:
        "204": { description: Deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/forms/{form_id}/embed:
    parameters:
      - { name: form_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Forms]
      summary: Get embed snippet for website (FRM-01)
      operationId: getFormEmbedSnippet
      responses:
        "200":
          description: HTML/JS embed codes
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FormEmbedSnippet" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/forms/{form_id}/consents:
    parameters:
      - { name: form_id, in: path, required: true, schema: { type: string } }
      - $ref: "#/components/parameters/Cursor"
      - $ref: "#/components/parameters/Limit"
    get:
      tags: [Forms]
      summary: Get consent audit trail (152-ФЗ / GDPR, FRM-02)
      operationId: getFormConsents
      responses:
        "200":
          description: List of consent records
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ConsentRecord" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/public/forms/{form_id}:
    parameters:
      - { name: form_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Subscriptions]
      summary: Public form configuration for widget/page
      security: []
      operationId: getPublicFormConfig
      responses:
        "200":
          description: Public form config
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PublicFormConfig" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/public/forms/{form_id}/submit:
    parameters:
      - { name: form_id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Subscriptions]
      summary: Public submit subscription form with honeypot & consent (FRM-01, FRM-02)
      security: []
      operationId: submitPublicForm
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PublicSubmitInput" }
      responses:
        "200":
          description: Submitted successfully
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PublicSubmitResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/public/confirm:
    get:
      tags: [Subscriptions]
      summary: Confirm Double Opt-in subscription via token (FRM-02)
      security: []
      operationId: confirmSubscription
      parameters:
        - { name: token, in: query, required: true, schema: { type: string } }
      responses:
        "200":
          description: Confirmation result (HTML page or JSON)
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "confirmed" }
                  email: { type: string }
                  confirmed_at: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/public/preferences:
    get:
      tags: [Subscriptions]
      summary: Get subscriber preference center options (FRM-03)
      security: []
      operationId: getSubscriberPreferences
      parameters:
        - { name: token, in: query, required: true, schema: { type: string } }
      responses:
        "200":
          description: Preferences data
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PreferenceCenterData" }
        "400": { $ref: "#/components/responses/ValidationError" }
    post:
      tags: [Subscriptions]
      summary: Update subscriber preferences or unsubscribe (FRM-03)
      security: []
      operationId: updateSubscriberPreferences
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdatePreferencesInput" }
      responses:
        "200":
          description: Preferences updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "updated" }
                  message: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }

  # ---------------- Automation Workflows (AUT-01..05) ----------------
  /v1/automations:
    get:
      tags: [Automations]
      summary: List automation workflows
      operationId: listAutomations
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: List of automations
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Automation" }
                  next_cursor: { type: [string, "null"] }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Automations]
      summary: Create automation workflow (AUT-01)
      operationId: createAutomation
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AutomationInput" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Automation" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/presets:
    get:
      tags: [Automations]
      summary: Get starter automation presets (Welcome, Cart, Reactivation, Birthday) (AUT-04)
      operationId: getAutomationPresets
      responses:
        "200":
          description: Preset workflows
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AutomationPreset" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/ai-architect/generate:
    post:
      tags: [Automations]
      summary: AI generation of scenario DAG from natural language prompt (AI-06)
      operationId: generateAIWorkflow
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIWorkflowGenerateRequest" }
      responses:
        "200":
          description: Generated workflow blueprint
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIWorkflowArchitectureResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/ai-architect/refine:
    post:
      tags: [Automations]
      summary: Conversational refinement of workflow DAG (AI-06)
      operationId: refineAIWorkflow
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIWorkflowRefineRequest" }
      responses:
        "200":
          description: Refined workflow blueprint
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIWorkflowArchitectureResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/ai-architect/validate:
    post:
      tags: [Automations]
      summary: Validate workflow DAG for cycles, delays and orphan steps (AI-06)
      operationId: validateAIWorkflow
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AutomationInput" }
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIWorkflowValidationResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/ai-architect/activate:
    post:
      tags: [Automations]
      summary: Convert AI blueprint into live engine automation (AI-06)
      operationId: activateAIWorkflow
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AIWorkflowActivateRequest" }
      responses:
        "201":
          description: Automation created and activated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIWorkflowActivateResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/ai-architect/history:
    get:
      tags: [Automations]
      summary: List saved AI workflow blueprints (AI-06)
      operationId: listAIWorkflowHistory
      parameters:
        - { name: limit, in: query, required: false, schema: { type: integer, default: 20 } }
      responses:
        "200":
          description: List of blueprints
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AIWorkflowHistoryEntry" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/ai-architect/history/{history_id}:
    parameters:
      - { name: history_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Automations]
      summary: Get AI workflow blueprint by ID (AI-06)
      operationId: getAIWorkflowHistoryEntry
      responses:
        "200":
          description: Blueprint details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AIWorkflowHistoryEntry" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/{automation_id}:
    parameters:
      - { name: automation_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Automations]
      summary: Get automation workflow details
      operationId: getAutomation
      responses:
        "200":
          description: Automation details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Automation" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Automations]
      summary: Update automation workflow (AUT-01)
      operationId: updateAutomation
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AutomationInput" }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Automation" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Automations]
      summary: Delete automation workflow
      operationId: deleteAutomation
      responses:
        "204": { description: Deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/automations/{automation_id}/start:
    parameters:
      - { name: automation_id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Automations]
      summary: Activate automation workflow
      operationId: startAutomation
      responses:
        "200":
          description: Started
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Automation" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/automations/{automation_id}/pause:
    parameters:
      - { name: automation_id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Automations]
      summary: Pause automation workflow
      operationId: pauseAutomation
      responses:
        "200":
          description: Paused
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Automation" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/automations/{automation_id}/stats:
    parameters:
      - { name: automation_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Automations]
      summary: Get per-step execution analytics (AUT-05)
      operationId: getAutomationStats
      responses:
        "200":
          description: Automation step stats
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AutomationStatsResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/automations/{automation_id}/runs:
    parameters:
      - { name: automation_id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Automations]
      summary: Manually enroll contact into workflow
      operationId: runAutomation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [contact_id]
              properties:
                contact_id: { type: string }
                context: { type: object, additionalProperties: true }
      responses:
        "200":
          description: Contact enrolled
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "enrolled" }
                  automation_id: { type: string }
                  contact_id: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/automations/runs/{run_id}/history:
    parameters:
      - { name: run_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Automations]
      summary: Get audit transition log of workflow run
      operationId: getAutomationRunHistory
      responses:
        "200":
          description: Transition history
          content:
            application/json:
              schema:
                type: object
                properties:
                  run_id: { type: string }
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        from_step_id: { type: string, nullable: true }
                        to_step_id: { type: string, nullable: true }
                        from_status: { type: string }
                        to_status: { type: string }
                        reason: { type: string }
                        created_at: { type: string, format: date-time }
  /v1/automations/{automation_id}/runs:
    parameters:
      - { name: automation_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Automations]
      summary: List recent runs of automation workflow
      operationId: listAutomationRuns
      parameters:
        - { name: limit, in: query, required: false, schema: { type: integer, default: 50 } }
      responses:
        "200":
          description: List of workflow runs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AutomationRunSummary" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/automations/runs/{run_id}/trace:
    parameters:
      - { name: run_id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Automations]
      summary: Get visual step trajectory for Run Path Highlighter on 2D canvas
      operationId: getAutomationRunTrace
      responses:
        "200":
          description: Visual run trace
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AutomationRunTraceResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/automations/{automation_id}/simulate:
    parameters:
      - { name: automation_id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Automations]
      summary: Execute in-memory Dry-Run simulation of workflow without sending real messages
      operationId: simulateAutomation
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AutomationSimulationInput" }
      responses:
        "200":
          description: Simulation trace and execution results
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AutomationSimulationResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/deliverability/reputation:
    get:
      tags: [Deliverability]
      summary: Account reputation breakdown by provider and proactive warnings (DLV-01)
      operationId: getDeliverabilityReputation
      parameters:
        - name: period
          in: query
          required: false
          schema:
            type: string
            enum: [24h, 7d, 30d, 90d]
            default: 7d
      responses:
        "200":
          description: Reputation score, provider metrics, and warnings
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverabilityReputationResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/blacklists:
    get:
      tags: [Deliverability]
      summary: DNSBL blacklist monitoring status for account domains and IPs (DLV-02)
      operationId: getBlacklistsOverview
      responses:
        "200":
          description: Monitored domains and IPs with their RBL listings
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BlacklistsOverviewResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/blacklists/check:
    post:
      tags: [Deliverability]
      summary: Trigger on-demand DNSBL blacklist recheck (DLV-02)
      operationId: triggerBlacklistCheck
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/BlacklistCheckRequest" }
      responses:
        "200":
          description: Blacklist check results
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BlacklistCheckResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/tls-check:
    post:
      tags: [Deliverability]
      summary: Test MTA-STS policy, TLS-RPT and probe MX STARTTLS capabilities (MTA-08)
      operationId: checkTLSAndSTS
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TLSCheckRequest" }
      responses:
        "200":
          description: TLS and MTA-STS inspection report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TLSCheckResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/fcrdns-check:
    post:
      tags: [Deliverability]
      summary: Verify Forward-Confirmed reverse DNS (FCrDNS) and PTR alignment for an IP (MTA-09)
      operationId: checkFCrDNS
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FCrDNSCheckRequest" }
      responses:
        "200":
          description: FCrDNS verification report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FCrDNSResult" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/inbox-tests:
    get:
      tags: [Deliverability]
      summary: List inbox placement tests (DLV-03)
      description: Returns list of historical seed placement tests for the account with folder placement rates.
      operationId: listInboxPlacementTests
      responses:
        "200":
          description: List of inbox placement tests
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/InboxPlacementTest" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

    post:
      tags: [Deliverability]
      summary: Run a new inbox placement seed test (DLV-03)
      description: Dispatches test emails to seed mailboxes across Gmail, Yandex, Mail.ru, Outlook, Rambler and inspects inbox/spam/promotions folders.
      operationId: createInboxPlacementTest
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InboxTestInput" }
      responses:
        "201":
          description: Test created and executed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/InboxTestReport" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/inbox-tests/{id}:
    get:
      tags: [Deliverability]
      summary: Get detailed inbox placement test report (DLV-03)
      description: Returns placement rates, mailbox level folder results (inbox/promotions/spam/missing), SPF/DKIM/DMARC statuses, and diagnostic recommendations.
      operationId: getInboxPlacementTest
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: Inbox placement test ID
      responses:
        "200":
          description: Detailed test report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/InboxTestReport" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/inbox-tests/{id}/check:
    post:
      tags: [Deliverability]
      summary: Re-check inbox placement test mailboxes (DLV-03)
      description: Triggers a fresh polling / inspection pass across seed mailboxes for this test.
      operationId: checkInboxPlacementTest
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: Inbox placement test ID
      responses:
        "200":
          description: Updated test report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/InboxTestReport" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/seed-mailboxes:
    get:
      tags: [Deliverability]
      summary: List active seed mailboxes for inbox testing (DLV-03)
      description: Returns list of available system and account-custom seed mailboxes.
      operationId: listSeedMailboxes
      responses:
        "200":
          description: List of seed mailboxes
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SeedMailbox" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

    post:
      tags: [Deliverability]
      summary: Add custom seed mailbox (DLV-03)
      operationId: createSeedMailbox
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SeedMailboxInput" }
      responses:
        "201":
          description: Seed mailbox added
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SeedMailbox" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/seed-mailboxes/{id}:
    delete:
      tags: [Deliverability]
      summary: Delete custom seed mailbox (DLV-03)
      operationId: deleteSeedMailbox
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: Seed mailbox ID
      responses:
        "200":
          description: Mailbox removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted: { type: boolean, example: true }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/deliverability/postmasters:
    get:
      tags: [Deliverability]
      summary: List ISP Postmaster Tools integrations (DLV-04)
      description: Returns list of connected Postmaster accounts for Gmail, Yandex, and Mail.ru with verification tokens.
      operationId: listPostmasters
      responses:
        "200":
          description: List of postmaster integrations
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PostmasterIntegration" }
        "401": { $ref: "#/components/responses/Unauthorized" }

    post:
      tags: [Deliverability]
      summary: Connect or configure Postmaster integration (DLV-04)
      description: Connects Gmail Postmaster Tools, Yandex Postmaster, or Mail.ru Postmaster with verification credentials.
      operationId: connectPostmaster
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PostmasterIntegrationInput" }
      responses:
        "201":
          description: Postmaster integration configured
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PostmasterIntegration" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/postmasters/{id}:
    delete:
      tags: [Deliverability]
      summary: Disconnect Postmaster integration (DLV-04)
      operationId: deletePostmaster
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: Integration ID
      responses:
        "200":
          description: Integration disconnected
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted: { type: boolean, example: true }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/deliverability/postmasters/{id}/sync:
    post:
      tags: [Deliverability]
      summary: Trigger immediate Postmaster sync (DLV-04)
      description: Fetches the latest daily metrics and reputation trends from ISP Postmaster API.
      operationId: syncPostmaster
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: Integration ID
      responses:
        "200":
          description: Synchronization result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PostmasterSyncResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/deliverability/postmasters/import-csv:
    post:
      tags: [Deliverability]
      summary: Import Postmaster CSV data (DLV-04)
      description: Uploads and parses CSV export files from Google Postmaster Tools, Yandex, or Mail.ru.
      operationId: importPostmasterCSV
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PostmasterCSVImportRequest" }
      responses:
        "200":
          description: CSV import result
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "imported" }
                  imported_rows: { type: integer, example: 14 }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/postmasters/metrics:
    get:
      tags: [Deliverability]
      summary: Get Postmaster daily metrics history (DLV-04)
      operationId: getPostmasterMetrics
      parameters:
        - in: query
          name: domain
          schema: { type: string }
          description: Filter by domain
        - in: query
          name: provider
          schema: { type: string, enum: [gmail, yandex, mailru] }
          description: Filter by ISP postmaster provider
        - in: query
          name: days
          schema: { type: integer, default: 14 }
          description: Number of historical days
        - in: query
          name: from_date
          schema: { type: string, format: date }
          description: Start date (YYYY-MM-DD)
        - in: query
          name: to_date
          schema: { type: string, format: date }
          description: End date (YYYY-MM-DD)
      responses:
        "200":
          description: Daily metrics series
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PostmasterDailyMetric" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/postmasters/summary:
    get:
      tags: [Deliverability]
      summary: Get unified Postmaster health & compliance summary (DLV-04)
      description: Aggregated cross-provider reputation report and Google/Yahoo 2024 compliance status.
      operationId: getPostmasterSummary
      parameters:
        - in: query
          name: domain
          schema: { type: string }
          description: Sender domain
      responses:
        "200":
          description: Unified Postmaster summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PostmasterSummary" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/dane-check:
    post:
      tags: [Deliverability]
      summary: Check DANE TLSA records, DNSSEC status, and certificate binding (MTA-14)
      description: Queries TLSA records (_25._tcp.<mx>) and probes MX STARTTLS certificate to verify DANE alignment (RFC 6698 / RFC 7672).
      operationId: checkDANE
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DANECheckRequest" }
      responses:
        "200":
          description: DANE inspection report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DANECheckResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/tls-rpt/report:
    post:
      tags: [Deliverability]
      summary: Ingest RFC 8460 SMTP TLS-RPT JSON/gzip report (MTA-14)
      description: Endpoint for external reporting MTAs (Google, Yahoo, Mail.ru) sending TLS-RPT reports for monitored domains.
      operationId: ingestTLSRPTReport
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
          application/tlsrpt+json:
            schema: { type: object }
          application/tlsrpt+gzip:
            schema: { type: string, format: binary }
      responses:
        "200":
          description: Report accepted and processed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TLSRPTIngestResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/deliverability/tls-rpt/reports:
    get:
      tags: [Deliverability]
      summary: List received RFC 8460 TLS-RPT reports (MTA-14)
      description: Returns paginated list of TLS-RPT reports for sender domains.
      operationId: listTLSRPTReports
      parameters:
        - in: query
          name: domain
          schema: { type: string }
          description: Filter by policy domain
        - in: query
          name: organization
          schema: { type: string }
          description: Filter by reporting organization
        - in: query
          name: policy_type
          schema: { type: string, enum: [sts, tlsa, no-policy-found] }
        - in: query
          name: limit
          schema: { type: integer, default: 20 }
        - in: query
          name: offset
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of TLS-RPT reports
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TLSRPTReport" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/tls-rpt/reports/{id}:
    get:
      tags: [Deliverability]
      summary: Get TLS-RPT report details with failure breakdown (MTA-14)
      operationId: getTLSRPTReport
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: TLS-RPT report detail
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TLSRPTReportDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/deliverability/tls-rpt/summary:
    get:
      tags: [Deliverability]
      summary: Get aggregated TLS-RPT health summary (MTA-14)
      description: Aggregates TLS session success/failure counts, failure breakdown by type, and time-series history.
      operationId: getTLSRPTSummary
      parameters:
        - in: query
          name: domain
          schema: { type: string }
          description: Optional domain filter
        - in: query
          name: period
          schema: { type: string, default: "14d" }
          description: Time period (7d, 14d, 30d, 90d)
      responses:
        "200":
          description: Aggregated TLS-RPT summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TLSRPTOverallSummary" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/tls-rpt/sample:
    post:
      tags: [Deliverability]
      summary: Simulate/generate a sample RFC 8460 TLS-RPT report (MTA-14)
      description: Test helper to verify TLS-RPT ingestion pipeline and generate realistic sample data.
      operationId: generateSampleTLSRPT
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TLSRPTSampleRequest" }
      responses:
        "201":
          description: Sample report generated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TLSRPTIngestResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }


  /v1/deliverability/ai-recommendations:
    get:
      tags: [Deliverability]
      summary: List AI deliverability recommendations (DLV-05)
      description: Returns active, applied, or dismissed deliverability recommendations generated by the AI Copilot.
      operationId: listDeliverabilityAIRecommendations
      parameters:
        - name: status
          in: query
          description: Filter by status (active, applied, dismissed, all)
          schema: { type: string, enum: [active, applied, dismissed, all], default: active }
        - name: category
          in: query
          description: Filter by category (isp_reputation, authentication, list_hygiene, content_triggers, sending_schedule, warmup)
          schema: { type: string }
        - name: severity
          in: query
          description: Filter by severity (critical, warning, opportunity, info)
          schema: { type: string, enum: [critical, warning, opportunity, info] }
      responses:
        "200":
          description: Recommendations list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverabilityAIRecommendationList" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/ai-recommendations/generate:
    post:
      tags: [Deliverability]
      summary: Trigger fresh AI deliverability evaluation (DLV-05)
      description: Analyzes live account metrics (ISP delivery rates, spam complaints, SPF/DKIM/DMARC status, blacklists, contact engagement) and synthesizes fresh actionable recommendations.
      operationId: generateDeliverabilityAIRecommendations
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DeliverabilityAIGenerateRequest" }
      responses:
        "200":
          description: Newly evaluated recommendations and updated health score
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverabilityAISummary" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/ai-recommendations/summary:
    get:
      tags: [Deliverability]
      summary: Get AI deliverability summary and health score (DLV-05)
      description: Returns overall deliverability health score (0-100), critical issue counts, and potential inbox lift percentage.
      operationId: getDeliverabilityAISummary
      responses:
        "200":
          description: Deliverability AI health summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverabilityAISummary" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/ai-recommendations/{id}/apply:
    post:
      tags: [Deliverability]
      summary: Mark or execute recommendation as applied (DLV-05)
      description: Marks recommendation as applied and executes automated 1-click action if applicable.
      operationId: applyDeliverabilityAIRecommendation
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Recommendation successfully applied
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverabilityAIApplyResponse" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/deliverability/ai-recommendations/{id}/dismiss:
    post:
      tags: [Deliverability]
      summary: Dismiss recommendation (DLV-05)
      description: Hides recommendation from active view.
      operationId: dismissDeliverabilityAIRecommendation
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Recommendation dismissed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "dismissed" }
                  id: { type: string }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }


  /v1/chat/channels:
    get:
      tags: [Chat Channels]
      summary: List chat and messenger channels (CHN-03)
      description: Returns external chat channels (MaxChat, LiveChat, Webhook, VK, WebPush).
      operationId: listChatChannels
      responses:
        "200":
          description: Channels list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChatChannelList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Chat Channels]
      summary: Create chat channel (CHN-03)
      operationId: createChatChannel
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ChatChannelInput" }
      responses:
        "201":
          description: Channel created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChatChannel" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/chat/channels/{id}:
    get:
      tags: [Chat Channels]
      summary: Get chat channel (CHN-03)
      operationId: getChatChannel
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Channel details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChatChannel" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Chat Channels]
      summary: Update chat channel (CHN-03)
      operationId: updateChatChannel
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ChatChannelPatchInput" }
      responses:
        "200":
          description: Channel updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChatChannel" }
    delete:
      tags: [Chat Channels]
      summary: Delete chat channel (CHN-03)
      operationId: deleteChatChannel
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204": { description: Channel deleted }

  /v1/chat/channels/{id}/test:
    post:
      tags: [Chat Channels]
      summary: Send test message through chat channel (CHN-03)
      operationId: testChatChannel
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [recipient_id, text]
              properties:
                recipient_id: { type: string, example: "chat_user_123" }
                text: { type: string, example: "Привет! Это тестовое сообщение из канала MaxChat." }
      responses:
        "200":
          description: Message dispatched
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "sent" }
                  message_id: { type: string }
                  recipient_id: { type: string }

  /v1/cascade/webhooks/max:
    post:
      tags: [Cascades]
      summary: Webhook delivery and read receipt callback for MAX Messenger (CHN-03)
      description: Receives delivery and read status callbacks from MAX Messenger to advance or complete cascades.
      operationId: receiveMaxDeliveryReceipt
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message_id, event]
              properties:
                message_id: { type: string, example: "max_msg_987654321" }
                event: { type: string, enum: [message_delivered, message_read, failed], example: "message_read" }
                dispatch_id: { type: string }
                timestamp: { type: string }
      responses:
        "200":
          description: Status acknowledged
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean, example: true }
                  status: { type: string, example: "acknowledged" }

  /v1/cascades:
    get:
      tags: [Cascades]
      summary: List cascade campaigns (CHN-04)
      description: Returns multi-channel fallback cascade campaigns (Chat -> Telegram -> SMS -> Email).
      operationId: listCascadeCampaigns
      responses:
        "200":
          description: Cascade campaigns list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CascadeCampaignList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Cascades]
      summary: Create cascade campaign (CHN-04)
      operationId: createCascadeCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CascadeCampaignInput" }
      responses:
        "201":
          description: Cascade campaign created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CascadeCampaign" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/cascades/{id}:
    get:
      tags: [Cascades]
      summary: Get cascade campaign (CHN-04)
      operationId: getCascadeCampaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cascade campaign details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CascadeCampaign" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Cascades]
      summary: Update cascade campaign (CHN-04)
      operationId: updateCascadeCampaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CascadeCampaignInput" }
      responses:
        "200":
          description: Cascade campaign updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CascadeCampaign" }
    delete:
      tags: [Cascades]
      summary: Delete cascade campaign (CHN-04)
      operationId: deleteCascadeCampaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204": { description: Cascade campaign deleted }

  /v1/cascades/{id}/start:
    post:
      tags: [Cascades]
      summary: Start cascade campaign (CHN-04)
      description: Initiates step 1 (Chat/Push) for all recipients in the target list/segment.
      operationId: startCascadeCampaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "202":
          description: Cascade campaign started
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CascadeCampaign" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /v1/cascades/{id}/pause:
    post:
      tags: [Cascades]
      summary: Pause cascade campaign (CHN-04)
      operationId: pauseCascadeCampaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cascade campaign paused
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CascadeCampaign" }

  /v1/cascades/{id}/stats:
    get:
      tags: [Cascades]
      summary: Get cascade campaign statistics (CHN-04)
      operationId: getCascadeStats
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cascade statistics
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CascadeStatsResponse" }

  # ---------------- Secure Egress Gateways (EGR-01) ----------------
  /v1/admin/egress/gateways:
    get:
      tags: [Admin, Egress]
      summary: List egress gateways for bypassing foreign platform blocks
      description: Returns all configured outbound egress proxies (Cloudflare Workers, HTTPS/SOCKS5, VLESS Reality).
      operationId: listEgressGateways
      responses:
        "200":
          description: List of egress gateways
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/EgressGateway" }
    post:
      tags: [Admin, Egress]
      summary: Create new egress gateway
      operationId: createEgressGateway
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateEgressGatewayInput" }
      responses:
        "201":
          description: Gateway created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/EgressGateway" }

  /v1/admin/egress/gateways/{id}:
    delete:
      tags: [Admin, Egress]
      summary: Delete egress gateway
      operationId: deleteEgressGateway
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Gateway deleted

  /v1/admin/egress/gateways/{id}/check:
    post:
      tags: [Admin, Egress]
      summary: Check health and latency of egress gateway immediately
      operationId: checkEgressGateway
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Diagnostic result
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/EgressCheckResult" }

  /v1/admin/egress/check-custom:
    post:
      tags: [Admin, Egress]
      summary: Test custom egress proxy endpoint before saving
      operationId: checkCustomEgress
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateEgressGatewayInput" }
      responses:
        "200":
          description: Test result
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/EgressCheckResult" }

  # ---------------- Omnichannel Shared Inbox (INBOX-01) ----------------
  /v1/inbox/settings:
    get:
      tags: [Inbox]
      summary: Get shared inbox relay configuration
      operationId: getInboxSettings
      responses:
        "200":
          description: Inbox settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/InboxSettings" }
    put:
      tags: [Inbox]
      summary: Update shared inbox relay configuration
      operationId: updateInboxSettings
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InboxSettings" }
      responses:
        "200":
          description: Updated settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/InboxSettings" }

  /v1/inbox/threads:
    get:
      tags: [Inbox]
      summary: List omnichannel customer threads
      operationId: listInboxThreads
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [all, open, assigned, closed, snoozed] }
        - name: limit
          in: query
          schema: { type: integer, default: 30 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: Threads list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/InboxThread" }
                  total: { type: integer }

  /v1/inbox/threads/{id}:
    get:
      tags: [Inbox]
      summary: Get thread details and message history
      operationId: getInboxThread
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Thread and message list
          content:
            application/json:
              schema:
                type: object
                properties:
                  thread: { $ref: "#/components/schemas/InboxThread" }
                  messages:
                    type: array
                    items: { $ref: "#/components/schemas/InboxMessage" }

  /v1/inbox/threads/{id}/messages:
    post:
      tags: [Inbox]
      summary: Send operator reply to customer on original channel
      operationId: replyInboxThread
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                operator_name: { type: string }
                text: { type: string }
      responses:
        "200":
          description: Reply sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/InboxMessage" }

  /v1/inbox/threads/{id}/close:
    post:
      tags: [Inbox]
      summary: Close thread / mark resolved
      operationId: closeInboxThread
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                operator_name: { type: string }
      responses:
        "200":
          description: Thread closed

  /v1/inbox/inbound:
    post:
      tags: [Inbox]
      summary: Ingest inbound message from external channel
      operationId: inboundInboxMessage
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InboundInboxPayload" }
      responses:
        "201":
          description: Message accepted

  /v1/inbox/simulate:
    post:
      tags: [Inbox]
      summary: Simulate inbound message for testing relay into Telegram/MAX
      operationId: simulateInboxMessage
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InboundInboxPayload" }
      responses:
        "200":
          description: Simulation successful

  # ---------------- Multi-DC Delivery Sites & Failover (MTA-13) ----------------
  /v1/mta/sites:
    get:
      tags: [MTA]
      summary: List all multi-DC delivery sites with live health and circuit breaker status (MTA-13)
      description: Returns registered KumoMTA delivery clusters/sites across multiple data centers with their status, latency, and circuit state.
      operationId: listDeliverySites
      responses:
        "200":
          description: Delivery sites list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/DeliverySite" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

    post:
      tags: [MTA]
      summary: Register a new delivery site (MTA-13)
      description: Adds a new KumoMTA cluster/edge delivery site into the routing pool.
      operationId: createDeliverySite
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DeliverySiteInput" }
      responses:
        "201":
          description: Delivery site created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverySite" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/mta/sites/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string }
        description: Delivery site ID or code
    get:
      tags: [MTA]
      summary: Get delivery site details (MTA-13)
      operationId: getDeliverySite
      responses:
        "200":
          description: Delivery site details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverySite" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

    patch:
      tags: [MTA]
      summary: Update delivery site configuration (MTA-13)
      description: Update site weight, priority, role, status or endpoint.
      operationId: updateDeliverySite
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DeliverySiteUpdateInput" }
      responses:
        "200":
          description: Updated delivery site
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeliverySite" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

    delete:
      tags: [MTA]
      summary: Delete/deregister a delivery site (MTA-13)
      operationId: deleteDeliverySite
      responses:
        "200":
          description: Delivery site deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted: { type: boolean, example: true }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/mta/sites/{id}/health-check:
    post:
      tags: [MTA]
      summary: Trigger on-demand health check probe for delivery site (MTA-13)
      operationId: probeDeliverySiteHealth
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: Delivery site ID or code
      responses:
        "200":
          description: Health check probe result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SiteHealthReport" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/mta/sites/failover:
    post:
      tags: [MTA]
      summary: Trigger manual failover or traffic evacuation between delivery sites (MTA-13)
      description: Diverts sending traffic from source site to target site, with optional site draining.
      operationId: triggerSiteFailover
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FailoverRequest" }
      responses:
        "200":
          description: Failover executed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FailoverEvent" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/mta/failover-events:
    get:
      tags: [MTA]
      summary: List historical failover and switchover events (MTA-13)
      operationId: listFailoverEvents
      parameters:
        - in: query
          name: limit
          schema: { type: integer, default: 20 }
        - in: query
          name: site
          schema: { type: string }
          description: Filter by site ID or code
      responses:
        "200":
          description: List of failover incidents
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/FailoverEvent" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  # ---------------- Auth & Users (ACC-03, ACC-05) ----------------
  /v1/auth/register:
    post:
      tags: [Auth]
      summary: Register a new account and owner user (ACC-03)
      operationId: registerUser
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegisterRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuthResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/auth/login:
    post:
      tags: [Auth]
      summary: Authenticate user with email and password (ACC-03)
      operationId: loginUser
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/LoginRequest" }
      responses:
        "200":
          description: Successful login
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuthResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/auth/me:
    get:
      tags: [Auth]
      summary: Get current authenticated user profile and account details (ACC-03)
      operationId: getCurrentUser
      responses:
        "200":
          description: Current user profile
          content:
            application/json:
              schema: { $ref: "#/components/schemas/UserMeResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/auth/logout:
    post:
      tags: [Auth]
      summary: Log out and revoke current session token (ACC-03)
      operationId: logoutUser
      responses:
        "200":
          description: Logged out
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/auth/forgot-password:
    post:
      tags: [Auth]
      summary: Request password reset link (ACC-03)
      operationId: forgotPassword
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ForgotPasswordRequest" }
      responses:
        "200":
          description: Password reset link initiated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
                  message: { type: string }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/auth/reset-password:
    post:
      tags: [Auth]
      summary: Reset password with token (ACC-03)
      operationId: resetPassword
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ResetPasswordRequest" }
      responses:
        "200":
          description: Password updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/auth/verify-email:
    post:
      tags: [Auth]
      summary: Confirm email address with token (ACC-03)
      operationId: verifyEmail
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/VerifyEmailRequest" }
      responses:
        "200":
          description: Email confirmed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/auth/sessions:
    get:
      tags: [Auth]
      summary: List all active sessions for current user (ACC-05)
      operationId: listUserSessions
      responses:
        "200":
          description: Active sessions
          content:
            application/json:
              schema: { $ref: "#/components/schemas/UserSessionsResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    delete:
      tags: [Auth]
      summary: Revoke all other active sessions except current (ACC-05)
      operationId: revokeAllOtherSessions
      responses:
        "200":
          description: Other sessions revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
                  revoked_count: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/auth/sessions/{session_id}:
    parameters:
      - { name: session_id, in: path, required: true, schema: { type: string } }
    delete:
      tags: [Auth]
      summary: Revoke a specific session (ACC-05)
      operationId: revokeSession
      responses:
        "200":
          description: Session revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Enterprise SSO (SAML 2.0 / OIDC) (ACC-09) ----------------
  /v1/auth/sso/discover:
    post:
      tags: [Auth]
      summary: Auto-discover SSO configuration by email address (ACC-09)
      description: Returns whether domain has Single Sign-On configured and required protocol / login URL.
      operationId: discoverSSO
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SSODiscoverRequest" }
      responses:
        "200":
          description: Discovery result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SSODiscoverResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/auth/sso/login:
    get:
      tags: [Auth]
      summary: Initiate SSO authentication flow (ACC-09)
      description: Generates AuthnRequest (SAML 2.0) or Authorization URL (OIDC) and redirects user to company IdP.
      operationId: initSSOLogin
      security: []
      parameters:
        - name: domain
          in: query
          schema: { type: string }
          description: Corporate email domain
        - name: email
          in: query
          schema: { type: string }
          description: User corporate email
        - name: return_url
          in: query
          schema: { type: string }
          description: Redirect URL after successful authentication
      responses:
        "302":
          description: Redirect to Identity Provider
        "200":
          description: Returns redirect_url if Accept header includes application/json
          content:
            application/json:
              schema:
                type: object
                properties:
                  redirect_url: { type: string, format: uri }
        "400": { $ref: "#/components/responses/ValidationError" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/auth/sso/saml/acs:
    post:
      tags: [Auth]
      summary: SAML 2.0 Assertion Consumer Service (ACS) endpoint (ACC-09)
      description: Receives POSTed SAMLResponse from IdP, validates X.509 signature and assertions, provisions user via JIT, and creates session.
      operationId: processSAMLACS
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [SAMLResponse]
              properties:
                SAMLResponse: { type: string }
                RelayState: { type: string }
      responses:
        "302":
          description: Redirects back to dashboard with session token
        "200":
          description: Session created (when application/json requested)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuthResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/auth/sso/oidc/callback:
    get:
      tags: [Auth]
      summary: OpenID Connect (OIDC) callback endpoint (ACC-09)
      description: Handles authorization code redirect from OIDC provider, exchanges code for tokens, retrieves userinfo, and creates session.
      operationId: processOIDCCallback
      security: []
      parameters:
        - name: code
          in: query
          required: true
          schema: { type: string }
        - name: state
          in: query
          schema: { type: string }
      responses:
        "302":
          description: Redirects back to dashboard with session token
        "200":
          description: Session created (when application/json requested)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuthResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/sso/configs:
    get:
      tags: [Auth]
      summary: List SSO provider configurations for the account (ACC-09)
      operationId: listSSOConfigs
      responses:
        "200":
          description: List of SSO configurations
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SSOConfig" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Auth]
      summary: Create new SAML 2.0 or OIDC SSO provider configuration (ACC-09)
      operationId: createSSOConfig
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateSSOConfigInput" }
      responses:
        "201":
          description: SSO configuration created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SSOConfig" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/sso/configs/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Auth]
      summary: Get SSO configuration details (ACC-09)
      operationId: getSSOConfig
      responses:
        "200":
          description: SSO configuration details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SSOConfig" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Auth]
      summary: Update SSO configuration (ACC-09)
      operationId: updateSSOConfig
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateSSOConfigInput" }
      responses:
        "200":
          description: SSO configuration updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SSOConfig" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Auth]
      summary: Delete SSO configuration (ACC-09)
      operationId: deleteSSOConfig
      responses:
        "204":
          description: SSO configuration deleted
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/sso/configs/{id}/saml-metadata:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Auth]
      summary: Get Service Provider SAML 2.0 Metadata XML (ACC-09)
      operationId: getSPMetadata
      responses:
        "200":
          description: SP XML Metadata
          content:
            application/xml:
              schema: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/sso/configs/{id}/audits:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Auth]
      summary: List audit log for SSO login events (ACC-09)
      operationId: listSSOAudits
      parameters:
        - name: limit
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: Audit entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SSOLoginAudit" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/sso/configs/{id}/test:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Auth]
      summary: Test SSO configuration connectivity and certificates (ACC-09)
      operationId: testSSOConfig
      responses:
        "200":
          description: Test diagnostic results
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SSOTestResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Team & Roles (ACC-04) ----------------
  /v1/team/members:
    get:
      tags: [Team]
      summary: List team members in the account (ACC-04)
      operationId: listTeamMembers
      responses:
        "200":
          description: Team members
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TeamMembersResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/team/members/{user_id}:
    parameters:
      - { name: user_id, in: path, required: true, schema: { type: string } }
    delete:
      tags: [Team]
      summary: Remove a member from the team (ACC-04)
      operationId: removeTeamMember
      responses:
        "200":
          description: Member removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Team]
      summary: Change role of a team member (ACC-04)
      operationId: updateTeamMemberRole
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateTeamMemberRoleRequest" }
      responses:
        "200":
          description: Role updated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TeamMember" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/team/invites:
    get:
      tags: [Team]
      summary: List pending team invitations (ACC-04)
      operationId: listTeamInvites
      responses:
        "200":
          description: Invitations list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TeamInvitesResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Team]
      summary: Invite a new user to the team by email (ACC-04)
      operationId: createTeamInvite
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateTeamInviteRequest" }
      responses:
        "201":
          description: Invitation sent
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TeamInvite" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/team/invites/{invite_id}:
    parameters:
      - { name: invite_id, in: path, required: true, schema: { type: string } }
    delete:
      tags: [Team]
      summary: Revoke a pending team invitation (ACC-04)
      operationId: revokeTeamInvite
      responses:
        "200":
          description: Invitation revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Audit Log (ACC-06) ----------------
  /v1/audit-logs:
    get:
      tags: [AuditLogs]
      summary: View account security and activity audit log (ACC-06)
      operationId: getAuditLogs
      parameters:
        - { $ref: "#/components/parameters/Cursor" }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
        - name: action
          in: query
          schema: { type: string }
        - name: resource_type
          in: query
          schema: { type: string }
        - name: user_id
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Audit log entries
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuditLogsResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  # ---------------- Media Library & CDN (EDT-04) ----------------
  /v1/media/upload:
    post:
      tags: [Media]
      summary: Upload an image asset to the media library (EDT-04)
      operationId: uploadMedia
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: Image file to upload (JPEG, PNG, GIF, WebP, SVG, max 10MB)
      responses:
        "201":
          description: Media file uploaded successfully
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MediaFile" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }

  /v1/media:
    get:
      tags: [Media]
      summary: List uploaded media files (EDT-04)
      operationId: listMedia
      parameters:
        - { $ref: "#/components/parameters/Cursor" }
        - { $ref: "#/components/parameters/Limit" }
      responses:
        "200":
          description: List of media files
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/MediaFile" }
                  next_cursor:
                    type: string

  /v1/media/{media_id}:
    parameters:
      - name: media_id
        in: path
        required: true
        schema: { type: string }
    delete:
      tags: [Media]
      summary: Delete a media file (EDT-04)
      operationId: deleteMedia
      responses:
        "200":
          description: Media file deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted: { type: boolean, example: true }
                  id: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/media/{media_id}/content:
    parameters:
      - name: media_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Media]
      summary: Serve media file content directly for emails/CDN without auth (EDT-04)
      operationId: getMediaContent
      security: []
      responses:
        "200":
          description: Raw image file content
          headers:
            Cache-Control:
              schema: { type: string, example: "public, max-age=31536000, immutable" }
            Content-Type:
              schema: { type: string, example: "image/png" }
          content:
            image/*:
              schema:
                type: string
                format: binary
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- IP Pools & Dedicated IPs (MTA-05) ----------------
  /v1/ip-pools:
    get:
      tags: [IPPools]
      summary: List available IP pools (shared and dedicated for the account) (MTA-05)
      operationId: listIPPools
      responses:
        "200":
          description: List of IP pools
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/IPPool" }
                  account_assignments:
                    type: object
                    properties:
                      tx_pool_id: { type: [string, "null"] }
                      mkt_pool_id: { type: [string, "null"] }
        "401": { $ref: "#/components/responses/Unauthorized" }

    post:
      tags: [IPPools]
      summary: Create an IP pool (MTA-05)
      operationId: createIPPool
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateIPPoolRequest" }
      responses:
        "201":
          description: IP pool created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPPool" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/ip-pools/assignments:
    post:
      tags: [IPPools]
      summary: Set dedicated IP pools for an account (MTA-05)
      operationId: setAccountIPPools
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SetAccountIPPoolsRequest" }
      responses:
        "200":
          description: Assignments updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated: { type: boolean }
                  account_id: { type: string }
                  tx_pool_id: { type: [string, "null"] }
                  mkt_pool_id: { type: [string, "null"] }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [IPPools]
      summary: Get details of an IP pool (MTA-05)
      operationId: getIPPool
      responses:
        "200":
          description: IP pool details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPPool" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Add an IP address to a pool (MTA-05)
      operationId: addIPToPool
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AddIPAddressRequest" }
      responses:
        "201":
          description: IP added to pool
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPAddress" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    delete:
      tags: [IPPools]
      summary: Remove an IP address from a pool (MTA-05)
      operationId: removeIPFromPool
      responses:
        "200":
          description: IP address removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted: { type: boolean, example: true }
                  id: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/verify-dns:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Verify FCrDNS and PTR alignment for all IPs in the pool (MTA-09)
      operationId: verifyPoolDNS
      responses:
        "200":
          description: FCrDNS verification results for pool IPs
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/FCrDNSResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/verify-dns:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Verify FCrDNS and PTR alignment for a specific IP in the pool (MTA-09)
      operationId: verifyIPDNS
      responses:
        "200":
          description: FCrDNS verification result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FCrDNSResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/warmup/plans:
    get:
      tags: [IPPools]
      summary: List available IP warm-up plan templates (MTA-11)
      operationId: listWarmupPlans
      responses:
        "200":
          description: List of warm-up plans
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/IPWarmupPlan" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/warmup:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [IPPools]
      summary: Get warm-up state, quotas, and spillover stats for an IP (MTA-11)
      operationId: getIPWarmupState
      responses:
        "200":
          description: Warm-up state
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPWarmupState" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/warmup/start:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Start warm-up plan on an IP address with spillover fallback (MTA-11)
      operationId: startIPWarmup
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/StartWarmupRequest" }
      responses:
        "200":
          description: Warm-up plan started
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPWarmupState" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/warmup/pause:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Pause IP warm-up progression (MTA-11)
      operationId: pauseIPWarmup
      responses:
        "200":
          description: Warm-up paused
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPWarmupState" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/warmup/resume:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Resume IP warm-up progression (MTA-11)
      operationId: resumeIPWarmup
      responses:
        "200":
          description: Warm-up resumed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPWarmupState" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/warmup/advance:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Advance IP warm-up plan to next day stage (MTA-11)
      operationId: advanceIPWarmup
      responses:
        "200":
          description: Advanced to next day stage
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPWarmupState" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/quarantine:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Manually quarantine an IP address from sending rotation (MTA-12)
      operationId: quarantineIP
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/QuarantineIPRequest" }
      responses:
        "200":
          description: IP address quarantined
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPAddress" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/unquarantine:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Remove IP address from quarantine and restore to active rotation (MTA-12)
      operationId: unquarantineIP
      responses:
        "200":
          description: IP address restored to rotation
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPAddress" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/{pool_id}/ips/{ip_id}/quarantine-check:
    parameters:
      - name: pool_id
        in: path
        required: true
        schema: { type: string }
      - name: ip_id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [IPPools]
      summary: Run on-demand DNSBL and block check on IP, auto-quarantining if listed (MTA-12)
      operationId: checkAndQuarantineIP
      responses:
        "200":
          description: Check result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IPQuarantineCheckResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ip-pools/quarantine-scan:
    post:
      tags: [IPPools]
      summary: Scan all active IP addresses across pools against DNSBL and blocklists (MTA-12)
      operationId: scanAndQuarantinePools
      responses:
        "200":
          description: Scan completed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QuarantineScanResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/moderation/campaigns:
    get:
      tags: [Admin]
      summary: List campaigns pending moderation review (ABS-03, ADM-02)
      operationId: listModerationCampaigns
      responses:
        "200":
          description: List of campaigns under review
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ModerationCampaignItem" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/moderation/campaigns/{campaign_id}:
    get:
      tags: [Admin]
      summary: Get details of campaign for moderation (ABS-03, ADM-02)
      operationId: getModerationCampaign
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Campaign moderation details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ModerationCampaignDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/moderation/campaigns/{campaign_id}/approve:
    post:
      tags: [Admin]
      summary: Approve campaign for sending (ABS-03, ADM-02)
      operationId: approveCampaign
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ApproveCampaignRequest" }
      responses:
        "200":
          description: Campaign approved
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/admin/moderation/campaigns/{campaign_id}/reject:
    post:
      tags: [Admin]
      summary: Reject campaign with a reason (ABS-03, ADM-02)
      operationId: rejectCampaign
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RejectCampaignRequest" }
      responses:
        "200":
          description: Campaign rejected
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Campaign" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/admin/accounts:
    get:
      tags: [Admin]
      summary: List all accounts with search and filters (ADM-02)
      operationId: adminListAccounts
      parameters:
        - name: q
          in: query
          description: Search term (name or ID)
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [active, suspended] }
        - name: moderation
          in: query
          schema: { type: string, enum: [pending, approved] }
        - name: limit
          in: query
          schema: { type: integer, default: 50, maximum: 200 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of accounts
          content:
            application/json:
              schema:
                type: object
                required: [data, total]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/AdminAccountItem" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/accounts/{account_id}:
    get:
      tags: [Admin]
      summary: Get detailed account info for operator (ADM-02)
      operationId: adminGetAccount
      parameters:
        - name: account_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Detailed account info
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AdminAccountDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Admin]
      summary: Update account settings, status or limits (ADM-02)
      operationId: adminUpdateAccount
      parameters:
        - name: account_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AdminUpdateAccountRequest" }
      responses:
        "200":
          description: Updated account
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AdminAccountItem" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/accounts/{account_id}/promote-tier:
    post:
      tags: [Admin]
      summary: Promote account to next warmup tier (ABS-01, ADM-02)
      operationId: adminPromoteAccountTier
      parameters:
        - name: account_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Promoted account
          content:
            application/json:
              schema:
                type: object
                required: [account_id, next_tier]
                properties:
                  account_id: { type: string }
                  next_tier: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/reputation:
    get:
      tags: [Admin]
      summary: Reputation overview across accounts and providers (ADM-02)
      operationId: adminGetReputationOverview
      responses:
        "200":
          description: Reputation summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AdminReputationOverview" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/stats:
    get:
      tags: [Admin]
      summary: Operator platform health & queues overview (ADM-02)
      operationId: adminGetPlatformStats
      responses:
        "200":
          description: Operator stats
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AdminPlatformStats" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/platform/relay-dns:
    get:
      tags: [Admin]
      summary: Technical relay DNS zone settings and public delegation health (ADM-02, MTA-11)
      description: |
        Platform admin only. The technical zone (e.g. `rv.apisend.ru`) is served by the platform's
        authoritative DNS; customers' sending subdomains CNAME into it (`<token>.<zone>`).
        `health` shows whether the zone is publicly delegated to `nameservers` and whether they resolve.
      operationId: adminGetRelayDNS
      responses:
        "200":
          description: Settings
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RelayDNSSettings" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    put:
      tags: [Admin]
      summary: Switch the technical relay DNS zone / nameservers (ADM-02, MTA-11)
      description: |
        Platform admin only. When `zone` changes, the previous zone is automatically kept in
        `legacy_zones` and keeps being served, so existing customer CNAMEs do not break; customers see a
        hint to update their records. Pass `legacy_zones` explicitly to retire old zones.
      operationId: adminSetRelayDNS
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [zone, nameservers]
              properties:
                zone: { type: string, example: rv.apisend.ru }
                nameservers: { type: array, minItems: 2, items: { type: string }, example: [ns1.apisend.ru, ns2.apisend.ru] }
                legacy_zones: { type: array, items: { type: string }, description: "Omit to keep current list (+ previous zone)." }
      responses:
        "200":
          description: Saved settings
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RelayDNSSettings" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/abuse/report:
    post:
      tags: [AntiAbuse]
      summary: Submit spam or abuse complaint (LAW-05)
      description: Public endpoint for email recipients, postmasters, and ISPs to report spam, phishing, fraud, or missing ORD marking.
      operationId: submitAbuseReport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AbuseReportInput"
      responses:
        "201":
          description: Abuse report registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AbuseReport"
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/admin/abuse-reports:
    get:
      tags: [Admin]
      summary: List all abuse and spam reports (LAW-05, ADM-02)
      description: Admin-only queue of submitted abuse reports with filtering by status and account.
      operationId: adminListAbuseReports
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [pending, investigating, resolved, dismissed] }
        - name: account_id
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 50, maximum: 100 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of abuse reports
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AbuseReport"
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/abuse-reports/{id}/resolve:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Admin]
      summary: Resolve or dismiss an abuse complaint (LAW-05, ADM-02)
      description: Updates report status and optionally applies enforcement actions (e.g. pause campaign or restrict account).
      operationId: adminResolveAbuseReport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AbuseReportResolution"
      responses:
        "200":
          description: Updated abuse report
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AbuseReport"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- System Health & Diagnostics (OPS-05, OPS-06) ----------------
  /v1/system/health:
    get:
      tags: [System, Status]
      summary: Deep system health diagnostics and worker liveness (OPS-05, OPS-06)
      operationId: getSystemHealth
      security: []
      responses:
        "200":
          description: Live health check of all microservices, workers, and DNS
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemHealthResponse"

  # ---------------- Status Page & Incidents (OPS-05) ----------------
  /v1/status:
    get:
      tags: [Status]
      summary: Public status page with component health, 90-day uptime and incidents (OPS-05)
      operationId: getPublicStatus
      security: []
      responses:
        "200":
          description: System status and component states
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusPageResponse"

  /v1/admin/incidents:
    get:
      tags: [Admin, Status]
      summary: List all system incidents (OPS-05)
      operationId: adminListIncidents
      responses:
        "200":
          description: List of incidents
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SystemIncident"
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      tags: [Admin, Status]
      summary: Create a system incident (OPS-05)
      operationId: adminCreateIncident
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSystemIncidentRequest"
      responses:
        "201":
          description: Incident created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemIncident"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/incidents/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    patch:
      tags: [Admin, Status]
      summary: Update or resolve system incident (OPS-05)
      operationId: adminUpdateIncident
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSystemIncidentRequest"
      responses:
        "200":
          description: Incident updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemIncident"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  # ---------------- Helpdesk & Support Tickets (ADM-03) ----------------
  /v1/tickets:
    get:
      tags: [Helpdesk]
      summary: List tickets for the authenticated account (ADM-03)
      operationId: listTickets
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [open, in_progress, waiting_customer, closed] }
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of support tickets
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SupportTicket"
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Helpdesk]
      summary: Create a new support ticket (ADM-03)
      operationId: createTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSupportTicketRequest"
      responses:
        "201":
          description: Support ticket created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportTicketDetail"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/tickets/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Helpdesk]
      summary: Get ticket details and conversation thread (ADM-03)
      operationId: getTicket
      responses:
        "200":
          description: Ticket details with messages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportTicketDetail"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/tickets/{id}/reply:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Helpdesk]
      summary: Add customer reply to support ticket (ADM-03)
      operationId: replyTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SupportTicketReplyRequest"
      responses:
        "201":
          description: Message added
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportTicketMessage"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/tickets/{id}/close:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Helpdesk]
      summary: Close support ticket (ADM-03)
      operationId: closeTicket
      responses:
        "200":
          description: Ticket closed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportTicket"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/tickets:
    get:
      tags: [Admin, Helpdesk]
      summary: List all tickets across all tenants (ADM-03)
      operationId: adminListTickets
      parameters:
        - name: account_id
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: category
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: Tickets list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/SupportTicket"
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/tickets/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      tags: [Admin, Helpdesk]
      summary: Admin get ticket details and thread (ADM-03)
      operationId: adminGetTicket
      responses:
        "200":
          description: Ticket detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportTicketDetail"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Admin, Helpdesk]
      summary: Admin update ticket status or priority (ADM-03)
      operationId: adminUpdateTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminUpdateTicketRequest"
      responses:
        "200":
          description: Updated ticket
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportTicket"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/tickets/{id}/reply:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    post:
      tags: [Admin, Helpdesk]
      summary: Agent reply to support ticket (ADM-03)
      operationId: adminReplyTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminTicketReplyRequest"
      responses:
        "201":
          description: Message added
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportTicketMessage"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/impersonate:
    post:
      tags: [Admin]
      summary: Impersonate user/tenant with security audit logging (ADM-03)
      operationId: adminImpersonate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminImpersonateRequest"
      responses:
        "200":
          description: Impersonation token issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminImpersonateResponse"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  # ---------------- SMS Channel (CHN-01) ----------------
  /v1/sms/send:
    post:
      tags: [SMS]
      summary: Send an SMS message (CHN-01)
      description: |
        Sends a single SMS message using approved alphanumeric sender ID or default gateway sender.
        Supports GSM-7 and UCS-2 encodings with automatic multi-segment calculation.
      operationId: sendSMS
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SMSRequest"
      responses:
        "202":
          description: SMS accepted for delivery
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SMSMessage"
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402":
          description: Insufficient funds or SMS quota exceeded
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/sms/messages:
    get:
      tags: [SMS]
      summary: List sent SMS messages (CHN-01)
      operationId: listSMSMessages
      security:
        - ApiKey: []
      parameters:
        - in: query
          name: recipient
          schema: { type: string }
          description: Filter by recipient phone number
        - in: query
          name: status
          schema:
            type: string
            enum: [accepted, sending, delivered, undelivered, failed, rejected, dropped]
        - in: query
          name: sender_id
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, default: 20, maximum: 100 }
        - in: query
          name: cursor
          schema: { type: string }
      responses:
        "200":
          description: List of SMS messages
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SMSMessage" }
                  next_cursor: { type: [string, "null"] }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/sms/messages/{id}:
    get:
      tags: [SMS]
      summary: Get SMS message status and details (CHN-01)
      operationId: getSMSMessage
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
          description: SMS message ULID
      responses:
        "200":
          description: SMS message details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SMSMessage" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/sms/calculate:
    post:
      tags: [SMS]
      summary: Calculate SMS parts, encoding and cost estimate (CHN-01)
      operationId: calculateSMS
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                text: { type: string, example: "Ваш код: 5543" }
      responses:
        "200":
          description: SMS length and segment calculation
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SMSCalculation" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/sms/senders:
    get:
      tags: [SMS]
      summary: List registered alphanumeric sender IDs (CHN-01)
      operationId: listSMSSenders
      security:
        - ApiKey: []
      responses:
        "200":
          description: List of sender IDs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SMSSenderID" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

    post:
      tags: [SMS]
      summary: Register a new alphanumeric sender ID for moderation (CHN-01)
      operationId: registerSMSSender
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SMSSenderIDInput" }
      responses:
        "201":
          description: Sender ID submitted for review
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SMSSenderID" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "409": { $ref: "#/components/responses/Conflict" }

  /v1/sms/senders/{id}:
    get:
      tags: [SMS]
      summary: Get sender ID details (CHN-01)
      operationId: getSMSSender
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Sender ID details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SMSSenderID" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

    delete:
      tags: [SMS]
      summary: Delete sender ID (CHN-01)
      operationId: deleteSMSSender
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "204": { description: Sender ID deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/sms/webhooks/{provider}:
    post:
      tags: [SMS]
      summary: Inbound DLR delivery status receipt webhook from SMS gateway (CHN-01)
      operationId: handleSMSWebhook
      security: []
      parameters:
        - in: path
          name: provider
          required: true
          schema:
            type: string
            enum: [smsc, smsru, mock]
      responses:
        "200":
          description: Status accepted and processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  processed: { type: integer, example: 1 }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/admin/sms/senders:
    get:
      tags: [Admin]
      summary: List all sender IDs across all accounts for operator moderation (CHN-01)
      operationId: adminListSMSSenders
      security:
        - ApiKey: []
      parameters:
        - in: query
          name: status
          schema: { type: string, enum: [pending, approved, rejected, suspended] }
      responses:
        "200":
          description: List of sender IDs for operator review
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SMSSenderID" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/admin/sms/senders/{id}/approve:
    post:
      tags: [Admin]
      summary: Approve alphanumeric sender ID (CHN-01)
      operationId: adminApproveSMSSender
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Sender ID approved
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/admin/sms/senders/{id}/reject:
    post:
      tags: [Admin]
      summary: Reject alphanumeric sender ID with reason (CHN-01)
      operationId: adminRejectSMSSender
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason: { type: string, example: "Имя не совпадает с товарным знаком юрлица" }
      responses:
        "200":
          description: Sender ID rejected
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/telegram/bots:
    get:
      tags: [Telegram]
      summary: List connected Telegram bots (CHN-02)
      operationId: listTelegramBots
      security:
        - ApiKey: []
      responses:
        "200":
          description: List of connected bots
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TelegramBot" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Telegram]
      summary: Connect a new Telegram bot via BotFather token (CHN-02)
      operationId: connectTelegramBot
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TelegramBotInput" }
      responses:
        "201":
          description: Bot connected and validated successfully
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TelegramBot" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "422":
          description: Invalid bot token or BotFather connection failure
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /v1/telegram/bots/{id}:
    get:
      tags: [Telegram]
      summary: Get Telegram bot details (CHN-02)
      operationId: getTelegramBot
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Bot details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TelegramBot" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Telegram]
      summary: Disconnect and delete Telegram bot (CHN-02)
      operationId: deleteTelegramBot
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Bot disconnected
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/telegram/bots/{id}/webhook:
    post:
      tags: [Telegram]
      summary: Register / renew webhook with Telegram Bot API (CHN-02)
      operationId: registerTelegramWebhook
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Webhook registered with Telegram
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  webhook_url: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/telegram/subscribers:
    get:
      tags: [Telegram]
      summary: List Telegram subscribers with filters and search (CHN-02)
      operationId: listTelegramSubscribers
      security:
        - ApiKey: []
      parameters:
        - in: query
          name: bot_id
          schema: { type: string }
          description: Filter by bot ID
        - in: query
          name: status
          schema: { type: string, enum: [active, unsubscribed, blocked_bot] }
        - in: query
          name: search
          schema: { type: string }
          description: Search by username, first name or chat_id
        - in: query
          name: limit
          schema: { type: integer, default: 50, maximum: 100 }
        - in: query
          name: offset
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: Paginated list of subscribers
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TelegramSubscriber" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/telegram/subscribers/{id}:
    get:
      tags: [Telegram]
      summary: Get Telegram subscriber profile (CHN-02)
      operationId: getTelegramSubscriber
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subscriber profile
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TelegramSubscriber" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Telegram]
      summary: Unsubscribe / deactivate Telegram subscriber (CHN-02)
      operationId: unsubscribeTelegramSubscriber
      security:
        - ApiKey: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subscriber unsubscribed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/telegram/send:
    post:
      tags: [Telegram]
      summary: Send a direct message to a chat or contact (CHN-02)
      operationId: sendTelegramMessage
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TelegramSendRequest" }
      responses:
        "202":
          description: Message queued or delivered
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TelegramMessage" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404":
          description: Bot or recipient contact not found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /v1/telegram/broadcast:
    post:
      tags: [Telegram]
      summary: Broadcast a message to all or filtered subscribers (CHN-02)
      operationId: broadcastTelegramMessage
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TelegramBroadcastRequest" }
      responses:
        "202":
          description: Broadcast initiated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TelegramBroadcastResponse" }
        "400": { $ref: "#/components/responses/ValidationError" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/telegram/messages:
    get:
      tags: [Telegram]
      summary: List sent Telegram messages (CHN-02)
      operationId: listTelegramMessages
      security:
        - ApiKey: []
      parameters:
        - in: query
          name: bot_id
          schema: { type: string }
        - in: query
          name: status
          schema: { type: string, enum: [pending, sent, delivered, failed, blocked_by_user] }
        - in: query
          name: limit
          schema: { type: integer, default: 50 }
        - in: query
          name: offset
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: Messages log
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TelegramMessage" }
                  total: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/telegram/webhooks/{bot_id}:
    post:
      tags: [Telegram]
      summary: Inbound webhook receiver for Telegram Bot API updates (CHN-02)
      operationId: handleTelegramWebhook
      security: []
      parameters:
        - in: path
          name: bot_id
          required: true
          schema: { type: string }
        - in: header
          name: X-Telegram-Bot-Api-Secret-Token
          schema: { type: string }
          description: Secret token set when registering webhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Standard Telegram Update object (JSON)
      responses:
        "200":
          description: Update acknowledged
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean, example: true }
        "403":
          description: Invalid secret token

  # ---------------- Integrations (CRM & No-Code platforms) [INT-03] ----------------
  /v1/integrations/crm/connections:
    get:
      tags: [Integrations]
      summary: List all CRM and No-Code platform connections
      operationId: listCRMConnections
      responses:
        "200":
          description: List of connections
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/CRMConnection" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Integrations]
      summary: Create or configure a new CRM/No-Code connection
      operationId: createCRMConnection
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CRMConnectionInput" }
      responses:
        "201":
          description: Connection created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CRMConnection" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/integrations/crm/connections/{id}:
    get:
      tags: [Integrations]
      summary: Get details of a specific CRM connection
      operationId: getCRMConnection
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Connection details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CRMConnection" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Integrations]
      summary: Delete a CRM connection
      operationId: deleteCRMConnection
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Connection deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/integrations/crm/webhooks/{provider}:
    post:
      tags: [Integrations]
      summary: Universal incoming webhook receiver for amoCRM, Bitrix24, RetailCRM, Albato, Zapier, Make
      operationId: handleCRMWebhook
      parameters:
        - in: path
          name: provider
          required: true
          schema: { $ref: "#/components/schemas/CRMProvider" }
        - in: query
          name: token
          schema: { type: string }
          description: Secret token or connection ID for authentication
        - in: header
          name: X-Integration-Token
          schema: { type: string }
          description: Alternative header authentication token
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
          application/x-www-form-urlencoded:
            schema: { type: object }
      responses:
        "200":
          description: Webhook received and processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  event_id: { type: string, example: "01J9CRM00000000000000001" }
                  action_taken: { type: string, example: "contact_created" }
                  contact_id: { type: [string, "null"] }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/integrations/crm/events:
    get:
      tags: [Integrations]
      summary: List incoming CRM synchronization events and audit log
      operationId: listCRMEvents
      parameters:
        - in: query
          name: provider
          schema: { $ref: "#/components/schemas/CRMProvider" }
        - in: query
          name: status
          schema: { type: string, enum: [processed, ignored, failed] }
        - in: query
          name: limit
          schema: { type: integer, default: 20 }
        - in: query
          name: offset
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: List of synchronization events
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/CRMSyncEvent" }
                  total: { type: integer, example: 42 }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/integrations/crm/simulate:
    post:
      tags: [Integrations]
      summary: Simulate incoming CRM webhook event for testing field mapping and workflows
      operationId: simulateCRMWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CRMSimulateRequest" }
      responses:
        "200":
          description: Simulation result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CRMSimulateResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  # ---------------- Migrations (Unisender, SendPulse, Mailchimp) [INT-04] ----------------
  /v1/migrations/competitors/validate:
    post:
      tags: [Migrations]
      summary: Validate competitor API credentials and fetch available lists and templates summary
      operationId: validateCompetitorCredentials
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/MigrationValidateInput" }
      responses:
        "200":
          description: Credentials validated and resources discovered
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MigrationSummary" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/migrations/competitors/jobs:
    get:
      tags: [Migrations]
      summary: List all migration jobs
      operationId: listMigrationJobs
      parameters:
        - in: query
          name: platform
          schema: { $ref: "#/components/schemas/CompetitorPlatform" }
        - in: query
          name: status
          schema: { $ref: "#/components/schemas/MigrationJobStatus" }
      responses:
        "200":
          description: Migration jobs list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/MigrationJob" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Migrations]
      summary: Create and start a new competitor migration job
      operationId: createMigrationJob
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/MigrationJobInput" }
      responses:
        "202":
          description: Migration job accepted and started in background
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MigrationJob" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/migrations/competitors/jobs/{id}:
    get:
      tags: [Migrations]
      summary: Get status and progress of a migration job
      operationId: getMigrationJob
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Migration job details
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MigrationJob" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/migrations/competitors/jobs/{id}/cancel:
    post:
      tags: [Migrations]
      summary: Cancel a running or pending migration job
      operationId: cancelMigrationJob
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Migration job cancelled
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MigrationJob" }
        "404": { $ref: "#/components/responses/NotFound" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/amp/validate:
    post:
      tags: [AMP]
      summary: Validate AMP for Email markup against amp4email specification
      description: |
        Requirement TX-16. Validates AMP email code against Google/amp.dev rules:
        checks for boilerplate, script tag, CSS size (<= 50KB), allowed tags, and syntax.
      operationId: validateAMPMarkup
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AMPValidateRequest" }
      responses:
        "200":
          description: Validation report
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AMPValidationResult" }
        "400": { $ref: "#/components/responses/ValidationError" }

  /v1/amp/samples:
    get:
      tags: [AMP]
      summary: Get curated interactive AMP email templates
      description: |
        Requirement TX-16. Returns ready-to-use interactive AMP email components
        (NPS survey, dynamic product carousel, accordion FAQ, interactive poll)
        with HTML and plain-text fallbacks.
      operationId: getAMPSamples
      responses:
        "200":
          description: Curated sample templates
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AMPSamplesResponse" }

  /v1/amp/actions/{action_name}:
    post:
      tags: [AMP]
      summary: Handle AMP action-xhr form submissions with AMP CORS headers
      description: |
        Requirement TX-16. Handles interactive email submissions from Gmail/Mail.ru
        and responds with AMP-Access-Control-Allow-Source CORS headers.
      operationId: submitAMPAction
      parameters:
        - in: path
          name: action_name
          required: true
          schema: { type: string }
        - in: query
          name: __amp_source_origin
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
          application/x-www-form-urlencoded:
            schema: { type: object, additionalProperties: true }
      responses:
        "200":
          description: Form submission processed successfully
          headers:
            AMP-Access-Control-Allow-Source:
              schema: { type: string }
            Access-Control-Expose-Headers:
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object }

  /v1/amp/submissions:
    get:
      tags: [AMP]
      summary: List received AMP form submissions
      description: |
        Requirement TX-16. Query interactive form responses collected from recipients.
      operationId: listAMPSubmissions
      parameters:
        - in: query
          name: campaign_id
          schema: { type: string }
        - in: query
          name: action_name
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of submissions
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AMPFormSubmissionsResponse" }

  # ---------------- Virtual Corporate Mailboxes [BOX-01] ----------------
  /v1/mailboxes:
    get:
      summary: List corporate mailboxes
      description: Returns all virtual corporate mailboxes for the account with real-time unread/total email counts and storage usage [BOX-01].
      operationId: listMailboxes
      tags: [Mailboxes]
      security:
        - ApiKey: []
      responses:
        "200":
          description: List of mailboxes
          content:
            application/json:
              schema:
                type: object
                required: [data, total]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Mailbox"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      summary: Create corporate mailbox
      description: Creates a new corporate mailbox for the specified email and generates an encrypted password hash for Dovecot IMAP/LMTP [BOX-01].
      operationId: createMailbox
      tags: [Mailboxes]
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateMailboxRequest"
      responses:
        "201":
          description: Mailbox created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mailbox"
        "400":
          $ref: "#/components/responses/ValidationError"
        "409":
          $ref: "#/components/responses/Conflict"

  /v1/mailboxes/{mailbox_id}:
    get:
      summary: Get mailbox details
      operationId: getMailbox
      tags: [Mailboxes]
      security:
        - ApiKey: []
      parameters:
        - name: mailbox_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Mailbox details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mailbox"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      summary: Update mailbox settings
      operationId: updateMailbox
      tags: [Mailboxes]
      security:
        - ApiKey: []
      parameters:
        - name: mailbox_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateMailboxRequest"
      responses:
        "200":
          description: Mailbox updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mailbox"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      summary: Delete mailbox
      operationId: deleteMailbox
      tags: [Mailboxes]
      security:
        - ApiKey: []
      parameters:
        - name: mailbox_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Mailbox deleted
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/mailboxes/{mailbox_id}/password:
    post:
      summary: Change mailbox password
      operationId: changeMailboxPassword
      tags: [Mailboxes]
      security:
        - ApiKey: []
      parameters:
        - name: mailbox_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password:
                  type: string
                  minLength: 6
      responses:
        "200":
          description: Password updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/mailboxes/{mailbox_id}/messages:
    get:
      summary: List messages in mailbox
      description: Returns recent messages from the mailbox Maildir with sender, subject, date, unread flag and snippet [BOX-01].
      operationId: listMailboxMessages
      tags: [Mailboxes]
      security:
        - ApiKey: []
      parameters:
        - name: mailbox_id
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 50
      responses:
        "200":
          description: List of mailbox messages
          content:
            application/json:
              schema:
                type: object
                required: [data, total]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/MailboxMessage"
                  total:
                    type: integer
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/mailboxes/{mailbox_id}/messages/{message_id}:
    get:
      summary: Get full mailbox message
      description: Returns parsed email message with headers, plain text and HTML bodies [BOX-01].
      operationId: getMailboxMessage
      tags: [Mailboxes]
      security:
        - ApiKey: []
      parameters:
        - name: mailbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Full email message
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MailboxMessageDetail"
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/mailboxes/{mailbox_id}/messages/{message_id}/read:
    post:
      summary: Mark message as read
      description: Moves message from new/ to cur/ with seen flag [BOX-01].
      operationId: markMailboxMessageRead
      tags: [Mailboxes]
      security:
        - ApiKey: []
      parameters:
        - name: mailbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Marked as read
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
        "404":
          $ref: "#/components/responses/NotFound"

  # ---------------- Mail Admin Portal Endpoints [BOX-01] ----------------
  /v1/mail-admin/login:
    post:
      summary: Mail administrator login
      description: Dedicated entrance for corporate mail administrator with anti-brute-force defense and IP lockout.
      operationId: mailAdminLogin
      tags: [Mailboxes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username: { type: string, example: "admin" }
                password: { type: string, example: "FitonikMaster2026!" }
      responses:
        "200":
          description: Logged in successfully
          content:
            application/json:
              schema:
                type: object
                required: [token, username, expires_in]
                properties:
                  token: { type: string }
                  username: { type: string }
                  expires_in: { type: integer }
        "401":
          description: Invalid credentials with remaining attempts count
          $ref: "#/components/responses/Unauthorized"
        "429":
          description: Locked out due to too many failed attempts
          $ref: "#/components/responses/RateLimited"

  /v1/mail-admin/logout:
    post:
      summary: Mail administrator logout
      operationId: mailAdminLogout
      tags: [Mailboxes]
      responses:
        "200":
          description: Session invalidated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }

  /v1/mail-admin/mailboxes:
    get:
      summary: List all corporate mailboxes for admin
      operationId: mailAdminListMailboxes
      tags: [Mailboxes]
      responses:
        "200":
          description: List of mailboxes
          content:
            application/json:
              schema:
                type: object
                required: [data, total]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Mailbox"
                  total:
                    type: integer
    post:
      summary: Create corporate mailbox from admin portal
      operationId: mailAdminCreateMailbox
      tags: [Mailboxes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateMailboxRequest"
      responses:
        "201":
          description: Mailbox created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mailbox"

  /v1/mail-admin/sso:
    post:
      summary: 1-Click drill-down SSO into Roundcube Webmail
      description: Generates an authenticated Roundcube session using master credentials without asking for user password.
      operationId: mailAdminSSO
      tags: [Mailboxes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, example: "info@fitonik.ru" }
      responses:
        "200":
          description: Roundcube session established
          content:
            application/json:
              schema:
                type: object
                required: [email, sessid, redirect_url]
                properties:
                  email: { type: string }
                  sessid: { type: string }
                  sessauth: { type: string }
                  redirect_url: { type: string }

  /v1/mail-admin/domains:
    get:
      summary: List corporate mail domains
      description: Returns all corporate domains configured for mailboxes with DNS status and instructions.
      operationId: mailAdminListDomains
      tags: [Mailboxes]
      responses:
        "200":
          description: List of corporate mail domains
          content:
            application/json:
              schema:
                type: object
                required: [data, total]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CorporateDomain"
                  total: { type: integer }
    post:
      summary: Add a new corporate mail domain
      description: Registers a new corporate domain, generates its DKIM signing key, and returns DNS setup records.
      operationId: mailAdminCreateDomain
      tags: [Mailboxes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain: { type: string, example: "fitomik.app" }
      responses:
        "201":
          description: Corporate domain registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CorporateDomain"
        "409":
          description: Domain already exists

  /v1/mail-admin/domains/{domain_id}/verify:
    post:
      summary: Check DNS records for corporate mail domain
      description: Performs live verification of MX, SPF, DKIM, and DMARC DNS records.
      operationId: mailAdminVerifyDomain
      tags: [Mailboxes]
      parameters:
        - name: domain_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Verification check completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CorporateDomain"

  /v1/mail-admin/domains/{domain_id}:
    delete:
      summary: Remove a corporate mail domain
      description: Removes the corporate domain if no active mailboxes are attached to it.
      operationId: mailAdminDeleteDomain
      tags: [Mailboxes]
      parameters:
        - name: domain_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Domain deleted
          content:
            application/json:
              schema:
                type: object
                required: [success]
                properties:
                  success: { type: boolean }
        "409":
          description: Active mailboxes exist

components:
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: "API key, e.g. `Authorization: Bearer sk_live_...`"

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Repeating a request with the same key within 24h returns the original result
        without sending again (TX-04). Keys are scoped to the account. Reusing a key with a
        different body returns `409`; a concurrent request with a key still in progress also returns `409`.
      schema: { type: string, minLength: 1, maxLength: 255 }
    MessageId:
      name: message_id
      in: path
      required: true
      schema: { type: string, pattern: "^[0-9A-HJKMNP-TV-Z]{26}$" }
    DomainId:
      name: domain_id
      in: path
      required: true
      schema: { type: string, pattern: "^[0-9A-HJKMNP-TV-Z]{26}$" }
    KeyId:
      name: key_id
      in: path
      required: true
      schema: { type: string, pattern: "^[0-9A-HJKMNP-TV-Z]{26}$" }
    Cursor:
      name: cursor
      in: query
      schema: { type: string }
    ListId:
      name: list_id
      in: path
      required: true
      schema: { type: string, pattern: "^[0-9A-HJKMNP-TV-Z]{26}$" }
    CampaignId:
      name: campaign_id
      in: path
      required: true
      schema: { type: string, pattern: "^[0-9A-HJKMNP-TV-Z]{26}$" }
    SegmentId:
      name: segment_id
      in: path
      required: true
      schema: { type: string, pattern: "^[0-9A-HJKMNP-TV-Z]{26}$" }
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 1000, default: 100 }

  responses:
    ValidationError:
      description: Request is invalid
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    Forbidden:
      description: Key lacks scope, domain not verified, or account restricted
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    Conflict:
      description: Conflict (e.g. idempotency key reused with a different body)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    PayloadTooLarge:
      description: Message exceeds the size limit
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    RateLimited:
      description: Rate limit exceeded; see Retry-After header
      headers:
        Retry-After: { description: Seconds to wait before retrying, schema: { type: integer } }
        X-RateLimit-Limit: { description: Requests per second allowed for the account, schema: { type: integer } }
        X-RateLimit-Remaining: { description: Tokens left in the bucket, schema: { type: integer } }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    Unavailable:
      description: A dependency is temporarily unavailable; nothing was accepted, retry with the same Idempotency-Key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }

  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - validation_error
                - unauthorized
                - forbidden
                - not_found
                - method_not_allowed
                - conflict
                - payload_too_large
                - rate_limited
                - internal_error
                - service_unavailable
            message: { type: string }
            details: { type: object, additionalProperties: true }
        request_id: { type: string }

    HealthReport:
      type: object
      required: [status]
      properties:
        status: { type: string, enum: [ok, fail] }
        checks:
          type: object
          additionalProperties: { type: string }

    TSAStatusResponse:
      type: object
      required: [status, daemon_url, suspensions]
      properties:
        status:
          type: string
          example: "OK"
        daemon_url:
          type: string
          example: "http://tsa-daemon:8008"
        suspensions:
          type: object
          properties:
            ready_q:
              type: array
              items:
                type: object
            sched_q:
              type: array
              items:
                type: object
        bounces:
          type: array
          items:
            type: object

    TestShapingRequest:
      type: object
      required: [response]
      properties:
        response:
          type: string
          example: "421 4.7.28 Our system has detected an unusual rate of unsolicited mail"
        domain:
          type: string
          example: "gmail.com"

    TestShapingResponse:
      type: object
      required: [matched]
      properties:
        matched:
          type: boolean
          example: true
        domain:
          type: string
          example: "gmail.com"
        rule_regex:
          type: string
          example: "4\\.7\\.28|rate limited|unusual rate"
        action:
          type: object
          properties:
            type:
              type: string
              example: "SetConfig"
            name:
              type: string
              example: "max_message_rate"
            value:
              type: string
              example: "10/minute"
        duration:
          type: string
          example: "30 minutes"

    Address:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email, maxLength: 254 }
        name: { type: string, maxLength: 200 }

    Metadata:
      type: object
      description: Custom key-value data returned in events and webhooks. Up to 20 keys; key ≤ 64, value ≤ 1024 characters.
      maxProperties: 20
      additionalProperties: { type: string, maxLength: 1024 }

    Recipient:
      allOf:
        - $ref: "#/components/schemas/Address"
        - type: object
          properties:
            variables:
              type: object
              additionalProperties: true
              description: Per-recipient template variables (TX-02); override request-level `variables`. Rendered from E3.
            metadata:
              $ref: "#/components/schemas/Metadata"
              description: Merged over request-level `metadata` for this recipient.

    Attachment:
      type: object
      required: [filename, content]
      properties:
        filename: { type: string, maxLength: 255 }
        content: { type: string, contentEncoding: base64, description: Standard base64 }
        content_type: { type: string, description: "Defaults to the type guessed from the file extension, else application/octet-stream" }
        content_id: { type: string, maxLength: 255, description: "Set for inline (CID) images: reference as `cid:<content_id>` in html" }

    SendEmailRequest:
      type: object
      required: [from, to]
      description: Either `html` or `text` (or both) is required. Unknown fields are rejected.
      properties:
        from: { $ref: "#/components/schemas/Address" }
        reply_to: { $ref: "#/components/schemas/Address" }
        to: { type: array, minItems: 1, maxItems: 500, items: { $ref: "#/components/schemas/Recipient" } }
        cc: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/Address" }, description: "Only with a single `to`; cc + bcc ≤ 50" }
        bcc: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/Address" }, description: "Only with a single `to`; cc + bcc ≤ 50" }
        subject: { type: string, minLength: 1, maxLength: 998 }
        html: { type: string }
        text: { type: string }
        amp_html: { type: string, description: "AMP for Email interactive markup (RFC 822 text/x-amp-html part placed after text/html)" }
        template_id: { type: string, description: "Use a stored template instead of subject/html/text (TX-07). Subject and body are populated from the template if omitted." }
        variables: { type: object, additionalProperties: true, description: Variables shared by all recipients }
        headers:
          type: object
          maxProperties: 50
          additionalProperties: { type: string, maxLength: 998 }
          description: |
            Custom headers. Names must be valid RFC 5322 field names; values must not contain CR/LF.
            Headers managed by the platform cannot be set: From, To, Cc, Bcc, Subject, Date, Message-ID,
            Reply-To, Sender, Return-Path, MIME-Version, Content-*, DKIM-Signature, Received, X-ESP-*.
        attachments: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/Attachment" } }
        tags: { type: array, maxItems: 10, uniqueItems: true, items: { type: string, minLength: 1, maxLength: 64 } }
        metadata: { $ref: "#/components/schemas/Metadata" }
        stream: { type: string, enum: [transactional, broadcast], default: transactional, description: "Selects queues and IP pools (TX-09)" }
        ip_pool: { type: string, description: "Explicit egress IP pool name (MTA-05). Overrides the default stream pool if allowed." }
        tracking:
          type: object
          properties:
            opens: { type: boolean, default: true }
            clicks: { type: boolean, default: true }
            utm: { $ref: "#/components/schemas/UTMParams" }
        send_at: { type: string, format: date-time, description: "Scheduled sending (TX-10, P1). Must be in the future, up to 72 hours." }
        sandbox: { type: boolean, default: false, description: "Process fully but never deliver externally (TX-08). Always on for `test` keys." }

    SendEmailResponse:
      type: object
      required: [submission_id, sandbox, messages]
      properties:
        submission_id: { type: string, description: ID of this request; groups the messages it created }
        sandbox: { type: boolean }
        messages:
          type: array
          description: One entry per recipient, in the order to → cc → bcc.
          items:
            type: object
            required: [email, message_id, status]
            properties:
              email: { type: string, format: email }
              message_id: { type: string }
              status: { type: string, enum: [accepted, scheduled, dropped], description: "`scheduled` when send_at is set (TX-10); `dropped` if suppressed" }
              reason: { type: string, description: Present when dropped (e.g. suppressed) }

    MessageStatus:
      type: string
      enum: [accepted, scheduled, queued, sent, delivered, deferred, bounced, dropped, failed, cancelled]

    MessageSummary:
      type: object
      required: [id, status, to, created_at]
      properties:
        id: { type: string }
        status: { $ref: "#/components/schemas/MessageStatus" }
        from: { type: string }
        to: { type: string }
        subject: { type: string }
        tags: { type: array, items: { type: string } }
        send_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    EventType:
      type: string
      enum: [accepted, queued, sent, delivered, deferred, bounced, dropped, failed, opened, clicked, unsubscribed, complained]

    Message:
      type: object
      required: [message_id, status, to, created_at]
      properties:
        message_id: { type: string }
        status: { $ref: "#/components/schemas/MessageStatus" }
        from: { type: string }
        to: { type: string }
        subject: { type: string }
        tags: { type: array, items: { type: string } }
        metadata: { type: object, additionalProperties: { type: string } }
        created_at: { type: string, format: date-time }
        events: { type: array, items: { $ref: "#/components/schemas/Event" } }

    Event:
      type: object
      required: [id, type, message_id, email, timestamp]
      properties:
        id: { type: string }
        type: { $ref: "#/components/schemas/EventType" }
        message_id: { type: string }
        email: { type: string }
        timestamp: { type: string, format: date-time }
        tags: { type: array, items: { type: string } }
        metadata: { type: object, additionalProperties: { type: string } }
        bounce:
          type: object
          properties:
            class: { type: string, enum: [hard, soft, block, spam, mailbox_full, unknown_user, policy, unknown] }
            smtp_code: { type: integer }
            enhanced_code: { type: string, example: "5.1.1" }
            response: { type: string }
        click:
          type: object
          properties:
            url: { type: string, format: uri }
        is_machine: { type: boolean, description: Open/click by a proxy or scanner (EVT-07) }
        user_agent: { type: string }
        ip: { type: string }
        tls_version: { type: string, description: TLS version negotiated during delivery attempt (MTA-08), example: "TLSv1.3" }
        tls_cipher: { type: string, description: TLS cipher suite negotiated during delivery attempt (MTA-08), example: "TLS_AES_256_GCM_SHA384" }
        stream: { type: string, enum: [transactional, broadcast], description: "Traffic stream (transactional or broadcast)" }
        mailbox_provider: { type: string, description: "Recipient mailbox provider (gmail, yandex, mailru, microsoft, rambler, yahoo, other)" }

    StatsRow:
      type: object
      properties:
        key: { type: string, description: "Group key: timestamp, tag, domain..." }
        accepted: { type: integer }
        delivered: { type: integer }
        deferred: { type: integer }
        bounced_hard: { type: integer }
        bounced_soft: { type: integer }
        dropped: { type: integer }
        opened_total: { type: integer, description: "Total open events (including Apple MPP and proxy pre-fetches)" }
        opened_unique: { type: integer, description: "Unique human opens (is_machine=false)" }
        opened_machine: { type: integer, description: "Machine opens (Apple Mail Privacy Protection, Google/Yahoo proxy, crawlers)" }
        clicked_total: { type: integer, description: "Total click events (including security scanners)" }
        clicked_unique: { type: integer, description: "Unique human clicks (is_machine=false)" }
        clicked_machine: { type: integer, description: "Machine clicks (Microsoft SafeLinks, Barracuda, Proofpoint, etc.)" }
        unsubscribed: { type: integer }
        complained: { type: integer }

    EventExportJob:
      type: object
      required: [id, status, format, created_at]
      properties:
        id: { type: string }
        status: { type: string, enum: [pending, processing, completed, failed] }
        format: { type: string, enum: [csv, jsonl] }
        filter: { type: object }
        row_count: { type: integer }
        size_bytes: { type: integer }
        download_url: { type: string }
        error_message: { type: string }
        created_at: { type: string, format: date-time }
        started_at: { type: string, format: date-time }
        completed_at: { type: string, format: date-time }

    CreateEventExportJobRequest:
      type: object
      properties:
        format: { type: string, enum: [csv, jsonl], default: csv }
        filter:
          type: object
          properties:
            type: { $ref: "#/components/schemas/EventType" }
            message_id: { type: string }
            email: { type: string, format: email }
            campaign_id: { type: string }
            tag: { type: string }
            is_machine: { type: boolean }
            from: { type: string, format: date-time }
            to: { type: string, format: date-time }
            limit: { type: integer, minimum: 1, maximum: 500000 }

    EventStreamSink:
      type: object
      required: [id, name, sink_type, status, created_at]
      properties:
        id: { type: string }
        name: { type: string }
        sink_type: { type: string, enum: [s3, clickhouse] }
        status: { type: string, enum: [active, paused, error] }
        event_types: { type: array, items: { type: string } }
        s3_config:
          type: object
          properties:
            endpoint: { type: string }
            bucket: { type: string }
            region: { type: string }
            prefix: { type: string }
            format: { type: string, enum: [jsonl, csv] }
        clickhouse_config:
          type: object
          properties:
            addr: { type: string }
            database: { type: string }
            table: { type: string }
            username: { type: string }
            secure: { type: boolean }
        total_forwarded: { type: integer }
        last_flushed_at: { type: string, format: date-time }
        last_error: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateEventStreamSinkRequest:
      type: object
      required: [name, sink_type]
      properties:
        name: { type: string }
        sink_type: { type: string, enum: [s3, clickhouse] }
        event_types: { type: array, items: { type: string } }
        s3_config:
          type: object
          properties:
            endpoint: { type: string }
            bucket: { type: string }
            region: { type: string }
            prefix: { type: string }
            access_key: { type: string }
            secret_key: { type: string }
            format: { type: string, enum: [jsonl, csv], default: jsonl }
        clickhouse_config:
          type: object
          properties:
            addr: { type: string }
            database: { type: string }
            table: { type: string }
            username: { type: string }
            password: { type: string }
            secure: { type: boolean }

    UpdateEventStreamSinkRequest:
      type: object
      properties:
        name: { type: string }
        status: { type: string, enum: [active, paused] }
        event_types: { type: array, items: { type: string } }
        s3_config:
          type: object
          properties:
            endpoint: { type: string }
            bucket: { type: string }
            region: { type: string }
            prefix: { type: string }
            access_key: { type: string }
            secret_key: { type: string }
            format: { type: string, enum: [jsonl, csv] }
        clickhouse_config:
          type: object
          properties:
            addr: { type: string }
            database: { type: string }
            table: { type: string }
            username: { type: string }
            password: { type: string }
            secure: { type: boolean }

    DnsRecord:
      type: object
      description: |
        A DNS record the customer must publish. The domain becomes `verified` when DKIM is valid AND
        (SPF OR return-path) is valid. DMARC and tracking are recommended (`required: false`).
      required: [type, host, value, purpose, required, status]
      properties:
        type: { type: string, enum: [TXT, CNAME, NS] }
        host: { type: string, description: Fully qualified record name, example: s20261004ab12._domainkey.mail.example.com }
        value: { type: string, description: Expected value (for SPF — the include to add to the existing record) }
        purpose: { type: string, enum: [spf, dkim, dmarc, return_path, tracking, relay] }
        required: { type: boolean }
        selector: { type: string, description: DKIM selector (only for `dkim`) }
        status:
          type: string
          description: "`error` — temporary DNS failure (SERVFAIL/timeout), the previous domain status is kept."
          enum: [pending, valid, invalid, error]
        error: { type: string, description: Human-readable reason when status is `invalid` or `error` }

    RelayDNSSettings:
      type: object
      required: [zone, nameservers, legacy_zones, health]
      properties:
        zone: { type: string, example: rv.apisend.ru }
        nameservers: { type: array, items: { type: string } }
        legacy_zones: { type: array, items: { type: string } }
        updated_by: { type: string }
        updated_at: { type: string, format: date-time }
        health:
          type: object
          properties:
            zone_delegated: { type: boolean }
            zone_nameservers: { type: array, items: { type: string } }
            nameserver_ips: { type: object, additionalProperties: { type: array, items: { type: string } } }
            problems: { type: array, items: { type: string } }

    DkimKey:
      type: object
      required: [selector, status, created_at]
      properties:
        selector: { type: string }
        status: { type: string, enum: [pending, active, retired] }
        created_at: { type: string, format: date-time }
        activated_at: { type: [string, "null"], format: date-time }
        retired_at: { type: [string, "null"], format: date-time }

    Domain:
      type: object
      required: [id, name, status, dns_records, dkim_keys, created_at]
      properties:
        id: { type: string }
        name: { type: string, example: mail.example.com }
        status:
          type: string
          description: "`failed` — was verified earlier but records are now invalid; sending is blocked."
          enum: [pending, verified, failed]
        dns_records: { type: array, items: { $ref: "#/components/schemas/DnsRecord" } }
        dkim_keys: { type: array, items: { $ref: "#/components/schemas/DkimKey" } }
        unisender_go:
          type: object
          properties:
            status: { type: string, enum: [not_synced, created, verified, failed] }
            dkim_pass: { type: boolean }
            spf_pass: { type: boolean }
            verification_record: { type: string }
            dkim_host: { type: string }
            dkim_value: { type: string }
            spf_value: { type: string }
            message: { type: string }
            checked_at: { type: [string, "null"], format: date-time }
            subdomain: { type: string, description: "Configured delegated label; empty means default." }
            sending_domain: { type: string, description: "FQDN registered in the upstream relay." }
        sending_subdomain:
          type: string
          example: send
          description: |
            Label used for white-label backup delivery (warm-up spillover, failover).
            `@` means the domain itself. Default: `send` for registrable domains, `@` for subdomains.
        sending_domain: { type: string, example: send.example.com, description: "FQDN of the sending subdomain." }
        relay_dns:
          type: object
          description: |
            How the sending subdomain is delegated to the platform. `cname_records` (default mode) point
            `<sending_domain>` and `<selector>._domainkey.<sending_domain>` into the platform's technical
            zone; `ns_records` delegate the whole subdomain. Values never reference the upstream relay.
          required: [mode, cname_records, ns_records, delegation_status]
          properties:
            mode: { type: string, enum: [cname, ns] }
            cname_records: { type: array, items: { $ref: "#/components/schemas/DnsRecord" }, description: "Empty when the sending domain is the domain itself." }
            ns_records: { type: array, items: { $ref: "#/components/schemas/DnsRecord" } }
            delegation_status: { type: string, enum: [pending, valid, invalid, error] }
            delegation_error: { type: string }
        last_checked_at: { type: [string, "null"], format: date-time }
        verified_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }

    Sender:
      type: object
      required: [id, account_id, email, name, reply_to, is_default, created_at, updated_at]
      properties:
        id: { type: string, example: "01H00000000000000000000001" }
        account_id: { type: string }
        email: { type: string, format: email, example: "info@fitonik.ru" }
        name: { type: string, example: "Фитоник Инфо" }
        reply_to: { type: string, example: "support@fitonik.ru" }
        domain_id: { type: [string, "null"] }
        is_default: { type: boolean, example: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateSenderInput:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email, example: "info@fitonik.ru" }
        name: { type: string, example: "Фитоник Инфо" }
        reply_to: { type: string, example: "support@fitonik.ru" }
        domain_id: { type: string }
        is_default: { type: boolean }

    UpdateSenderInput:
      type: object
      properties:
        name: { type: string, example: "Фитоник Отдел Продаж" }
        reply_to: { type: string, example: "sales@fitonik.ru" }
        is_default: { type: boolean }

    TemplateInput:
      type: object
      required: [name, subject]
      properties:
        name: { type: string, maxLength: 200 }
        subject: { type: string }
        html: { type: string }
        text: { type: string }
        amp_html: { type: string, description: "AMP for Email interactive markup (RFC 822 text/x-amp-html)" }
        default_locale: { type: string, example: "ru" }

    Template:
      allOf:
        - $ref: "#/components/schemas/TemplateInput"
        - type: object
          required: [id, version, created_at, updated_at]
          properties:
            id: { type: string }
            version: { type: integer }
            locales: { type: array, items: { type: string }, example: ["en", "ru"] }
            created_at: { type: string, format: date-time }
            updated_at: { type: string, format: date-time }

    TemplateLocaleInput:
      type: object
      required: [subject]
      properties:
        subject: { type: string }
        html: { type: string }
        text: { type: string }
        amp_html: { type: string, description: "AMP for Email interactive markup for this locale" }

    TemplateLocale:
      allOf:
        - $ref: "#/components/schemas/TemplateLocaleInput"
        - type: object
          required: [id, account_id, template_id, locale, created_at, updated_at]
          properties:
            id: { type: string }
            account_id: { type: string }
            template_id: { type: string }
            locale: { type: string, example: "en" }
            created_at: { type: string, format: date-time }
            updated_at: { type: string, format: date-time }

    DynamicBlockRule:
      type: object
      required: [content]
      properties:
        segment: { type: string, description: Target segment name or ID }
        tag: { type: string, description: Target tag }
        locale: { type: string, description: Target locale code }
        content: { type: string, description: Content to inject when rule matches }

    DynamicBlockInput:
      type: object
      required: [name]
      properties:
        template_id: { type: string, nullable: true }
        name: { type: string, description: 'Identifier for {{dynamic_block "name"}}' }
        description: { type: string }
        default_content: { type: string, description: Fallback content if no rule matches }
        rules: { type: array, items: { $ref: "#/components/schemas/DynamicBlockRule" } }

    DynamicBlock:
      allOf:
        - $ref: "#/components/schemas/DynamicBlockInput"
        - type: object
          required: [id, account_id, created_at, updated_at]
          properties:
            id: { type: string }
            account_id: { type: string }
            created_at: { type: string, format: date-time }
            updated_at: { type: string, format: date-time }

    SuppressionStream:
      type: string
      enum: [all, transactional, broadcast]
      default: all

    SuppressionReason:
      type: string
      enum: [hard_bounce, complaint, unsubscribe, manual]

    Suppression:
      type: object
      required: [email, reason, created_at]
      properties:
        email: { type: string, format: email }
        reason: { $ref: "#/components/schemas/SuppressionReason" }
        stream: { $ref: "#/components/schemas/SuppressionStream" }
        details: { type: string }
        source_message_id: { type: [string, "null"] }
        created_at: { type: string, format: date-time }

    GlobalSuppressionReason:
      type: string
      enum: [spam_trap, role_based, honeypot, complaint, platform_ban, manual]

    GlobalSuppression:
      type: object
      required: [email, reason, created_at]
      properties:
        email: { type: string }
        reason: { $ref: "#/components/schemas/GlobalSuppressionReason" }
        details: { type: string }
        created_at: { type: string, format: date-time }

    SuppressionCheckResult:
      type: object
      required: [email, is_suppressed, scope]
      properties:
        email: { type: string, format: email }
        is_suppressed: { type: boolean }
        reason: { type: string }
        stream: { $ref: "#/components/schemas/SuppressionStream" }
        scope: { type: string, enum: [global, account, role_based, none] }
        details: { type: string }

    WebhookInput:
      type: object
      required: [url, events]
      properties:
        url: { type: string, format: uri }
        events: { type: array, minItems: 1, items: { $ref: "#/components/schemas/EventType" } }
        enabled: { type: boolean, default: true }

    Webhook:
      allOf:
        - $ref: "#/components/schemas/WebhookInput"
        - type: object
          required: [id, created_at]
          properties:
            id: { type: string }
            signing_secret: { type: string, description: Returned only on creation; HMAC-SHA256 key }
            created_at: { type: string, format: date-time }

    Scope:
      type: string
      description: |
        Permission granted to an API key:
        - `send` — send email (HTTP API and SMTP);
        - `read_events` / `read_stats` — read events, message status and statistics;
        - `manage_domains`, `manage_templates`, `manage_suppressions`, `manage_webhooks` — CRUD of the resource;
        - `manage_keys` — create, list and revoke API keys.
      enum: [send, read_events, read_stats, manage_domains, manage_templates, manage_suppressions, manage_webhooks, manage_keys]

    ApiKeyMode:
      type: string
      description: "`test` keys never deliver externally (sandbox, TX-08)."
      enum: [live, test]

    ApiKey:
      type: object
      required: [id, name, mode, prefix, scopes, created_at]
      properties:
        id: { type: string }
        name: { type: string }
        mode: { $ref: "#/components/schemas/ApiKeyMode" }
        prefix: { type: string, description: First characters of the key for identification, example: sk_live_7fQ2 }
        scopes: { type: array, items: { $ref: "#/components/schemas/Scope" } }
        last_used_at: { type: [string, "null"], format: date-time, description: Updated with up to 1 minute delay }
        created_at: { type: string, format: date-time }
        ip_allowlist: { type: array, items: { type: string }, description: "Optional IP allowlist (IPv4, IPv6, CIDR) (ACC-08)" }
        rate_limit_rps: { type: integer, nullable: true, description: "Optional sub-rate limit in requests per second (ACC-11)" }

    ContactList:
      type: object
      required: [id, name, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        created_at: { type: string, format: date-time }

    ContactInput:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email }
        phone: { type: string }
        tags: { type: array, items: { type: string } }
        attributes: { type: object, additionalProperties: true, description: Merge variables for templates }
        consent_source: { type: string }

    Contact:
      type: object
      required: [id, list_id, email, active, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        list_id: { type: string }
        email: { type: string, format: email }
        phone: { type: string }
        tags: { type: array, items: { type: string } }
        attributes: { type: object, additionalProperties: true }
        active: { type: boolean }
        unsubscribed_at: { type: [string, "null"], format: date-time }
        consent_source: { type: string }
        consent_at: { type: [string, "null"], format: date-time }
        engagement_score: { type: integer, minimum: 0, maximum: 100, description: "RFM engagement score 0-100" }
        engagement_tier: { type: string, enum: [engaged, active, cooling, dormant, new] }
        last_open_at: { type: [string, "null"], format: date-time }
        last_click_at: { type: [string, "null"], format: date-time }
        unopened_streak: { type: integer }
        total_sent: { type: integer }
        total_opened: { type: integer }
        total_clicked: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    TierStats:
      type: object
      required: [count, percent]
      properties:
        count: { type: integer }
        percent: { type: number, description: "Percentage of total contacts (0.0 - 100.0)" }

    EngagementDistribution:
      type: object
      required: [total_contacts, average_score, tiers]
      properties:
        total_contacts: { type: integer }
        average_score: { type: number }
        tiers:
          type: object
          properties:
            engaged: { $ref: "#/components/schemas/TierStats" }
            active: { $ref: "#/components/schemas/TierStats" }
            cooling: { $ref: "#/components/schemas/TierStats" }
            dormant: { $ref: "#/components/schemas/TierStats" }
            new: { $ref: "#/components/schemas/TierStats" }

    SunsetPolicy:
      type: object
      required: [account_id, enabled, dormant_days, min_sent, action, tag_name, auto_run, last_processed_count, created_at, updated_at]
      properties:
        account_id: { type: string }
        enabled: { type: boolean }
        dormant_days: { type: integer, example: 90 }
        min_sent: { type: integer, example: 5 }
        action: { type: string, enum: [unsubscribe, tag] }
        tag_name: { type: string, example: "dormant" }
        auto_run: { type: boolean }
        last_run_at: { type: [string, "null"], format: date-time }
        last_processed_count: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SunsetPolicyInput:
      type: object
      properties:
        enabled: { type: boolean }
        dormant_days: { type: integer, minimum: 14, maximum: 730 }
        min_sent: { type: integer, minimum: 1, maximum: 100 }
        action: { type: string, enum: [unsubscribe, tag] }
        tag_name: { type: string }
        auto_run: { type: boolean }

    SunsetPreviewResult:
      type: object
      required: [dormant_count, sample_emails]
      properties:
        dormant_count: { type: integer }
        sample_emails:
          type: array
          items: { type: string }

    SunsetRunResult:
      type: object
      required: [processed_count, action, executed_at]
      properties:
        processed_count: { type: integer }
        action: { type: string }
        executed_at: { type: string, format: date-time }

    # ---------------- Email Client Preview & Dark Mode (EDT-05) ----------------
    ClientPreviewInput:
      type: object
      required: [html]
      properties:
        html:
          type: string
        subject:
          type: string

    EmailClient:
      type: object
      required: [id, name, category, platform, engine, market_share_pct, dark_mode_support]
      properties:
        id: { type: string }
        name: { type: string }
        category: { type: string, enum: [mobile, desktop, web] }
        platform: { type: string }
        engine: { type: string }
        market_share_pct: { type: number }
        dark_mode_support: { type: string, enum: [native, partial_invert, full_invert, none] }
        icon: { type: string }
        description: { type: string }

    ClientIssue:
      type: object
      required: [id, severity, category, title, description, affected_clients, recommendation]
      properties:
        id: { type: string }
        severity: { type: string, enum: [error, warning, tip] }
        category: { type: string, enum: [outlook, gmail, dark_mode, responsive, fonts] }
        title: { type: string }
        description: { type: string }
        affected_clients:
          type: array
          items: { type: string }
        recommendation: { type: string }

    ClientPreviewReport:
      type: object
      required: [overall_score, dark_mode_score, html_size_bytes, style_size_bytes, is_gmail_clipped, summary, issues, clients, transformed_html]
      properties:
        overall_score: { type: integer }
        dark_mode_score: { type: integer }
        html_size_bytes: { type: integer }
        style_size_bytes: { type: integer }
        is_gmail_clipped: { type: boolean }
        summary:
          type: object
          properties:
            errors: { type: integer }
            warnings: { type: integer }
            tips: { type: integer }
        issues:
          type: array
          items:
            $ref: "#/components/schemas/ClientIssue"
        clients:
          type: array
          items:
            $ref: "#/components/schemas/EmailClient"
        transformed_html:
          type: object
          additionalProperties:
            type: string

    CountryStat:
      type: object
      required: [country_code, country_name, opens, clicks, unique_users, percentage]
      properties:
        country_code: { type: string }
        country_name: { type: string }
        opens: { type: integer }
        clicks: { type: integer }
        unique_users: { type: integer }
        percentage: { type: number }

    CityStat:
      type: object
      required: [city, country_code, country_name, opens, clicks, percentage]
      properties:
        city: { type: string }
        country_code: { type: string }
        country_name: { type: string }
        opens: { type: integer }
        clicks: { type: integer }
        percentage: { type: number }

    CampaignGeoReport:
      type: object
      required: [campaign_id, total_events, total_opens, total_clicks, countries, cities]
      properties:
        campaign_id: { type: string }
        total_events: { type: integer }
        total_opens: { type: integer }
        total_clicks: { type: integer }
        countries:
          type: array
          items:
            $ref: "#/components/schemas/CountryStat"
        cities:
          type: array
          items:
            $ref: "#/components/schemas/CityStat"

    DeviceStat:
      type: object
      required: [device_type, count, percentage, clean_count, machine_count]
      properties:
        device_type: { type: string }
        count: { type: integer }
        percentage: { type: number }
        clean_count: { type: integer }
        machine_count: { type: integer }

    OSStat:
      type: object
      required: [name, count, percentage]
      properties:
        name: { type: string }
        count: { type: integer }
        percentage: { type: number }

    ClientStat:
      type: object
      required: [name, count, percentage]
      properties:
        name: { type: string }
        count: { type: integer }
        percentage: { type: number }

    CampaignDeviceReport:
      type: object
      required: [campaign_id, total_events, devices, operating_systems, email_clients]
      properties:
        campaign_id: { type: string }
        total_events: { type: integer }
        devices:
          type: array
          items:
            $ref: "#/components/schemas/DeviceStat"
        operating_systems:
          type: array
          items:
            $ref: "#/components/schemas/OSStat"
        email_clients:
          type: array
          items:
            $ref: "#/components/schemas/ClientStat"

    Conversion:
      type: object
      required: [id, account_id, order_id, contact_email, amount, currency, items_count, status, attribution_type, attributed_at, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        campaign_id: { type: [string, "null"] }
        message_id: { type: [string, "null"] }
        order_id: { type: string }
        contact_email: { type: string, format: email }
        amount: { type: number }
        currency: { type: string, default: "RUB" }
        items_count: { type: integer, default: 1 }
        status: { type: string, enum: [completed, paid, pending, cancelled] }
        attribution_type: { type: string, enum: [last_click, last_open, campaign_tag, manual] }
        attributed_at: { type: string, format: date-time }
        metadata: { type: object, additionalProperties: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateConversionInput:
      type: object
      required: [order_id, contact_email, amount]
      properties:
        order_id: { type: string }
        campaign_id: { type: string }
        message_id: { type: string }
        contact_email: { type: string, format: email }
        amount: { type: number, minimum: 0 }
        currency: { type: string, default: "RUB" }
        items_count: { type: integer, minimum: 1, default: 1 }
        status: { type: string, enum: [completed, paid, pending, cancelled], default: "completed" }
        attribution_type: { type: string }
        metadata: { type: object, additionalProperties: true }

    CampaignConversionsReport:
      type: object
      required: [campaign_id, total_revenue, currency, orders_count, average_order_value, conversion_rate, revenue_per_email, recent_orders]
      properties:
        campaign_id: { type: string }
        total_revenue: { type: number }
        currency: { type: string }
        orders_count: { type: integer }
        average_order_value: { type: number }
        conversion_rate: { type: number }
        revenue_per_email: { type: number }
        recent_orders:
          type: array
          items:
            $ref: "#/components/schemas/Conversion"

    CompareCampaignsInput:
      type: object
      required: [campaign_ids]
      properties:
        campaign_ids:
          type: array
          minItems: 1
          maxItems: 5
          items:
            type: string

    CampaignsComparisonItem:
      type: object
      required: [campaign_id, name, subject, status, total_recipients, delivered, bounced, bounce_rate, opens_total, opens_unique, clean_opens, open_rate, clean_open_rate, clicks_total, clicks_unique, clean_clicks, click_rate, clean_click_rate, ctor, unsubscribes, unsubscribe_rate, complaints, complaint_rate, desktop_pct, mobile_pct, total_revenue, orders_count, average_order_value, conversion_rate, revenue_per_email, is_best_open_rate, is_best_click_rate, is_best_ctor, is_lowest_bounce, is_best_revenue]
      properties:
        campaign_id: { type: string }
        name: { type: string }
        subject: { type: string }
        status: { type: string }
        started_at: { type: [string, "null"], format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
        total_recipients: { type: integer }
        delivered: { type: integer }
        bounced: { type: integer }
        bounce_rate: { type: number }
        opens_total: { type: integer }
        opens_unique: { type: integer }
        clean_opens: { type: integer }
        open_rate: { type: number }
        clean_open_rate: { type: number }
        clicks_total: { type: integer }
        clicks_unique: { type: integer }
        clean_clicks: { type: integer }
        click_rate: { type: number }
        clean_click_rate: { type: number }
        ctor: { type: number }
        unsubscribes: { type: integer }
        unsubscribe_rate: { type: number }
        complaints: { type: integer }
        complaint_rate: { type: number }
        desktop_pct: { type: number }
        mobile_pct: { type: number }
        total_revenue: { type: number }
        orders_count: { type: integer }
        average_order_value: { type: number }
        conversion_rate: { type: number }
        revenue_per_email: { type: number }
        is_best_open_rate: { type: boolean }
        is_best_click_rate: { type: boolean }
        is_best_ctor: { type: boolean }
        is_lowest_bounce: { type: boolean }
        is_best_revenue: { type: boolean }

    CampaignsComparisonReport:
      type: object
      required: [campaigns, compared_at]
      properties:
        campaigns:
          type: array
          items:
            $ref: "#/components/schemas/CampaignsComparisonItem"
        compared_at: { type: string, format: date-time }

    ScheduledReport:
      type: object
      required: [id, account_id, name, report_type, frequency, recipients, format, status, next_run_at, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        report_type: { type: string, enum: [campaign_summary, account_performance, deliverability_digest, ecommerce_revenue] }
        frequency: { type: string, enum: [daily, weekly, monthly] }
        recipients: { type: array, items: { type: string, format: email } }
        format: { type: string, enum: [html, csv, xlsx, both] }
        campaign_id: { type: [string, "null"] }
        status: { type: string, enum: [active, paused] }
        last_run_at: { type: [string, "null"], format: date-time }
        next_run_at: { type: string, format: date-time }
        last_error: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    ScheduledReportInput:
      type: object
      required: [name, report_type, frequency, recipients]
      properties:
        name: { type: string }
        report_type: { type: string, enum: [campaign_summary, account_performance, deliverability_digest, ecommerce_revenue] }
        frequency: { type: string, enum: [daily, weekly, monthly] }
        recipients: { type: array, items: { type: string, format: email } }
        format: { type: string, enum: [html, csv, xlsx, both], default: both }
        campaign_id: { type: [string, "null"] }
        status: { type: string, enum: [active, paused], default: active }

    AnalyticsExport:
      type: object
      required: [id, account_id, name, export_type, format, status, row_count, file_size_bytes, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        campaign_id: { type: [string, "null"] }
        name: { type: string }
        export_type: { type: string, enum: [campaign_events, campaigns_overview, conversions, deliverability] }
        format: { type: string, enum: [csv, xlsx] }
        status: { type: string, enum: [pending, completed, failed] }
        row_count: { type: integer }
        file_size_bytes: { type: integer }
        download_url: { type: string }
        created_at: { type: string, format: date-time }

    ExportRequest:
      type: object
      required: [export_type, format]
      properties:
        name: { type: string }
        export_type: { type: string, enum: [campaign_events, campaigns_overview, conversions, deliverability] }
        format: { type: string, enum: [csv, xlsx], default: csv }
        campaign_id: { type: string }
        date_from: { type: string, format: date-time }
        date_to: { type: string, format: date-time }

    ContactProfile:
      type: object
      required: [id, list_id, email, active, created_at, lists, stats]
      properties:
        id: { type: string }
        account_id: { type: string }
        list_id: { type: string }
        email: { type: string, format: email }
        phone: { type: string }
        tags: { type: array, items: { type: string } }
        attributes: { type: object, additionalProperties: true }
        active: { type: boolean }
        unsubscribed_at: { type: [string, "null"], format: date-time }
        consent_source: { type: string }
        consent_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        lists:
          type: array
          description: Contact lists this email address belongs to
          items:
            type: object
            required: [id, name]
            properties:
              id: { type: string }
              name: { type: string }
              created_at: { type: string, format: date-time }
        stats:
          $ref: "#/components/schemas/ContactStats"

    ContactStats:
      type: object
      required: [sent, delivered, opened, clicked, bounced, complained, unsubscribed]
      properties:
        sent: { type: integer, description: Total sent messages }
        delivered: { type: integer, description: Successfully delivered messages }
        opened: { type: integer, description: Unique opens }
        clicked: { type: integer, description: Unique clicks }
        bounced: { type: integer, description: Bounces recorded }
        complained: { type: integer, description: Spam complaints }
        unsubscribed: { type: integer, description: Unsubscribes }

    ContactSTOProfile:
      type: object
      required: [contact_id, account_id, optimal_hour_utc, optimal_day_of_week, confidence_score, total_signals, hourly_distribution, daily_distribution, source, calculated_at]
      properties:
        contact_id: { type: string }
        account_id: { type: string }
        email: { type: string }
        optimal_hour_utc:
          type: integer
          minimum: 0
          maximum: 23
          description: "Optimal hour of day in UTC (0-23)"
        optimal_hour_local:
          type: integer
          minimum: 0
          maximum: 23
          description: "Optimal hour in contact local timezone (0-23)"
        optimal_day_of_week:
          type: integer
          minimum: 1
          maximum: 7
          description: "Optimal day of week (1=Monday, 7=Sunday)"
        confidence_score:
          type: number
          format: float
          minimum: 0.0
          maximum: 1.0
          description: "Confidence in optimal send time (0.00-1.00)"
        total_signals:
          type: integer
          description: "Total recorded opens and clicks used in calculation"
        hourly_distribution:
          type: array
          items: { type: integer }
          description: "24-element array of activity counts per UTC hour (00:00 to 23:00)"
        daily_distribution:
          type: array
          items: { type: integer }
          description: "7-element array of activity counts per day of week (Monday to Sunday)"
        source:
          type: string
          enum: [history, inferred_tz, default]
          description: "How optimal send time was determined"
        timezone:
          type: string
          description: "Inferred or configured timezone"
        next_recommended_send_at:
          type: string
          format: date-time
          description: "Next optimal dispatch timestamp"
        calculated_at:
          type: string
          format: date-time

    ContactPatchInput:
      type: object
      properties:
        phone: { type: string }
        tags: { type: array, items: { type: string } }
        attributes: { type: object, additionalProperties: true }
        active: { type: boolean }
        consent_source: { type: string }

    EmailValidationResult:
      type: object
      required: [email, status, score, domain, syntax_valid, has_mx, is_disposable, is_role_based, is_free_mail]
      properties:
        email: { type: string }
        normalized: { type: string }
        status:
          type: string
          enum: [valid, risky, invalid]
        score:
          type: number
          format: float
          description: Quality score from 0.0 (invalid) to 1.0 (clean)
        domain: { type: string }
        local_part: { type: string }
        syntax_valid: { type: boolean }
        has_mx: { type: boolean }
        mx_records:
          type: array
          items: { type: string }
        is_disposable: { type: boolean }
        is_role_based: { type: boolean }
        is_free_mail: { type: boolean }
        reasons:
          type: array
          items: { type: string }

    ContactValidationResponse:
      type: object
      required: [results]
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/EmailValidationResult"

    ListValidationReport:
      type: object
      required: [list_id, total_contacts, summary, breakdown, validated_at]
      properties:
        list_id: { type: string }
        total_contacts: { type: integer }
        summary:
          type: object
          required: [valid, risky, invalid]
          properties:
            valid: { type: integer }
            risky: { type: integer }
            invalid: { type: integer }
        breakdown:
          type: object
          required: [syntax_errors, no_mx, disposable, role_based, free_mail]
          properties:
            syntax_errors: { type: integer }
            no_mx: { type: integer }
            disposable: { type: integer }
            role_based: { type: integer }
            free_mail: { type: integer }
        sample_issues:
          type: array
          items:
            $ref: "#/components/schemas/EmailValidationResult"
        validated_at:
          type: string
          format: date-time

    ImportJob:
      type: object
      required: [id, list_id, status, started_at]
      properties:
        id: { type: string }
        list_id: { type: string }
        status: { type: string, enum: [processing, completed, failed] }
        filename: { type: string }
        total_rows: { type: integer }
        imported_rows: { type: integer }
        updated_rows: { type: integer }
        invalid_rows: { type: integer }
        error_summary: { type: object, additionalProperties: true }
        started_at: { type: string, format: date-time }
        finished_at: { type: [string, "null"], format: date-time }

    CampaignStatus:
      type: string
      description: |
        - `draft` — created, not started;
        - `under_review` — pending moderation by operator (first campaigns of new accounts, ABS-03);
        - `scheduled` — waiting for `send_at` / a free engine worker;
        - `sending` — in progress (engine holds a lease, renewed every batch);
        - `paused` — paused by the user; `paused_by_system` — auto-paused (bounce/complaint threshold);
        - `blocked` — rejected by anti-spam check or operator moderation;
        - final: `completed` (all contacts processed), `failed` (nothing sent and errors, or a fatal error
          such as an unverified sender domain), `cancelled`.
      enum: [draft, under_review, scheduled, sending, paused, paused_by_system, blocked, completed, failed, cancelled]

    CampaignInput:
      type: object
      required: [list_id, name, subject, html, from_email, from_name]
      properties:
        list_id: { type: string }
        name: { type: string, minLength: 1, maxLength: 200 }
        subject: { type: string, minLength: 1, maxLength: 998 }
        html: { type: string, minLength: 1, description: "Supports `{{variable}}` merge tags from contact attributes" }
        text: { type: string }
        amp_html: { type: string, description: "AMP for Email interactive markup (RFC 822 text/x-amp-html part placed after text/html)" }
        from_email: { type: string, format: email }
        from_name: { type: string }
        send_at: { type: [string, "null"], format: date-time, description: Start not earlier than this time }
        rate_limit_per_hour: { type: integer, minimum: 0, default: 0, description: "Max messages per hour (0 = unlimited)" }
        segment_id: { type: [string, "null"], description: "Optional segment within list_id to send only to matching contacts (CNT-05)" }
        utm_params: { $ref: "#/components/schemas/UTMParams" }
        base_source:
          type: string
          enum: [website_optin, crm_clients, event_participants, partner_co_marketing, imported_other]
          description: "Source of contact base for moderation check (ABS-03)"
        base_source_details:
          type: string
          description: "Details regarding user opt-in / consent collection method (ABS-03)"
        is_advertisement:
          type: boolean
          default: false
          description: "Whether this campaign contains third-party advertising subject to 38-FZ / ORD marking (LAW-06)"
        ord_erid:
          type: string
          description: "ORD advertising token (erid)"
        ord_advertiser:
          type: string
          description: "Advertiser legal name and tax ID / INN"
        ord_banner_placement:
          type: string
          enum: [footer, header, manual]
          default: footer
          description: "Placement of the mandatory advertising disclaimer banner"
        is_ab_test:
          type: boolean
          default: false
          description: "Whether this campaign uses A/B split testing (CMP-06)"
        sto_enabled:
          type: boolean
          default: false
          description: "Send Time Optimization: dispatch each recipient's email at their personal peak open hour (CMP-08)"
        sto_window_hours:
          type: integer
          minimum: 1
          maximum: 72
          default: 24
          description: "Delivery window in hours for STO distribution (default: 24)"
        auto_resend:
          $ref: "#/components/schemas/AutoResendConfig"

    AutoResendConfig:
      type: object
      properties:
        enabled:
          type: boolean
          description: "Whether to automatically resend to unopeners after campaign completion (CMP-07)"
        subject:
          type: string
          description: "Alternative subject line for the follow-up resend"
        wait_hours:
          type: integer
          minimum: 1
          maximum: 168
          default: 48
          description: "Delay in hours after main campaign finishes before auto-resending"

    Campaign:
      allOf:
        - $ref: "#/components/schemas/CampaignInput"
        - type: object
          required: [id, status, sandbox, stats, created_at]
          properties:
            id: { type: string }
            account_id: { type: string }
            status: { $ref: "#/components/schemas/CampaignStatus" }
            sandbox: { type: boolean, description: Inherited from the key that created the campaign (test key → true) }
            stats:
              type: object
              required: [sent, failed, skipped]
              properties:
                sent: { type: integer, description: Messages accepted for delivery }
                failed: { type: integer, description: Messages rejected at submission }
                skipped: { type: integer, description: Inactive/unsubscribed or invalid contacts }
            last_error: { type: [string, "null"] }
            spam_score: { type: number, description: "Spam risk score 0.00-10.00" }
            review_comment: { type: [string, "null"], description: "Operator feedback/reason if rejected or reviewed" }
            reviewed_at: { type: [string, "null"], format: date-time }
            reviewed_by: { type: [string, "null"] }
            is_ab_test: { type: boolean }
            ab_phase: { type: string, enum: [testing, evaluating, sending_winner, finished] }
            sto_enabled: { type: boolean }
            sto_window_hours: { type: integer }
            parent_campaign_id: { type: [string, "null"] }
            resend_filter: { type: [string, "null"], enum: [unopened] }
            started_at: { type: [string, "null"], format: date-time }
            finished_at: { type: [string, "null"], format: date-time }
            created_at: { type: string, format: date-time }

    CampaignPatchInput:
      type: object
      properties:
        name: { type: string, minLength: 1, maxLength: 200 }
        subject: { type: string, minLength: 1, maxLength: 998 }
        html: { type: string, minLength: 1 }
        text: { type: string }
        amp_html: { type: string, description: "AMP for Email interactive markup" }
        from_email: { type: string, format: email }
        from_name: { type: string }
        send_at: { type: [string, "null"], format: date-time, description: "Start not earlier than this time (null clears schedule)" }
        rate_limit_per_hour: { type: integer, minimum: 0 }
        segment_id: { type: [string, "null"] }
        utm_params: { $ref: "#/components/schemas/UTMParams" }
        base_source:
          type: string
          enum: [website_optin, crm_clients, event_participants, partner_co_marketing, imported_other]
        base_source_details: { type: string }
        is_advertisement: { type: boolean }
        ord_erid: { type: string }
        ord_advertiser: { type: string }
        ord_banner_placement: { type: string, enum: [footer, header, manual] }
        is_ab_test: { type: boolean }
        sto_enabled: { type: boolean }
        sto_window_hours: { type: integer, minimum: 1, maximum: 72 }
        auto_resend: { $ref: "#/components/schemas/AutoResendConfig" }
        rss_feed_id: { type: [string, "null"], description: "Optional link to the generating RSS feed campaign (CMP-09)" }

    # ---------------- RSS-to-Email / Automated Digests (CMP-09) Schemas ----------------
    RSSFeed:
      type: object
      required: [id, account_id, name, feed_url, list_id, from_email, from_name, subject_template, template_html, schedule_type, schedule_time, status, created_at, updated_at]
      properties:
        id: { type: string, example: "01J9X4T7M8K1N2P3Q4R5S6T7U8" }
        account_id: { type: string }
        name: { type: string, example: "Еженедельный дайджест блога" }
        feed_url: { type: string, format: uri, example: "https://habr.com/ru/rss/all/" }
        list_id: { type: string }
        segment_id: { type: [string, "null"] }
        from_email: { type: string, format: email, example: "digest@company.ru" }
        from_name: { type: string, example: "Редакция Блога" }
        subject_template: { type: string, example: "Дайджест: {{feed.title}} ({{items_count}} новых публикаций)" }
        template_html: { type: string }
        template_text: { type: string }
        schedule_type: { type: string, enum: [daily, weekly, interval, realtime], example: "weekly" }
        schedule_time: { type: string, example: "10:00" }
        schedule_days:
          type: array
          items: { type: integer, minimum: 1, maximum: 7 }
          example: [1, 4]
        check_interval_minutes: { type: integer, default: 60, example: 120 }
        min_items: { type: integer, default: 1, example: 2 }
        max_items: { type: integer, default: 10, example: 5 }
        status: { type: string, enum: [active, paused, error], example: "active" }
        last_error: { type: [string, "null"] }
        last_polled_at: { type: [string, "null"], format: date-time }
        last_sent_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    RSSFeedInput:
      type: object
      required: [name, feed_url, list_id, from_email, from_name]
      properties:
        name: { type: string, minLength: 1, maxLength: 200 }
        feed_url: { type: string, format: uri }
        list_id: { type: string }
        segment_id: { type: string }
        from_email: { type: string, format: email }
        from_name: { type: string }
        subject_template: { type: string, default: "Дайджест: {{feed.title}} ({{items_count}} новых публикаций)" }
        template_html: { type: string }
        template_text: { type: string }
        schedule_type: { type: string, enum: [daily, weekly, interval, realtime], default: "daily" }
        schedule_time: { type: string, default: "09:00" }
        schedule_days: { type: array, items: { type: integer, minimum: 1, maximum: 7 }, default: [1, 2, 3, 4, 5] }
        check_interval_minutes: { type: integer, default: 60 }
        min_items: { type: integer, default: 1 }
        max_items: { type: integer, default: 10 }

    RSSFeedPatchInput:
      type: object
      properties:
        name: { type: string }
        feed_url: { type: string, format: uri }
        list_id: { type: string }
        segment_id: { type: [string, "null"] }
        from_email: { type: string, format: email }
        from_name: { type: string }
        subject_template: { type: string }
        template_html: { type: string }
        template_text: { type: string }
        schedule_type: { type: string, enum: [daily, weekly, interval, realtime] }
        schedule_time: { type: string }
        schedule_days: { type: array, items: { type: integer, minimum: 1, maximum: 7 } }
        check_interval_minutes: { type: integer }
        min_items: { type: integer }
        max_items: { type: integer }

    RSSFeedItem:
      type: object
      required: [guid, title, link]
      properties:
        guid: { type: string }
        title: { type: string }
        link: { type: string, format: uri }
        description: { type: string }
        content: { type: string }
        author: { type: string }
        published_at: { type: string, format: date-time }
        image_url: { type: string, format: uri }

    RSSFeedValidationResult:
      type: object
      required: [valid, feed_type, items_count]
      properties:
        valid: { type: boolean }
        feed_type: { type: string, enum: [rss_2_0, atom_1_0, unknown] }
        title: { type: string }
        description: { type: string }
        site_url: { type: string }
        items_count: { type: integer }
        sample_items:
          type: array
          items: { $ref: "#/components/schemas/RSSFeedItem" }
        error: { type: string }

    RSSFeedRun:
      type: object
      required: [id, feed_id, status, items_found, items_processed, run_at]
      properties:
        id: { type: string }
        feed_id: { type: string }
        status: { type: string, enum: [completed, skipped_no_items, error] }
        items_found: { type: integer }
        items_processed: { type: integer }
        campaign_id: { type: [string, "null"] }
        error_message: { type: [string, "null"] }
        run_at: { type: string, format: date-time }

    RSSFeedRunResult:
      type: object
      required: [feed_id, status, items_found, new_items_count]
      properties:
        feed_id: { type: string }
        status: { type: string, enum: [completed, skipped_no_items, error] }
        items_found: { type: integer }
        new_items_count: { type: integer }
        campaign_id: { type: [string, "null"] }
        preview_subject: { type: string }
        error: { type: string }

    ABVariantInput:
      type: object
      required: [letter, subject, from_name, from_email]
      properties:
        letter: { type: string, example: "A" }
        name: { type: string, example: "Вариант А" }
        subject: { type: string, minLength: 1 }
        from_name: { type: string }
        from_email: { type: string, format: email }
        html: { type: string }
        text: { type: string }

    ABVariant:
      type: object
      required: [id, campaign_id, letter, subject, from_name, from_email, sent_count, opened_count, clicked_count, open_rate, click_rate, created_at]
      properties:
        id: { type: string }
        campaign_id: { type: string }
        letter: { type: string }
        name: { type: string }
        subject: { type: string }
        from_name: { type: string }
        from_email: { type: string }
        html: { type: string }
        text: { type: string }
        sent_count: { type: integer }
        opened_count: { type: integer }
        clicked_count: { type: integer }
        open_rate: { type: number, description: "Open rate percentage (0.0 - 100.0)" }
        click_rate: { type: number, description: "Click rate percentage (0.0 - 100.0)" }
        created_at: { type: string, format: date-time }

    ABTestInput:
      type: object
      required: [variants]
      properties:
        metric:
          type: string
          enum: [open_rate, click_rate]
          default: open_rate
        sample_percent:
          type: integer
          minimum: 10
          maximum: 100
          default: 20
        wait_hours:
          type: integer
          minimum: 1
          maximum: 72
          default: 4
        variants:
          type: array
          minItems: 2
          maxItems: 5
          items:
            $ref: "#/components/schemas/ABVariantInput"

    ABTest:
      type: object
      required: [campaign_id, status, metric, sample_percent, wait_hours, variants, created_at, updated_at]
      properties:
        campaign_id: { type: string }
        status:
          type: string
          enum: [draft, running, evaluating, completed]
        metric:
          type: string
          enum: [open_rate, click_rate]
        sample_percent: { type: integer }
        wait_hours: { type: integer }
        winner_variant: { type: [string, "null"] }
        winner_selected_at: { type: [string, "null"], format: date-time }
        winner_selection_mode: { type: [string, "null"], enum: [auto, manual] }
        eval_due_at: { type: [string, "null"], format: date-time }
        variants:
          type: array
          items:
            $ref: "#/components/schemas/ABVariant"
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    ChooseWinnerInput:
      type: object
      required: [variant]
      properties:
        variant:
          type: string
          example: "A"

    ResendUnopenedInput:
      type: object
      required: [subject]
      properties:
        subject: { type: string, minLength: 1 }
        from_name: { type: string }
        from_email: { type: string, format: email }
        send_at: { type: string, format: date-time }

    PreflightItemStatus:
      type: string
      enum: [passed, warning, failed]

    PreflightItem:
      type: object
      required: [code, name, status, message]
      properties:
        code:
          type: string
          example: unsubscribe_link
        name:
          type: string
          example: Ссылка отписки
        status:
          $ref: "#/components/schemas/PreflightItemStatus"
        message:
          type: string
          example: В теле письма найдена ссылка отписки
        details:
          type: object
          additionalProperties: true

    PreflightReport:
      type: object
      required: [ready, items]
      properties:
        ready:
          type: boolean
          description: True if there are no blocking failed checks
        items:
          type: array
          items:
            $ref: "#/components/schemas/PreflightItem"

    AntiSpamScoreRequest:
      type: object
      required: [subject, html]
      properties:
        subject:
          type: string
          example: "Специальное предложение недели"
        html:
          type: string
          example: "<div><p>Здравствуйте! Ознакомьтесь с новинками каталога.</p></div>"
        text:
          type: string
        from_email:
          type: string
          format: email
        check_urls:
          type: boolean
          default: true
          description: Perform DNSBL lookups (Spamhaus DBL, SURBL) on embedded links

    AntiSpamMatchedRule:
      type: object
      required: [code, name, score]
      properties:
        code:
          type: string
          example: CAPS_SUBJECT
        name:
          type: string
          example: Избыточный Caps Lock в теме письма
        description:
          type: string
          example: 60% букв в теме написаны заглавными
        score:
          type: number
          format: float
          example: 1.5
        details:
          type: string

    AntiSpamBlacklistedURL:
      type: object
      required: [url, domain, source, reason]
      properties:
        url:
          type: string
          example: "http://spamsite.org"
        domain:
          type: string
          example: "spamsite.org"
        source:
          type: string
          example: "Spamhaus DBL"
        reason:
          type: string
          example: "Домен классифицирован Spamhaus DBL как спам-домен"

    AntiSpamScoreResponse:
      type: object
      required: [score, threshold, is_spam, status, matched_rules, blacklisted_urls, recommendations]
      properties:
        score:
          type: number
          format: float
          description: Overall spam score (higher means higher spam probability)
          example: 1.5
        threshold:
          type: number
          format: float
          description: Blocking score threshold (default 5.0)
          example: 5.0
        is_spam:
          type: boolean
          description: True if message score >= threshold or links are in threat blacklists
        status:
          type: string
          enum: [clean, warning, spam]
          example: clean
        reason:
          type: string
          description: Explanation if marked as spam
        matched_rules:
          type: array
          items:
            $ref: "#/components/schemas/AntiSpamMatchedRule"
        blacklisted_urls:
          type: array
          items:
            $ref: "#/components/schemas/AntiSpamBlacklistedURL"
        recommendations:
          type: array
          items:
            type: string
          description: Actionable tips to improve deliverability and lower spam score

    KYCStatus:
      type: object
      required: [account_id, status, phone_verified, company_verified, website_verified, requirements_met, total_requirements]
      properties:
        account_id:
          type: string
        status:
          type: string
          enum: [unverified, pending, verified, rejected]
          example: verified
        phone:
          type: string
          example: "+79991234567"
        phone_verified:
          type: boolean
        phone_verified_at:
          type: string
          format: date-time
        company_inn:
          type: string
          example: "7707083893"
        company_name:
          type: string
          example: "ООО «Технологии Рассылок»"
        company_type:
          type: string
          enum: [legal_entity, individual]
          example: legal_entity
        company_verified:
          type: boolean
        company_verified_at:
          type: string
          format: date-time
        website_url:
          type: string
          example: "https://example.com"
        website_verified:
          type: boolean
        website_verified_at:
          type: string
          format: date-time
        kyc_verified_at:
          type: string
          format: date-time
        kyc_notes:
          type: string
        requirements_met:
          type: integer
          example: 3
        total_requirements:
          type: integer
          example: 3

    KYCVerifyCompanyRequest:
      type: object
      required: [inn]
      properties:
        inn:
          type: string
          example: "7707083893"
          description: 10-digit (legal entity) or 12-digit (individual entrepreneur) Russian INN
        company_name:
          type: string
          example: "ООО «Технологии Рассылок»"
        kpp:
          type: string
          example: "770101001"
        ogrn:
          type: string
          example: "1027700132195"
        address:
          type: string

    KYCVerifyWebsiteRequest:
      type: object
      required: [website_url]
      properties:
        website_url:
          type: string
          example: "https://myshop.ru"

    KYCWebsiteVerificationResult:
      type: object
      required: [url, domain, is_reachable, http_status, has_valid_tls, matches_verified_domain, verified, message]
      properties:
        url:
          type: string
        domain:
          type: string
        is_reachable:
          type: boolean
        http_status:
          type: integer
        has_valid_tls:
          type: boolean
        title:
          type: string
        matches_verified_domain:
          type: boolean
          description: True if domain matches one of account verified sender domains
        verified:
          type: boolean
        message:
          type: string

    KYCSendPhoneCodeRequest:
      type: object
      required: [phone]
      properties:
        phone:
          type: string
          example: "+79991234567"

    KYCSendPhoneCodeResponse:
      type: object
      required: [phone, expires_at]
      properties:
        phone:
          type: string
        expires_at:
          type: string
          format: date-time
        debug_code:
          type: string
          description: Provided in test/dev environments

    KYCVerifyPhoneCodeRequest:
      type: object
      required: [code]
      properties:
        code:
          type: string
          example: "123456"

    AdminKYCUpdateRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [verified, rejected, unverified, pending]
        notes:
          type: string
          example: "Документы и сайт проверены оператором"

    UTMParams:
      type: object
      properties:
        enabled:
          type: boolean
          default: false
          description: Automatically append UTM tags to HTTP/HTTPS links
        source:
          type: string
          example: esp
          description: Campaign source (utm_source). Defaults to `esp`.
        medium:
          type: string
          example: email
          description: Campaign medium (utm_medium). Defaults to `email`.
        campaign:
          type: string
          example: summer_sale
          description: Campaign name (utm_campaign). Defaults to campaign name.
        content:
          type: string
          example: hero_button
          description: Campaign content identifier (utm_content).
        term:
          type: string
          description: Campaign search keyword or tag (utm_term).

    CampaignClickMap:
      type: object
      required: [campaign_id, total_clicks, unique_clicks, clean_clicks, clean_unique_clicks, links]
      properties:
        campaign_id:
          type: string
        total_clicks:
          type: integer
          description: Total click events across all links
        unique_clicks:
          type: integer
          description: Unique recipients who clicked at least one link
        clean_clicks:
          type: integer
          description: Total clicks excluding automated security scanners/bots
        clean_unique_clicks:
          type: integer
          description: Unique human recipients who clicked at least one link
        links:
          type: array
          items:
            $ref: "#/components/schemas/CampaignLinkStats"

    CampaignLinkStats:
      type: object
      required: [url, total_clicks, unique_clicks, clean_clicks, clean_unique_clicks, click_percentage]
      properties:
        url:
          type: string
          description: Target link URL
        total_clicks:
          type: integer
        unique_clicks:
          type: integer
        clean_clicks:
          type: integer
        clean_unique_clicks:
          type: integer
        click_percentage:
          type: number
          format: float
          description: Share of clicks on this link relative to total campaign clicks (0..100)
        first_clicked_at:
          type: [string, "null"]
          format: date-time
        last_clicked_at:
          type: [string, "null"]
          format: date-time

    QuotaInfo:
      type: object
      required: [plan, billing_status, reputation_status, warmup_tier, hourly_limit, hourly_usage, daily_limit, daily_usage, monthly_limit, monthly_usage]
      properties:
        plan: { type: string }
        billing_status: { type: string }
        reputation_status: { type: string, enum: [good, warning, restricted, suspended] }
        warmup_tier: { type: integer, description: "Warmup stage (1..4)" }
        hourly_limit: { type: integer, description: "Max messages per hour (0 = unlimited)" }
        hourly_usage: { type: integer, description: "Messages sent in current hour" }
        daily_limit: { type: integer, description: "Max messages per day (0 = unlimited)" }
        daily_usage: { type: integer, description: "Messages sent in current day" }
        monthly_limit: { type: integer, description: "Max messages per month (0 = unlimited)" }
        monthly_usage: { type: integer, description: "Messages sent in current month" }

    BillingPlan:
      type: object
      required: [id, name, price_rub, monthly_messages, max_contacts, rate_limit_rps, dedicated_ips, features]
      properties:
        id:
          type: string
          enum: [free, starter, pro, enterprise, payg]
        name:
          type: string
        price_rub:
          type: number
        monthly_messages:
          type: integer
        max_contacts:
          type: integer
        rate_limit_rps:
          type: integer
        dedicated_ips:
          type: integer
        features:
          type: array
          items: { type: string }

    CompanyRequisites:
      type: object
      required: [company_name, inn]
      properties:
        company_name: { type: string }
        inn: { type: string }
        kpp: { type: string }
        legal_address: { type: string }
        bank_name: { type: string }
        bank_bik: { type: string }
        bank_account: { type: string }
        bank_corr_account: { type: string }
        edo_operator: { type: string }
        edo_id: { type: string }

    CompanyRequisitesInput:
      type: object
      required: [company_name, inn]
      properties:
        company_name: { type: string }
        inn: { type: string }
        kpp: { type: string }
        legal_address: { type: string }
        bank_name: { type: string }
        bank_bik: { type: string }
        bank_account: { type: string }
        bank_corr_account: { type: string }
        edo_operator: { type: string }
        edo_id: { type: string }

    BillingAccountInfo:
      type: object
      required: [account_id, plan, billing_status, balance_rub, currency, auto_renew, monthly_limit, monthly_usage, contacts_count, contacts_limit]
      properties:
        account_id: { type: string }
        plan: { type: string }
        billing_status: { type: string, enum: [active, past_due, suspended] }
        balance_rub: { type: number }
        currency: { type: string, default: "RUB" }
        subscription_expires_at: { type: [string, "null"], format: date-time }
        auto_renew: { type: boolean }
        monthly_limit: { type: integer }
        monthly_usage: { type: integer }
        contacts_count: { type: integer }
        contacts_limit: { type: integer }
        company_requisites:
          anyOf:
            - $ref: "#/components/schemas/CompanyRequisites"
            - type: "null"

    TopupRequest:
      type: object
      required: [amount_rub, provider, payment_method, return_url]
      properties:
        amount_rub:
          type: number
          minimum: 100
        provider:
          type: string
          enum: [yookassa, cloudpayments]
        payment_method:
          type: string
          enum: [card, sbp, sberpay]
        return_url:
          type: string

    TopupResponse:
      type: object
      required: [payment_id, confirmation_url, status, amount_rub]
      properties:
        payment_id: { type: string }
        confirmation_url: { type: string }
        status: { type: string }
        amount_rub: { type: number }

    SubscribePlanRequest:
      type: object
      required: [plan_id, period_months, payment_method]
      properties:
        plan_id:
          type: string
          enum: [free, starter, pro, enterprise]
        period_months:
          type: integer
          enum: [1, 3, 6, 12]
        payment_method:
          type: string
          enum: [balance, yookassa, cloudpayments, bank_invoice]
        return_url:
          type: string

    SubscribePlanResponse:
      type: object
      required: [success, plan, status]
      properties:
        success: { type: boolean }
        plan: { type: string }
        status: { type: string }
        confirmation_url: { type: [string, "null"] }
        invoice_id: { type: [string, "null"] }
        message: { type: string }

    BillingInvoice:
      type: object
      required: [id, account_id, invoice_number, amount_rub, status, description, payment_method, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        invoice_number: { type: string }
        amount_rub: { type: number }
        status: { type: string, enum: [pending, paid, canceled, failed] }
        description: { type: string }
        payment_method: { type: string }
        payment_provider_id: { type: [string, "null"] }
        paid_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
        pdf_url: { type: [string, "null"] }

    CreateBankInvoiceRequest:
      type: object
      required: [amount_rub, description, company_requisites]
      properties:
        amount_rub:
          type: number
          minimum: 500
        plan_id:
          type: [string, "null"]
        description:
          type: string
        company_requisites:
          $ref: "#/components/schemas/CompanyRequisitesInput"

    PromoCodeType:
      type: string
      enum: [percentage_discount, fixed_discount, bonus_balance, bonus_credits]

    PromoCode:
      type: object
      required: [id, code, type, value, is_active]
      properties:
        id: { type: string }
        code: { type: string }
        type: { $ref: "#/components/schemas/PromoCodeType" }
        value: { type: number }
        description: { type: string }
        max_uses: { type: [integer, "null"] }
        used_count: { type: integer }
        per_account_limit: { type: integer }
        min_order_amount_rub: { type: number }
        expires_at: { type: [string, "null"], format: date-time }
        is_active: { type: boolean }
        created_at: { type: string, format: date-time }

    PromoCodeInput:
      type: object
      required: [code, type, value]
      properties:
        code: { type: string }
        type: { $ref: "#/components/schemas/PromoCodeType" }
        value: { type: number }
        description: { type: string }
        max_uses: { type: integer }
        per_account_limit: { type: integer, default: 1 }
        min_order_amount_rub: { type: number, default: 0.0 }
        expires_at: { type: string, format: date-time }
        is_active: { type: boolean, default: true }

    PromoCodeValidateInput:
      type: object
      required: [code]
      properties:
        code: { type: string }
        order_amount_rub: { type: number }

    PromoCodeValidateResult:
      type: object
      required: [valid, code]
      properties:
        valid: { type: boolean }
        code: { type: string }
        type: { $ref: "#/components/schemas/PromoCodeType" }
        value: { type: number }
        discount_amount_rub: { type: number }
        final_amount_rub: { type: number }
        bonus_balance_rub: { type: number }
        description: { type: string }
        error_message: { type: string }

    PromoCodeRedeemInput:
      type: object
      required: [code]
      properties:
        code: { type: string }

    PromoCodeRedeemResult:
      type: object
      required: [success, code, message]
      properties:
        success: { type: boolean }
        code: { type: string }
        reward_type: { $ref: "#/components/schemas/PromoCodeType" }
        reward_value: { type: number }
        new_balance_rub: { type: number }
        message: { type: string }

    ReferralProgramInfo:
      type: object
      required: [referral_code, referral_url, reward_per_referral_rub, referee_bonus_rub, total_referrals, qualified_referrals, total_earned_rub, referrals]
      properties:
        referral_code: { type: string }
        referral_url: { type: string }
        reward_per_referral_rub: { type: number }
        referee_bonus_rub: { type: number }
        total_referrals: { type: integer }
        qualified_referrals: { type: integer }
        total_earned_rub: { type: number }
        referrals:
          type: array
          items: { $ref: "#/components/schemas/ReferralItem" }

    ReferralItem:
      type: object
      required: [id, referred_account_id, status, reward_amount_rub, created_at]
      properties:
        id: { type: string }
        referred_account_id: { type: string }
        account_name: { type: string }
        status: { type: string, enum: [registered, qualified, rewarded] }
        reward_amount_rub: { type: number }
        created_at: { type: string, format: date-time }
        rewarded_at: { type: [string, "null"], format: date-time }

    PartnerTier:
      type: string
      enum: [silver, gold, platinum]

    PartnerProfile:
      type: object
      required: [account_id, tier, commission_rate, partner_code, payout_balance_rub, total_earned_rub, payout_requisites]
      properties:
        account_id: { type: string }
        tier: { $ref: "#/components/schemas/PartnerTier" }
        commission_rate: { type: number }
        partner_code: { type: string }
        payout_balance_rub: { type: number }
        total_earned_rub: { type: number }
        active_clients_count: { type: integer }
        payout_requisites: { type: object }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    PartnerProfileInput:
      type: object
      required: [payout_requisites]
      properties:
        partner_code: { type: string }
        payout_requisites: { type: object }

    PartnerClient:
      type: object
      required: [id, client_account_id, client_name, commission_rate, status, total_paid_rub, total_commission_rub, created_at]
      properties:
        id: { type: string }
        client_account_id: { type: string }
        client_name: { type: string }
        client_email: { type: string }
        commission_rate: { type: number }
        status: { type: string, enum: [active, inactive] }
        total_paid_rub: { type: number }
        total_commission_rub: { type: number }
        created_at: { type: string, format: date-time }

    PartnerCommission:
      type: object
      required: [id, client_account_id, payment_amount_rub, commission_amount_rub, rate, created_at]
      properties:
        id: { type: string }
        client_account_id: { type: string }
        client_name: { type: string }
        invoice_id: { type: string }
        payment_amount_rub: { type: number }
        commission_amount_rub: { type: number }
        rate: { type: number }
        description: { type: string }
        created_at: { type: string, format: date-time }

    PartnerPayout:
      type: object
      required: [id, amount_rub, status, payout_type, created_at]
      properties:
        id: { type: string }
        amount_rub: { type: number }
        status: { type: string, enum: [pending, approved, paid, rejected] }
        payout_type: { type: string, enum: [bank_transfer, balance_transfer] }
        requisites: { type: object }
        comment: { type: string }
        created_at: { type: string, format: date-time }
        processed_at: { type: [string, "null"], format: date-time }

    PartnerPayoutInput:
      type: object
      required: [amount_rub]
      properties:
        amount_rub: { type: number, minimum: 1000 }
        comment: { type: string }


    SegmentConditions:
      type: object
      description: Filtering rules for contacts in the list (CNT-05)
      properties:
        tags_include:
          type: array
          items: { type: string }
          description: Contact must have all of these tags
        tags_exclude:
          type: array
          items: { type: string }
          description: Contact must not have any of these tags
        status:
          type: string
          enum: [active, all, unsubscribed]
          default: active
          description: Contact subscription status
        created_within_days:
          type: integer
          minimum: 1
          description: Contact added within the last N days
        attributes:
          type: array
          items:
            type: object
            required: [field, op, value]
            properties:
              field: { type: string }
              op: { type: string, enum: [eq, neq, contains, starts_with, exists] }
              value: { type: string }

    Segment:
      type: object
      required: [id, account_id, list_id, name, conditions, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        list_id: { type: string }
        name: { type: string, minLength: 1, maxLength: 200 }
        conditions: { $ref: "#/components/schemas/SegmentConditions" }
        matching_count: { type: integer, description: "Calculated count of contacts currently matching conditions" }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SegmentInput:
      type: object
      required: [name, conditions]
      properties:
        name: { type: string, minLength: 1, maxLength: 200 }
        conditions: { $ref: "#/components/schemas/SegmentConditions" }

    SubscriptionForm:
      type: object
      required: [id, account_id, list_id, name, form_type, title, button_text, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        list_id: { type: string }
        name: { type: string, minLength: 1, maxLength: 200 }
        form_type: { type: string, enum: [embedded, popup, page], default: embedded }
        title: { type: string }
        description: { type: string }
        button_text: { type: string }
        success_message: { type: string }
        fields:
          type: array
          items:
            type: object
            required: [name, label, type]
            properties:
              name: { type: string }
              label: { type: string }
              type: { type: string, enum: [email, text, phone, telegram] }
              required: { type: boolean, default: false }
        styles:
          type: object
          properties:
            theme: { type: string }
            primary_color: { type: string }
            border_radius: { type: integer }
            font_family: { type: string }
        settings:
          type: object
          properties:
            popup_delay_sec: { type: integer }
            double_opt_in: { type: boolean, default: true }
            redirect_url: { type: string }
            consent_text: { type: string }
        confirmation_subject: { type: string }
        confirmation_html: { type: string }
        submissions_count: { type: integer, default: 0 }
        views_count: { type: integer, default: 0 }
        is_active: { type: boolean, default: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SubscriptionFormInput:
      type: object
      required: [list_id, name, title, button_text]
      properties:
        list_id: { type: string }
        name: { type: string, minLength: 1, maxLength: 200 }
        form_type: { type: string, enum: [embedded, popup, page], default: embedded }
        title: { type: string }
        description: { type: string }
        button_text: { type: string }
        success_message: { type: string }
        fields:
          type: array
          items:
            type: object
            required: [name, label, type]
            properties:
              name: { type: string }
              label: { type: string }
              type: { type: string, enum: [email, text, phone, telegram] }
              required: { type: boolean }
        styles:
          type: object
          properties:
            theme: { type: string }
            primary_color: { type: string }
            border_radius: { type: integer }
            font_family: { type: string }
        settings:
          type: object
          properties:
            popup_delay_sec: { type: integer }
            double_opt_in: { type: boolean }
            redirect_url: { type: string }
            consent_text: { type: string }
        confirmation_subject: { type: string }
        confirmation_html: { type: string }
        is_active: { type: boolean, default: true }

    FormEmbedSnippet:
      type: object
      required: [form_id, html_code, js_script_code, standalone_url]
      properties:
        form_id: { type: string }
        html_code: { type: string }
        js_script_code: { type: string }
        standalone_url: { type: string }

    ConsentRecord:
      type: object
      required: [id, form_id, email, ip_address, status, created_at]
      properties:
        id: { type: string }
        form_id: { type: string }
        contact_id: { type: [string, "null"] }
        email: { type: string }
        ip_address: { type: string }
        user_agent: { type: string }
        source_url: { type: string }
        consent_text: { type: string }
        status: { type: string, enum: [pending, confirmed, unsubscribed] }
        confirmed_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }

    PublicFormConfig:
      type: object
      required: [form_id, form_type, title, button_text, fields]
      properties:
        form_id: { type: string }
        form_type: { type: string }
        title: { type: string }
        description: { type: string }
        button_text: { type: string }
        fields: { type: array, items: { type: object } }
        styles: { type: object }
        consent_text: { type: string }

    PublicSubmitInput:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email }
        name: { type: string }
        phone: { type: string }
        telegram: { type: string, description: "Telegram username or chat handle" }
        custom_fields: { type: object, additionalProperties: true }
        consent_text: { type: string }
        source_url: { type: string }
        honeypot: { type: string, description: "Anti-bot trap field; must be empty" }

    PublicSubmitResponse:
      type: object
      required: [status, message]
      properties:
        status: { type: string, enum: [pending_confirmation, subscribed] }
        message: { type: string }
        redirect_url: { type: [string, "null"] }

    PreferenceCenterData:
      type: object
      required: [email, is_unsubscribed]
      properties:
        email: { type: string }
        is_unsubscribed: { type: boolean }
        topics: { type: array, items: { type: string } }
        frequency: { type: string, enum: [all, weekly, monthly] }

    UpdatePreferencesInput:
      type: object
      required: [token]
      properties:
        token: { type: string }
        unsubscribe_all: { type: boolean, default: false }
        topics: { type: array, items: { type: string } }
        frequency: { type: string }
        unsubscribe_reason: { type: string }

    AutomationTrigger:
      type: object
      required: [type]
      properties:
        type: { type: string, enum: [subscribe, tag_added, event, date_field, inactivity] }
        config: { type: object, additionalProperties: true }

    AskQuestionBranch:
      type: object
      required: [id, name, pattern, pattern_type]
      properties:
        id: { type: string }
        name: { type: string }
        pattern: { type: string }
        pattern_type: { type: string, enum: [regex, keywords, exact] }
        target_step_id: { type: [string, "null"] }

    AutomationStep:
      type: object
      required: [id, type]
      properties:
        id: { type: string }
        type:
          type: string
          enum:
            - action_email
            - action_max
            - action_telegram
            - action_sms
            - delay
            - condition
            - wait_for_event
            - cascade
            - goal
            - ask_question
            - ab_split
            - action_tag
            - action_move_list
            - action_webhook
        name: { type: string }
        config: { type: object, additionalProperties: true }
        next_step_id: { type: [string, "null"] }
        yes_step_id: { type: [string, "null"] }
        no_step_id: { type: [string, "null"] }
        match_step_id: { type: [string, "null"] }
        timeout_step_id: { type: [string, "null"] }
        step_a_id: { type: [string, "null"] }
        step_b_id: { type: [string, "null"] }
        fallback_step_id: { type: [string, "null"] }
        branches:
          type: array
          items: { $ref: "#/components/schemas/AskQuestionBranch" }

    AutomationStepStats:
      type: object
      required: [step_id, entered, completed, errors]
      properties:
        step_id: { type: string }
        entered: { type: integer }
        completed: { type: integer }
        errors: { type: integer }

    Automation:
      type: object
      required: [id, account_id, name, status, trigger, steps, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        description: { type: string }
        status: { type: string, enum: [draft, active, paused] }
        trigger: { $ref: "#/components/schemas/AutomationTrigger" }
        steps:
          type: array
          items: { $ref: "#/components/schemas/AutomationStep" }
        subscribers_count: { type: integer }
        completed_count: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    AutomationInput:
      type: object
      required: [name, trigger, steps]
      properties:
        name: { type: string }
        description: { type: string }
        trigger: { $ref: "#/components/schemas/AutomationTrigger" }
        steps:
          type: array
          items: { $ref: "#/components/schemas/AutomationStep" }
        is_active: { type: boolean }

    AutomationPreset:
      type: object
      required: [id, name, description, category, trigger, steps]
      properties:
        id: { type: string }
        name: { type: string }
        description: { type: string }
        category: { type: string }
        trigger: { $ref: "#/components/schemas/AutomationTrigger" }
        steps:
          type: array
          items: { $ref: "#/components/schemas/AutomationStep" }

    AutomationStatsResponse:
      type: object
      required: [automation_id, total_entered, total_completed, total_active, steps]
      properties:
        automation_id: { type: string }
        total_entered: { type: integer }
        total_completed: { type: integer }
        total_active: { type: integer }
        steps:
          type: array
          items: { $ref: "#/components/schemas/AutomationStepStats" }

    AutomationRunSummary:
      type: object
      required: [id, contact_id, status, current_step_id, created_at, updated_at]
      properties:
        id: { type: string }
        contact_id: { type: string }
        contact_email: { type: string }
        contact_phone: { type: string }
        status: { type: string }
        current_step_id: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        deadline_at: { type: [string, "null"], format: date-time }

    RunTraceStep:
      type: object
      required: [step_id, status, entered_at]
      properties:
        step_id: { type: string }
        status: { type: string, enum: [completed, current_waiting, failed, skipped] }
        branch_taken: { type: string }
        entered_at: { type: string, format: date-time }
        completed_at: { type: [string, "null"], format: date-time }
        log: { type: string }

    AutomationRunTraceResponse:
      type: object
      required: [run_id, automation_id, contact_id, status, current_step_id, steps_trace, active_step_ids, visited_edge_ids]
      properties:
        run_id: { type: string }
        automation_id: { type: string }
        contact_id: { type: string }
        contact_email: { type: string }
        contact_phone: { type: string }
        status: { type: string }
        current_step_id: { type: string }
        deadline_at: { type: [string, "null"], format: date-time }
        seconds_left: { type: integer }
        steps_trace:
          type: array
          items: { $ref: "#/components/schemas/RunTraceStep" }
        active_step_ids:
          type: array
          items: { type: string }
        visited_edge_ids:
          type: array
          items: { type: string }

    SimulatedEventInput:
      type: object
      required: [event_type, delay_seconds]
      properties:
        event_type: { type: string }
        delay_seconds: { type: integer }
        payload: { type: object, additionalProperties: true }

    AutomationSimulationInput:
      type: object
      properties:
        contact_id: { type: string }
        contact_email: { type: string }
        contact_attributes: { type: object, additionalProperties: true }
        simulated_events:
          type: array
          items: { $ref: "#/components/schemas/SimulatedEventInput" }
        simulated_replies:
          type: object
          additionalProperties: { type: string }

    SimulationStepTrace:
      type: object
      required: [step_id, step_name, step_type, virtual_offset_sec, status, log_message]
      properties:
        step_id: { type: string }
        step_name: { type: string }
        step_type: { type: string }
        branch_taken: { type: string }
        virtual_offset_sec: { type: integer }
        status: { type: string, enum: [executed, skipped, waiting, goal_reached] }
        log_message: { type: string }
        state_snapshot: { type: object, additionalProperties: true }

    AutomationSimulationResponse:
      type: object
      required: [automation_id, final_status, total_steps, total_duration_sec, steps_executed, final_context]
      properties:
        automation_id: { type: string }
        final_status: { type: string }
        total_steps: { type: integer }
        total_duration_sec: { type: integer }
        steps_executed:
          type: array
          items: { $ref: "#/components/schemas/SimulationStepTrace" }
        final_context: { type: object, additionalProperties: true }

    AIWorkflowGenerateRequest:
      type: object
      required: [prompt]
      properties:
        prompt: { type: string, description: Description of desired workflow scenario }
        industry: { type: string, description: Industry context (ecommerce, b2b, edtech, saas) }
        context: { type: string, description: Additional instructions }

    AIWorkflowRefineRequest:
      type: object
      required: [current_workflow, instruction]
      properties:
        current_workflow: { $ref: "#/components/schemas/AutomationInput" }
        instruction: { type: string, description: Conversational prompt for modifications }

    AIWorkflowValidationResult:
      type: object
      required: [valid, total_steps_count, branching_count, action_emails_count, estimated_duration, has_cycles]
      properties:
        valid: { type: boolean }
        errors: { type: array, items: { type: string } }
        warnings: { type: array, items: { type: string } }
        total_steps_count: { type: integer }
        branching_count: { type: integer }
        action_emails_count: { type: integer }
        estimated_duration: { type: string }
        has_cycles: { type: boolean }

    AIWorkflowArchitectureResponse:
      type: object
      required: [name, description, workflow, explanation, validation, mermaid_diagram]
      properties:
        id: { type: string }
        name: { type: string }
        description: { type: string }
        workflow: { $ref: "#/components/schemas/AutomationInput" }
        explanation: { type: string }
        validation: { $ref: "#/components/schemas/AIWorkflowValidationResult" }
        mermaid_diagram: { type: string }
        suggested_tags: { type: array, items: { type: string } }

    AIWorkflowActivateRequest:
      type: object
      properties:
        workflow: { $ref: "#/components/schemas/AutomationInput" }
        history_id: { type: string }
        is_active: { type: boolean, default: false }

    AIWorkflowActivateResponse:
      type: object
      required: [automation_id, name, status, steps_count, trigger_type]
      properties:
        automation_id: { type: string }
        name: { type: string }
        status: { type: string }
        steps_count: { type: integer }
        trigger_type: { type: string }

    AIWorkflowHistoryEntry:
      type: object
      required: [id, account_id, prompt, explanation, workflow, validation, mermaid_diagram, is_activated, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        prompt: { type: string }
        industry: { type: string }
        explanation: { type: string }
        workflow: { $ref: "#/components/schemas/AutomationInput" }
        validation: { $ref: "#/components/schemas/AIWorkflowValidationResult" }
        mermaid_diagram: { type: string }
        is_activated: { type: boolean }
        automation_id: { type: string }
        created_at: { type: string, format: date-time }

    AIOperatorVitality:
      type: object
      required: [overall_score, status, infrastructure_score, deliverability_score, security_score, ai_score, budget_spent_rub, budget_limit_rub, budget_usage_percent, active_incidents_count, pending_approvals_count, checked_at]
      properties:
        overall_score: { type: integer, example: 96 }
        status: { type: string, enum: [optimal, good, warning, critical] }
        infrastructure_score: { type: integer, example: 98 }
        deliverability_score: { type: integer, example: 95 }
        security_score: { type: integer, example: 99 }
        ai_score: { type: integer, example: 94 }
        budget_spent_rub: { type: number, example: 12450.00 }
        budget_limit_rub: { type: number, example: 50000.00 }
        budget_usage_percent: { type: number, example: 24.9 }
        active_incidents_count: { type: integer, example: 0 }
        pending_approvals_count: { type: integer, example: 1 }
        subsystems: { type: object, additionalProperties: true }
        checked_at: { type: string, format: date-time }

    AIOperatorFeedItem:
      type: object
      required: [id, category, severity, title, description, is_autonomous, created_at]
      properties:
        id: { type: string }
        category: { type: string, example: sre }
        severity: { type: string, enum: [info, warning, critical] }
        title: { type: string }
        description: { type: string }
        metadata: { type: object, additionalProperties: true }
        is_autonomous: { type: boolean }
        created_at: { type: string, format: date-time }

    AIOperatorApprovalItem:
      type: object
      required: [id, action_type, target_entity_id, title, description, risk_score, status, created_at]
      properties:
        id: { type: string }
        action_type: { type: string, example: provision_ip }
        target_entity_id: { type: string }
        title: { type: string }
        description: { type: string }
        risk_score: { type: integer, example: 45 }
        metadata: { type: object, additionalProperties: true }
        status: { type: string, enum: [pending, approved, rejected, expired] }
        decided_by: { type: string }
        decided_at: { type: string, format: date-time }
        rejection_reason: { type: string }
        created_at: { type: string, format: date-time }

    AIOperatorDecideRequest:
      type: object
      required: [decision]
      properties:
        decision: { type: string, enum: [approve, reject] }
        reason: { type: string }
        decided_by: { type: string, default: web_operator }

    AIOperatorConfig:
      type: object
      required: [id, notifications_enabled, min_severity_for_telegram, budget_limit_rub]
      properties:
        id: { type: string }
        telegram_bot_token: { type: string }
        telegram_chat_id: { type: string }
        notifications_enabled: { type: boolean }
        min_severity_for_telegram: { type: string, enum: [info, warning, critical] }
        auto_approval_timeout_sec: { type: integer }
        budget_limit_rub: { type: number }
        budget_spent_rub: { type: number }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    ProviderReputation:
      type: object
      required: [provider, name, accepted, delivered, bounced_hard, bounced_soft, complained, bounce_rate, complaint_rate, delivery_rate, status]
      properties:
        provider: { type: string }
        name: { type: string }
        accepted: { type: integer }
        delivered: { type: integer }
        bounced_hard: { type: integer }
        bounced_soft: { type: integer }
        complained: { type: integer }
        bounce_rate: { type: number }
        complaint_rate: { type: number }
        delivery_rate: { type: number }
        status: { type: string, enum: [good, warning, critical] }

    DeliverabilityWarning:
      type: object
      required: [level, title, message, recommendation]
      properties:
        level: { type: string, enum: [info, warning, critical] }
        provider: { type: string }
        title: { type: string }
        message: { type: string }
        recommendation: { type: string }

    DeliverabilityReputationResponse:
      type: object
      required: [score, status, period, summary, providers, warnings]
      properties:
        score: { type: integer, minimum: 0, maximum: 100 }
        status: { type: string, enum: [excellent, good, warning, poor] }
        period: { type: string }
        summary:
          type: object
          required: [accepted, delivered, bounced_hard, bounced_soft, complained, bounce_rate, complaint_rate, delivery_rate]
          properties:
            accepted: { type: integer }
            delivered: { type: integer }
            bounced_hard: { type: integer }
            bounced_soft: { type: integer }
            complained: { type: integer }
            bounce_rate: { type: number }
            complaint_rate: { type: number }
            delivery_rate: { type: number }
        providers:
          type: array
          items: { $ref: "#/components/schemas/ProviderReputation" }
        warnings:
          type: array
          items: { $ref: "#/components/schemas/DeliverabilityWarning" }
        domains_summary:
          type: object
          properties:
            total: { type: integer }
            verified: { type: integer }
            pending: { type: integer }
        blacklists_summary:
          type: object
          properties:
            total_monitored: { type: integer }
            clean: { type: integer }
            listed: { type: integer }

    BlacklistResultItem:
      type: object
      required: [rbl_name, rbl_host, is_listed, checked_at]
      properties:
        rbl_name: { type: string }
        rbl_host: { type: string }
        is_listed: { type: boolean }
        return_code: { type: string }
        details: { type: string }
        delist_url: { type: string }
        checked_at: { type: string, format: date-time }

    BlacklistMonitor:
      type: object
      required: [id, target_type, target_value, status, listed_count, total_lists]
      properties:
        id: { type: string }
        target_type: { type: string, enum: [domain, ip] }
        target_value: { type: string }
        status: { type: string, enum: [clean, listed, checking, error] }
        listed_count: { type: integer }
        total_lists: { type: integer }
        last_checked_at: { type: string, format: date-time }
        results:
          type: array
          items: { $ref: "#/components/schemas/BlacklistResultItem" }

    BlacklistsOverviewResponse:
      type: object
      required: [monitors, total_clean, total_listed]
      properties:
        monitors:
          type: array
          items: { $ref: "#/components/schemas/BlacklistMonitor" }
        total_clean: { type: integer }
        total_listed: { type: integer }
        last_check_at: { type: string, format: date-time }

    BlacklistCheckRequest:
      type: object
      properties:
        target_type: { type: string, enum: [domain, ip] }
        target_value: { type: string }

    BlacklistCheckResponse:
      type: object
      required: [status, checked_count, monitors]
      properties:
        status: { type: string }
        checked_count: { type: integer }
        monitors:
          type: array
          items: { $ref: "#/components/schemas/BlacklistMonitor" }

    TLSCheckRequest:
      type: object
      required: [domain]
      properties:
        domain:
          type: string
          description: Domain name to probe (e.g. gmail.com, yandex.ru)
          example: gmail.com
        probe_smtp:
          type: boolean
          description: Whether to connect to MX hosts and perform STARTTLS handshake probe
          default: true

    TLSCheckResponse:
      type: object
      required: [domain, overall_status, summary, mta_sts, tls_rpt, mx_hosts]
      properties:
        domain:
          type: string
          example: gmail.com
        overall_status:
          type: string
          enum: [secure, warning, insecure]
          example: secure
        summary:
          type: string
          example: "Domain enforces MTA-STS and all MX servers support TLS 1.3"
        mta_sts:
          type: object
          required: [supported, mode]
          properties:
            supported: { type: boolean, example: true }
            mode: { type: string, enum: [enforce, testing, none, missing], example: enforce }
            policy_id: { type: string, example: "20200810T000000" }
            max_age: { type: integer, example: 86400 }
            mx_patterns:
              type: array
              items: { type: string }
              example: ["*.google.com", "aspmx.l.google.com"]
            policy_url: { type: string, example: "https://mta-sts.gmail.com/.well-known/mta-sts.txt" }
            error: { type: string }
        tls_rpt:
          type: object
          required: [supported]
          properties:
            supported: { type: boolean, example: true }
            rua: { type: string, example: "mailto:tls-reports@gmail.com" }
        mx_hosts:
          type: array
          items:
            type: object
            required: [host, pref, matches_sts, starttls_supported]
            properties:
              host: { type: string, example: "gmail-smtp-in.l.google.com" }
              pref: { type: integer, example: 5 }
              matches_sts: { type: boolean, example: true }
              starttls_supported: { type: boolean, example: true }
              tls_version: { type: string, example: "TLS 1.3" }
              cipher_suite: { type: string, example: "TLS_AES_256_GCM_SHA384" }
              cert_valid: { type: boolean, example: true }
              cert_expires_at: { type: string, format: date-time, example: "2026-12-01T00:00:00Z" }
              cert_issuer: { type: string, example: "GTS CA 1C3" }
              error: { type: string }

    SeedMailbox:
      type: object
      required: [id, email, provider, active, created_at]
      properties:
        id: { type: string }
        account_id: { type: [string, "null"] }
        email: { type: string, format: email }
        provider: { type: string, enum: [gmail, yandex, mailru, outlook, rambler, other] }
        display_name: { type: string }
        active: { type: boolean }
        is_system: { type: boolean }
        created_at: { type: string, format: date-time }

    SeedMailboxInput:
      type: object
      required: [email, provider]
      properties:
        email: { type: string, format: email }
        provider: { type: string, enum: [gmail, yandex, mailru, outlook, rambler, other] }
        display_name: { type: string }
        imap_host: { type: string }
        imap_port: { type: integer, default: 993 }
        imap_username: { type: string }
        imap_password: { type: string }

    InboxPlacementTest:
      type: object
      required: [id, account_id, name, subject, from_email, tracking_tag, status, total_seeds, inbox_rate, spam_rate, overall_score, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        campaign_id: { type: [string, "null"] }
        template_id: { type: [string, "null"] }
        subject: { type: string }
        from_email: { type: string, format: email }
        from_name: { type: string }
        tracking_tag: { type: string }
        status: { type: string, enum: [pending, sending, inspecting, completed, failed] }
        total_seeds: { type: integer }
        inbox_count: { type: integer }
        promotions_count: { type: integer }
        spam_count: { type: integer }
        missing_count: { type: integer }
        inbox_rate: { type: number, format: float }
        spam_rate: { type: number, format: float }
        promotions_rate: { type: number, format: float }
        overall_score: { type: integer }
        created_at: { type: string, format: date-time }
        completed_at: { type: [string, "null"], format: date-time }

    InboxTestInput:
      type: object
      required: [subject, html, from_email]
      properties:
        name: { type: string }
        campaign_id: { type: string }
        template_id: { type: string }
        subject: { type: string }
        from_email: { type: string, format: email }
        from_name: { type: string }
        html: { type: string }
        text: { type: string }
        providers:
          type: array
          items: { type: string, enum: [gmail, yandex, mailru, outlook, rambler, other] }

    PlacementResultItem:
      type: object
      required: [id, email, provider, folder, spf_status, dkim_status, dmarc_status]
      properties:
        id: { type: string }
        seed_id: { type: [string, "null"] }
        email: { type: string, format: email }
        provider: { type: string }
        folder: { type: string, enum: [inbox, promotions, spam, missing] }
        spf_status: { type: string, enum: [pass, neutral, fail, unknown] }
        dkim_status: { type: string, enum: [pass, fail, unknown] }
        dmarc_status: { type: string, enum: [pass, fail, unknown] }
        subject_received: { type: string }
        received_at: { type: [string, "null"], format: date-time }

    ProviderPlacementStats:
      type: object
      required: [provider, display_name, total, inbox, promotions, spam, missing, inbox_rate]
      properties:
        provider: { type: string }
        display_name: { type: string }
        total: { type: integer }
        inbox: { type: integer }
        promotions: { type: integer }
        spam: { type: integer }
        missing: { type: integer }
        inbox_rate: { type: number, format: float }
        spam_rate: { type: number, format: float }
        promotions_rate: { type: number, format: float }

    DiagnosticTip:
      type: object
      required: [level, title, message, recommendation]
      properties:
        level: { type: string, enum: [critical, warning, info, success] }
        title: { type: string }
        message: { type: string }
        recommendation: { type: string }

    InboxTestReport:
      type: object
      required: [test, results, providers_stats, recommendations]
      properties:
        test: { $ref: "#/components/schemas/InboxPlacementTest" }
        results:
          type: array
          items: { $ref: "#/components/schemas/PlacementResultItem" }
        providers_stats:
          type: array
          items: { $ref: "#/components/schemas/ProviderPlacementStats" }
        recommendations:
          type: array
          items: { $ref: "#/components/schemas/DiagnosticTip" }

    PostmasterProvider:
      type: string
      enum: [gmail, yandex, mailru]

    PostmasterIntegration:
      type: object
      required: [id, account_id, provider, domain, status, auth_type, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        provider: { $ref: "#/components/schemas/PostmasterProvider" }
        domain: { type: string }
        status: { type: string, enum: [connected, pending_verification, error, syncing] }
        verification_token: { type: string }
        auth_type: { type: string, enum: [api_key, oauth_token, service_account, csv_import] }
        sync_interval_hours: { type: integer, default: 24 }
        last_synced_at: { type: [string, "null"], format: date-time }
        sync_error: { type: string }
        dns_txt_record_name: { type: string }
        dns_txt_record_value: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    PostmasterIntegrationInput:
      type: object
      required: [provider, domain]
      properties:
        provider: { $ref: "#/components/schemas/PostmasterProvider" }
        domain: { type: string }
        auth_type: { type: string, enum: [api_key, oauth_token, service_account, csv_import], default: api_key }
        credentials: { type: string }
        sync_interval_hours: { type: integer, default: 24 }

    PostmasterDailyMetric:
      type: object
      required: [id, account_id, provider, domain, date]
      properties:
        id: { type: string }
        account_id: { type: string }
        integration_id: { type: [string, "null"] }
        provider: { $ref: "#/components/schemas/PostmasterProvider" }
        domain: { type: string }
        date: { type: string, format: date }
        user_reported_spam_rate: { type: number, format: float }
        domain_reputation: { type: string }
        ip_reputation: { type: string }
        spf_success_rate: { type: number, format: float }
        dkim_success_rate: { type: number, format: float }
        dmarc_success_rate: { type: number, format: float }
        inbound_tls_rate: { type: number, format: float }
        delivery_errors_rate: { type: number, format: float }
        yandex_sent_count: { type: integer }
        yandex_delivered_count: { type: integer }
        yandex_read_count: { type: integer }
        yandex_deleted_unread_count: { type: integer }
        yandex_spam_count: { type: integer }
        yandex_spam_rate: { type: number, format: float }
        yandex_unsubscribed_count: { type: integer }
        mailru_sent_count: { type: integer }
        mailru_delivered_count: { type: integer }
        mailru_spam_percent: { type: number, format: float }
        mailru_deleted_percent: { type: number, format: float }
        mailru_reputation_trend: { type: string }
        created_at: { type: string, format: date-time }

    ProviderPostmasterHealth:
      type: object
      required: [provider, name, connected, domain, status, current_spam_rate, spam_threshold, reputation, auth_health]
      properties:
        provider: { $ref: "#/components/schemas/PostmasterProvider" }
        name: { type: string }
        connected: { type: boolean }
        domain: { type: string }
        status: { type: string }
        last_synced_at: { type: [string, "null"], format: date-time }
        current_spam_rate: { type: number, format: float }
        spam_threshold: { type: number, format: float }
        reputation: { type: string }
        auth_health: { type: string }
        summary_text: { type: string }

    PostmasterSummary:
      type: object
      required: [domain, overall_score, overall_status, providers, average_spam_rate, compliance_google_yahoo_2024, warnings, metrics_history]
      properties:
        domain: { type: string }
        overall_score: { type: integer }
        overall_status: { type: string, enum: [excellent, good, warning, critical] }
        providers:
          type: array
          items: { $ref: "#/components/schemas/ProviderPostmasterHealth" }
        average_spam_rate: { type: number, format: float }
        compliance_google_yahoo_2024: { type: boolean }
        warnings:
          type: array
          items: { $ref: "#/components/schemas/DeliverabilityWarning" }
        metrics_history:
          type: array
          items: { $ref: "#/components/schemas/PostmasterDailyMetric" }

    PostmasterSyncResult:
      type: object
      required: [integration_id, provider, domain, synced_days, status, message, synced_at]
      properties:
        integration_id: { type: string }
        provider: { $ref: "#/components/schemas/PostmasterProvider" }
        domain: { type: string }
        synced_days: { type: integer }
        status: { type: string }
        message: { type: string }
        synced_at: { type: string, format: date-time }

    PostmasterCSVImportRequest:
      type: object
      required: [provider, domain, csv_data]
      properties:
        provider: { $ref: "#/components/schemas/PostmasterProvider" }
        domain: { type: string }
        csv_data: { type: string }

    DANECheckRequest:
      type: object
      required: [domain]
      properties:
        domain: { type: string, description: "Domain name or MX hostname to inspect" }
        port: { type: integer, default: 25, description: "Port to probe (default 25)" }
        probe_smtp: { type: boolean, default: true, description: "Connect via STARTTLS to inspect certificate" }

    TLSARecord:
      type: object
      required: [usage, selector, matching_type, certificate]
      properties:
        usage: { type: integer, description: "0: PKIX-TA, 1: PKIX-EE, 2: DANE-TA, 3: DANE-EE" }
        selector: { type: integer, description: "0: Full Cert, 1: SubjectPublicKeyInfo" }
        matching_type: { type: integer, description: "0: Exact, 1: SHA-256, 2: SHA-512" }
        certificate: { type: string, description: "Certificate association data (hex)" }
        parsed_usage: { type: string }
        parsed_selector: { type: string }
        parsed_matching_type: { type: string }

    DANECertDetails:
      type: object
      properties:
        subject: { type: string }
        issuer: { type: string }
        valid_from: { type: string, format: date-time }
        valid_to: { type: string, format: date-time }
        spki_sha256: { type: string }
        cert_sha256: { type: string }
        spki_sha512: { type: string }
        cert_sha512: { type: string }

    DANECheckResponse:
      type: object
      required: [domain, mx_host, port, overall_status, summary, dnssec_status, dnssec_authenticated, tlsa_records]
      properties:
        domain: { type: string }
        mx_host: { type: string }
        port: { type: integer }
        overall_status: { type: string, enum: [valid, mismatch, no_tlsa, dnssec_invalid, probe_failed] }
        summary: { type: string }
        dnssec_status: { type: string, enum: [secure, insecure, bogus, indeterminate] }
        dnssec_authenticated: { type: boolean }
        tlsa_records:
          type: array
          items: { $ref: "#/components/schemas/TLSARecord" }
        cert_matched: { type: boolean }
        cert_details: { $ref: "#/components/schemas/DANECertDetails" }
        matched_tlsa_record: { type: string }
        recommended_tlsa: { type: string }
        warnings:
          type: array
          items: { type: string }

    TLSRPTIngestResponse:
      type: object
      required: [status, report_id, policies_processed, total_sessions, message]
      properties:
        status: { type: string }
        report_id: { type: string }
        policies_processed: { type: integer }
        total_sessions: { type: integer }
        message: { type: string }

    TLSRPTReport:
      type: object
      required: [id, account_id, report_id, organization_name, policy_domain, policy_type, total_successful_sessions, total_failure_sessions, start_datetime, end_datetime, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        report_id: { type: string }
        organization_name: { type: string }
        contact_info: { type: string }
        policy_domain: { type: string }
        policy_type: { type: string, enum: [sts, tlsa, no-policy-found] }
        total_successful_sessions: { type: integer }
        total_failure_sessions: { type: integer }
        success_rate: { type: number, format: float }
        start_datetime: { type: string, format: date-time }
        end_datetime: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    TLSRPTFailureDetail:
      type: object
      required: [result_type, failed_session_count]
      properties:
        id: { type: string }
        result_type: { type: string }
        sending_mta_ip: { type: string }
        receiving_ip: { type: string }
        receiving_mx_hostname: { type: string }
        failed_session_count: { type: integer }
        additional_information: { type: string }

    TLSRPTReportDetail:
      type: object
      required: [report, failures]
      properties:
        report: { $ref: "#/components/schemas/TLSRPTReport" }
        failures:
          type: array
          items: { $ref: "#/components/schemas/TLSRPTFailureDetail" }
        raw_json: { type: object }

    TLSRPTFailureBreakdown:
      type: object
      required: [result_type, count, percentage]
      properties:
        result_type: { type: string }
        count: { type: integer }
        percentage: { type: number, format: float }

    TLSRPTDailyMetric:
      type: object
      required: [date, successful, failed, success_rate]
      properties:
        date: { type: string }
        successful: { type: integer }
        failed: { type: integer }
        success_rate: { type: number, format: float }

    TLSRPTOverallSummary:
      type: object
      required: [total_successful_sessions, total_failure_sessions, tls_success_rate, health_status, organizations, failures_by_type, daily_metrics, suggested_policy_record]
      properties:
        domain: { type: string }
        period: { type: string }
        total_successful_sessions: { type: integer }
        total_failure_sessions: { type: integer }
        tls_success_rate: { type: number, format: float }
        health_status: { type: string, enum: [excellent, good, warning, critical] }
        organizations:
          type: array
          items: { type: string }
        failures_by_type:
          type: array
          items: { $ref: "#/components/schemas/TLSRPTFailureBreakdown" }
        daily_metrics:
          type: array
          items: { $ref: "#/components/schemas/TLSRPTDailyMetric" }
        suggested_policy_record: { type: string }

    TLSRPTSampleRequest:
      type: object
      properties:
        domain: { type: string, default: "fitonik.ru" }
        provider: { type: string, default: "Google LLC" }
        with_failures: { type: boolean, default: true }

    UserRole:
      type: string
      enum: [owner, admin, marketer, developer, analyst, billing]

    User:
      type: object
      required: [id, account_id, email, name, role, is_email_verified, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        email: { type: string, format: email }
        name: { type: string }
        role: { $ref: "#/components/schemas/UserRole" }
        is_email_verified: { type: boolean }
        two_factor_enabled: { type: boolean }
        last_login_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    AccountInfo:
      type: object
      required: [id, name, status, plan]
      properties:
        id: { type: string }
        name: { type: string }
        status: { type: string }
        plan: { type: string }

    RegisterRequest:
      type: object
      required: [name, email, password]
      properties:
        name: { type: string, minLength: 1 }
        email: { type: string, format: email }
        password: { type: string, minLength: 8 }
        company_name: { type: string }

    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email: { type: string, format: email }
        password: { type: string }

    AuthResponse:
      type: object
      required: [token, user, account, expires_at]
      properties:
        token: { type: string }
        user: { $ref: "#/components/schemas/User" }
        account: { $ref: "#/components/schemas/AccountInfo" }
        expires_at: { type: string, format: date-time }

    UserMeResponse:
      type: object
      required: [user, account]
      properties:
        user: { $ref: "#/components/schemas/User" }
        account: { $ref: "#/components/schemas/AccountInfo" }

    ForgotPasswordRequest:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email }

    ResetPasswordRequest:
      type: object
      required: [token, new_password]
      properties:
        token: { type: string }
        new_password: { type: string, minLength: 8 }

    VerifyEmailRequest:
      type: object
      required: [token]
      properties:
        token: { type: string }

    UserSession:
      type: object
      required: [id, ip, user_agent, is_current, last_active_at, created_at]
      properties:
        id: { type: string }
        ip: { type: string }
        user_agent: { type: string }
        is_current: { type: boolean }
        last_active_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    UserSessionsResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/UserSession" }

    TeamMember:
      type: object
      required: [id, email, name, role, is_email_verified, created_at]
      properties:
        id: { type: string }
        email: { type: string, format: email }
        name: { type: string }
        role: { $ref: "#/components/schemas/UserRole" }
        is_email_verified: { type: boolean }
        last_login_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    TeamMembersResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/TeamMember" }

    TeamInvite:
      type: object
      required: [id, email, role, status, expires_at, created_at]
      properties:
        id: { type: string }
        email: { type: string, format: email }
        role: { $ref: "#/components/schemas/UserRole" }
        status: { type: string, enum: [pending, accepted, expired, revoked] }
        expires_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    TeamInvitesResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/TeamInvite" }

    CreateTeamInviteRequest:
      type: object
      required: [email, role]
      properties:
        email: { type: string, format: email }
        role: { $ref: "#/components/schemas/UserRole" }

    UpdateTeamMemberRoleRequest:
      type: object
      required: [role]
      properties:
        role: { $ref: "#/components/schemas/UserRole" }

    AuditLogEntry:
      type: object
      required: [id, action, resource_type, created_at]
      properties:
        id: { type: string }
        user_id: { type: string }
        user_email: { type: string }
        action: { type: string }
        resource_type: { type: string }
        resource_id: { type: string }
        details: { type: object, additionalProperties: true }
        ip: { type: string }
        created_at: { type: string, format: date-time }

    AuditLogsResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/AuditLogEntry" }
        next_cursor: { type: string }

    SSOConfig:
      type: object
      required: [id, account_id, name, protocol, status, domains, enforce_sso, jit_provisioning_enabled, default_role, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        protocol: { type: string, enum: [saml, oidc] }
        status: { type: string, enum: [active, disabled, testing] }
        domains:
          type: array
          items: { type: string }
        enforce_sso: { type: boolean }
        jit_provisioning_enabled: { type: boolean }
        default_role: { $ref: "#/components/schemas/UserRole" }
        idp_entity_id: { type: string }
        idp_sso_url: { type: string }
        idp_x509_cert: { type: string }
        sp_entity_id: { type: string }
        sp_acs_url: { type: string }
        oidc_issuer_url: { type: string }
        oidc_client_id: { type: string }
        oidc_client_secret: { type: string }
        oidc_auth_url: { type: string }
        oidc_token_url: { type: string }
        oidc_userinfo_url: { type: string }
        oidc_scopes: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateSSOConfigInput:
      type: object
      required: [name, protocol, domains]
      properties:
        name: { type: string }
        protocol: { type: string, enum: [saml, oidc] }
        domains:
          type: array
          items: { type: string }
        enforce_sso: { type: boolean }
        jit_provisioning_enabled: { type: boolean }
        default_role: { $ref: "#/components/schemas/UserRole" }
        idp_entity_id: { type: string }
        idp_sso_url: { type: string }
        idp_x509_cert: { type: string }
        sp_entity_id: { type: string }
        sp_acs_url: { type: string }
        oidc_issuer_url: { type: string }
        oidc_client_id: { type: string }
        oidc_client_secret: { type: string }
        oidc_auth_url: { type: string }
        oidc_token_url: { type: string }
        oidc_userinfo_url: { type: string }
        oidc_scopes: { type: string }

    UpdateSSOConfigInput:
      type: object
      properties:
        name: { type: string }
        status: { type: string, enum: [active, disabled, testing] }
        domains:
          type: array
          items: { type: string }
        enforce_sso: { type: boolean }
        jit_provisioning_enabled: { type: boolean }
        default_role: { $ref: "#/components/schemas/UserRole" }
        idp_entity_id: { type: string }
        idp_sso_url: { type: string }
        idp_x509_cert: { type: string }
        oidc_issuer_url: { type: string }
        oidc_client_id: { type: string }
        oidc_client_secret: { type: string }
        oidc_auth_url: { type: string }
        oidc_token_url: { type: string }
        oidc_userinfo_url: { type: string }
        oidc_scopes: { type: string }

    SSODiscoverRequest:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email }

    SSODiscoverResponse:
      type: object
      required: [sso_available]
      properties:
        sso_available: { type: boolean }
        enforce_sso: { type: boolean }
        protocol: { type: string, enum: [saml, oidc] }
        config_id: { type: string }
        provider_name: { type: string }
        login_url: { type: string }

    SSOLoginAudit:
      type: object
      required: [id, sso_config_id, account_id, email, protocol, status, created_at]
      properties:
        id: { type: string }
        sso_config_id: { type: string }
        account_id: { type: string }
        user_id: { type: string }
        email: { type: string }
        protocol: { type: string, enum: [saml, oidc] }
        status: { type: string, enum: [success, failed] }
        error_message: { type: string }
        ip: { type: string }
        user_agent: { type: string }
        created_at: { type: string, format: date-time }

    SSOTestResult:
      type: object
      required: [valid, protocol, endpoints_reachable, errors, warnings, summary]
      properties:
        valid: { type: boolean }
        protocol: { type: string, enum: [saml, oidc] }
        endpoints_reachable: { type: boolean }
        cert_valid: { type: boolean }
        cert_expires_at: { type: string, format: date-time }
        errors:
          type: array
          items: { type: string }
        warnings:
          type: array
          items: { type: string }
        summary: { type: string }

    MediaFile:
      type: object
      required: [id, account_id, filename, url, content_type, size_bytes, created_at]
      properties:
        id: { type: string, description: ULID identifier }
        account_id: { type: string }
        filename: { type: string }
        url: { type: string, description: Public URL for embedding in email templates }
        content_type: { type: string, example: "image/png" }
        size_bytes: { type: integer, example: 1048576 }
        width: { type: [integer, "null"], example: 800 }
        height: { type: [integer, "null"], example: 600 }
        created_at: { type: string, format: date-time }

    IPPoolType:
      type: string
      enum: [shared_transactional, shared_marketing, dedicated]

    IPAddressStatus:
      type: string
      enum: [active, warmup, quarantined, disabled]

    IPAddress:
      type: object
      required: [id, pool_id, ip_address, status, weight, created_at]
      properties:
        id: { type: string }
        pool_id: { type: string }
        ip_address: { type: string, example: "192.0.2.10" }
        ptr_domain: { type: string, example: "mail1.esp-platform.ru" }
        status: { $ref: "#/components/schemas/IPAddressStatus" }
        weight: { type: integer, default: 1 }
        fcrdns_status:
          $ref: "#/components/schemas/FCrDNSStatus"
        fcrdns_resolved_ptr:
          type: string
          example: "mail1.esp-platform.ru"
        fcrdns_forward_ips:
          type: array
          items: { type: string }
          example: ["192.0.2.10"]
        fcrdns_details:
          type: string
          example: "FCrDNS verified: IP resolves to mail1.esp-platform.ru and forward-resolves back to IP"
        fcrdns_last_checked_at:
          type: [string, "null"]
          format: date-time
        quarantine_reason:
          type: [string, "null"]
          example: "Listed in zen.spamhaus.org (SBL)"
        quarantined_at:
          type: [string, "null"]
          format: date-time
        auto_quarantine:
          type: boolean
          default: true
        created_at: { type: string, format: date-time }

    IPPool:
      type: object
      required: [id, name, pool_type, created_at]
      properties:
        id: { type: string }
        name: { type: string, example: "shared-tx" }
        pool_type: { $ref: "#/components/schemas/IPPoolType" }
        account_id: { type: [string, "null"] }
        ips:
          type: array
          items: { $ref: "#/components/schemas/IPAddress" }
        created_at: { type: string, format: date-time }

    SetAccountIPPoolsRequest:
      type: object
      properties:
        account_id:
          type: string
          description: Client account ID. Required for platform admins; defaults to caller account if omitted.
        tx_pool_id:
          type: [string, "null"]
          description: Dedicated or shared transactional pool ID or name
        mkt_pool_id:
          type: [string, "null"]
          description: Dedicated or shared marketing pool ID or name

    CreateIPPoolRequest:
      type: object
      required: [name, pool_type]
      properties:
        name: { type: string, minLength: 2, maxLength: 64 }
        pool_type: { $ref: "#/components/schemas/IPPoolType" }
        account_id: { type: [string, "null"], description: "Only for dedicated pools" }

    AddIPAddressRequest:
      type: object
      required: [ip_address]
      properties:
        ip_address: { type: string }
        ptr_domain: { type: string }
        status: { $ref: "#/components/schemas/IPAddressStatus", default: active }
        weight: { type: integer, default: 1 }

    FCrDNSStatus:
      type: string
      enum: [valid, ptr_mismatch, forward_mismatch, missing_ptr, forward_failed, invalid_ip, unverified]

    FCrDNSResult:
      type: object
      required: [ip_address, fcrdns_valid, status, details, checked_at]
      properties:
        ip_id:
          type: string
          description: "ID of the pool IP address if checked within a pool"
        pool_id:
          type: string
          description: "ID of the IP pool if checked within a pool"
        ip_address:
          type: string
          example: "192.0.2.10"
        configured_ptr:
          type: string
          example: "mail1.esp-platform.ru"
        resolved_ptr:
          type: string
          example: "mail1.esp-platform.ru"
        forward_ips:
          type: array
          items: { type: string }
          example: ["192.0.2.10"]
        fcrdns_valid:
          type: boolean
          example: true
        status:
          $ref: "#/components/schemas/FCrDNSStatus"
        details:
          type: string
          example: "FCrDNS verified: IP resolves to mail1.esp-platform.ru and forward-resolves back to IP"
        checked_at:
          type: string
          format: date-time

    FCrDNSCheckRequest:
      type: object
      required: [ip]
      properties:
        ip:
          type: string
          example: "192.0.2.10"
        ptr_domain:
          type: string
          description: "Expected PTR domain name to verify alignment with"
          example: "mail1.esp-platform.ru"

    IPWarmupStage:
      type: object
      required: [day, daily_limit, provider_limits]
      properties:
        day:
          type: integer
          example: 1
        daily_limit:
          type: integer
          example: 250
        provider_limits:
          type: object
          additionalProperties:
            type: integer
          example:
            gmail: 50
            yandex: 50
            mailru: 50
            outlook: 30
            yahoo: 20
            other: 50

    IPWarmupPlan:
      type: object
      required: [id, name, description, total_days, stages]
      properties:
        id:
          type: string
          example: "standard_30d"
        name:
          type: string
          example: "Standard 30 Days"
        description:
          type: string
          example: "Gradual 30-day warm-up plan starting at 250/day up to 100,000+/day"
        total_days:
          type: integer
          example: 30
        stages:
          type: array
          items:
            $ref: "#/components/schemas/IPWarmupStage"

    IPWarmupStateStatus:
      type: string
      enum: [in_progress, paused, completed]

    IPWarmupState:
      type: object
      required: [id, pool_id, ip_id, ip_address, plan_id, plan_name, current_day, total_days, status, fallback_pool, daily_sent_total, daily_limit_total, sent_by_provider, limits_by_provider, spilled_by_provider, started_at, day_started_at, updated_at]
      properties:
        id:
          type: string
        pool_id:
          type: string
        ip_id:
          type: string
        ip_address:
          type: string
          example: "192.0.2.10"
        plan_id:
          type: string
          example: "standard_30d"
        plan_name:
          type: string
          example: "Standard 30 Days"
        current_day:
          type: integer
          example: 1
        total_days:
          type: integer
          example: 30
        status:
          $ref: "#/components/schemas/IPWarmupStateStatus"
        fallback_pool:
          type: string
          example: "shared-tx"
        daily_sent_total:
          type: integer
          example: 45
        daily_limit_total:
          type: integer
          example: 250
        sent_by_provider:
          type: object
          additionalProperties:
            type: integer
          example:
            gmail: 20
            yandex: 15
            mailru: 10
            outlook: 0
            yahoo: 0
            other: 0
        limits_by_provider:
          type: object
          additionalProperties:
            type: integer
          example:
            gmail: 50
            yandex: 50
            mailru: 50
            outlook: 30
            yahoo: 20
            other: 50
        spilled_by_provider:
          type: object
          additionalProperties:
            type: integer
          example:
            gmail: 0
            yandex: 0
            mailru: 0
            outlook: 0
            yahoo: 0
            other: 0
        started_at:
          type: string
          format: date-time
        day_started_at:
          type: string
          format: date-time
        completed_at:
          type: [string, "null"]
          format: date-time
        updated_at:
          type: string
          format: date-time

    StartWarmupRequest:
      type: object
      properties:
        plan_id:
          type: string
          example: "standard_30d"
        fallback_pool:
          type: string
          example: "shared-tx"

    QuarantineIPRequest:
      type: object
      required: [reason]
      properties:
        reason:
          type: string
          example: "Temporary block from Gmail due to spike in 554 responses"

    IPQuarantineAction:
      type: string
      enum: [quarantined, unquarantined, none]

    IPQuarantineCheckResult:
      type: object
      required: [ip_id, pool_id, ip_address, is_quarantined, dnsbl_listed, listed_zones, details, action_taken, checked_at]
      properties:
        ip_id:
          type: string
        pool_id:
          type: string
        ip_address:
          type: string
          example: "192.0.2.10"
        is_quarantined:
          type: boolean
        dnsbl_listed:
          type: boolean
        listed_zones:
          type: array
          items:
            type: string
          example: ["zen.spamhaus.org"]
        details:
          type: string
          example: "Listed in 1 DNSBL zones: zen.spamhaus.org"
        action_taken:
          $ref: "#/components/schemas/IPQuarantineAction"
        checked_at:
          type: string
          format: date-time

    QuarantineScanResponse:
      type: object
      required: [scanned_ips, quarantined_count, results]
      properties:
        scanned_ips:
          type: integer
          example: 5
        quarantined_count:
          type: integer
          example: 1
        results:
          type: array
          items:
            $ref: "#/components/schemas/IPQuarantineCheckResult"

    ModerationCampaignItem:
      type: object
      required: [id, account_id, account_name, name, subject, from_email, contacts_count, status, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        account_name: { type: string }
        name: { type: string }
        subject: { type: string }
        from_email: { type: string }
        from_name: { type: string }
        list_id: { type: string }
        list_name: { type: string }
        contacts_count: { type: integer }
        status: { $ref: "#/components/schemas/CampaignStatus" }
        base_source: { type: string }
        base_source_details: { type: string }
        spam_score: { type: number }
        created_at: { type: string, format: date-time }

    ModerationCampaignDetail:
      allOf:
        - $ref: "#/components/schemas/ModerationCampaignItem"
        - type: object
          required: [html]
          properties:
            html: { type: string }
            text: { type: string }
            links:
              type: array
              items: { type: string }
            account_warmup_tier: { type: integer }
            account_reputation: { type: string }
            account_created_at: { type: string, format: date-time }

    ApproveCampaignRequest:
      type: object
      properties:
        approve_account:
          type: boolean
          description: "Mark account as moderation approved to skip review for subsequent campaigns"
          default: false
        comment:
          type: string
          description: "Operator comment"

    RejectCampaignRequest:
      type: object
      required: [reason]
      properties:
        reason:
          type: string
          description: "Rejection reason explaining what user should fix"
        suspend_account:
          type: boolean
          description: "Also suspend the whole account if phishing/malicious"
          default: false

    AdminAccountItem:
      type: object
      required: [id, name, status, plan, warmup_tier, reputation_status, moderation_status, created_at]
      properties:
        id: { type: string }
        name: { type: string }
        status: { type: string, enum: [active, suspended] }
        plan: { type: string }
        billing_status: { type: string }
        reputation_status: { type: string }
        moderation_status: { type: string, enum: [pending, approved] }
        warmup_tier: { type: integer }
        hourly_limit: { type: integer }
        daily_limit: { type: integer }
        monthly_limit: { type: integer }
        rate_limit_rps: { type: integer }
        total_campaigns: { type: integer }
        total_sent: { type: integer }
        created_at: { type: string, format: date-time }

    AdminAccountDetail:
      allOf:
        - $ref: "#/components/schemas/AdminAccountItem"
        - type: object
          properties:
            domains_count: { type: integer }
            api_keys_count: { type: integer }
            bounce_rate: { type: number }
            complaint_rate: { type: number }

    AdminUpdateAccountRequest:
      type: object
      properties:
        status: { type: string, enum: [active, suspended] }
        plan: { type: string }
        moderation_status: { type: string, enum: [pending, approved] }
        warmup_tier: { type: integer, minimum: 1, maximum: 4 }
        hourly_limit: { type: integer, minimum: 0 }
        daily_limit: { type: integer, minimum: 0 }
        monthly_limit: { type: integer, minimum: 0 }
        rate_limit_rps: { type: integer, minimum: 1 }

    AdminReputationOverview:
      type: object
      required: [overall_reputation, warning_accounts_count, restricted_accounts_count, accounts]
      properties:
        overall_reputation: { type: string }
        warning_accounts_count: { type: integer }
        restricted_accounts_count: { type: integer }
        accounts:
          type: array
          items:
            type: object
            required: [account_id, account_name, reputation_status, bounce_rate, complaint_rate]
            properties:
              account_id: { type: string }
              account_name: { type: string }
              reputation_status: { type: string }
              bounce_rate: { type: number }
              complaint_rate: { type: number }
              last_warning: { type: string }

    AdminPlatformStats:
      type: object
      required: [total_accounts, active_accounts, campaigns_under_review, total_sent_today, total_queued, active_ips, quarantined_ips]
      properties:
        total_accounts: { type: integer }
        active_accounts: { type: integer }
        campaigns_under_review: { type: integer }
        total_sent_today: { type: integer }
        total_queued: { type: integer }
        active_ips: { type: integer }
        quarantined_ips: { type: integer }

    ContactDataExport:
      type: object
      required: [account_id, contact, lists, consent_proof, export_metadata]
      properties:
        account_id: { type: string }
        contact: { $ref: "#/components/schemas/Contact" }
        lists:
          type: array
          items: { $ref: "#/components/schemas/ContactList" }
        consent_proof: { $ref: "#/components/schemas/ConsentAuditTrail" }
        export_metadata:
          type: object
          additionalProperties: { type: string }

    ConsentAuditTrail:
      type: object
      required: [contact_id, email, consent_source, consent_status, legislation]
      properties:
        contact_id: { type: string }
        email: { type: string, format: email }
        phone: { type: string }
        consent_source: { type: string }
        consent_at: { type: [string, "null"], format: date-time }
        consent_status: { type: string, enum: [granted, revoked] }
        unsubscribed_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        legislation: { type: string }

    AbuseReportInput:
      type: object
      required: [reporter_email, reason]
      properties:
        account_id: { type: string }
        campaign_id: { type: string }
        message_id: { type: string }
        reporter_email: { type: string, format: email }
        reason:
          type: string
          enum: [spam, phishing, fraud, no_consent, ord_missing, other]
        details: { type: string }
        raw_headers: { type: string }

    AbuseReport:
      type: object
      required: [id, account_id, reporter_email, reason, status, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        campaign_id: { type: [string, "null"] }
        message_id: { type: [string, "null"] }
        reporter_email: { type: string, format: email }
        reason: { type: string }
        details: { type: string }
        raw_headers: { type: string }
        status: { type: string, enum: [pending, investigating, resolved, dismissed] }
        resolution_notes: { type: string }
        resolved_by: { type: [string, "null"] }
        resolved_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }

    AbuseReportResolution:
      type: object
      required: [status, resolution_notes]
      properties:
        status:
          type: string
          enum: [investigating, resolved, dismissed]
        resolution_notes: { type: string }
        resolved_by: { type: string }
        take_action:
          type: string
          enum: [none, pause_campaign, restrict_account]

    StatusComponent:
      type: object
      required: [id, name, status, latency_ms]
      properties:
        id: { type: string, example: "api" }
        name: { type: string, example: "REST API Gateway" }
        status: { type: string, enum: [operational, degraded, partial_outage, major_outage] }
        latency_ms: { type: integer, example: 12 }

    SystemIncident:
      type: object
      required: [id, title, status, severity, affected_components, message, created_at, updated_at]
      properties:
        id: { type: string }
        title: { type: string }
        status: { type: string, enum: [investigating, identified, monitoring, resolved] }
        severity: { type: string, enum: [minor, major, critical] }
        affected_components:
          type: array
          items: { type: string }
        message: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        resolved_at: { type: [string, "null"], format: date-time }

    CreateSystemIncidentRequest:
      type: object
      required: [title, severity, affected_components, message]
      properties:
        title: { type: string }
        severity: { type: string, enum: [minor, major, critical] }
        status: { type: string, enum: [investigating, identified, monitoring, resolved] }
        affected_components:
          type: array
          items: { type: string }
        message: { type: string }

    UpdateSystemIncidentRequest:
      type: object
      properties:
        title: { type: string }
        status: { type: string, enum: [investigating, identified, monitoring, resolved] }
        severity: { type: string, enum: [minor, major, critical] }
        affected_components:
          type: array
          items: { type: string }
        message: { type: string }

    SystemHealthComponent:
      type: object
      required: [id, name, status, role]
      properties:
        id: { type: string, example: "campaign_engine" }
        name: { type: string, example: "Воркер рассылок (campaign-engine)" }
        status: { type: string, enum: [ok, degraded, fail] }
        role: { type: string, enum: [worker, storage, queue, gateway, mta, dns, database, cache] }
        latency_ms: { type: integer, nullable: true }
        last_heartbeat: { type: string, format: date-time, nullable: true }
        error: { type: string, nullable: true }
        details: { type: string, nullable: true }

    SystemHealthResponse:
      type: object
      required: [status, healthy, checked_at, issues, components]
      properties:
        status: { type: string, enum: [healthy, degraded, critical] }
        healthy: { type: boolean }
        checked_at: { type: string, format: date-time }
        issues:
          type: array
          items: { type: string }
        components:
          type: array
          items:
            $ref: "#/components/schemas/SystemHealthComponent"

    StatusPageResponse:
      type: object
      required: [status, components, uptime_90d, active_incidents, past_incidents]
      properties:
        status: { type: string, enum: [operational, degraded, partial_outage, major_outage] }
        components:
          type: array
          items: { $ref: "#/components/schemas/StatusComponent" }
        uptime_90d: { type: number, example: 99.98 }
        active_incidents:
          type: array
          items: { $ref: "#/components/schemas/SystemIncident" }
        past_incidents:
          type: array
          items: { $ref: "#/components/schemas/SystemIncident" }

    SupportTicket:
      type: object
      required: [id, account_id, subject, category, priority, status, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        user_id: { type: [string, "null"] }
        subject: { type: string }
        category: { type: string, enum: [general, billing, deliverability, api, abuse] }
        priority: { type: string, enum: [low, normal, high, urgent] }
        status: { type: string, enum: [open, in_progress, waiting_customer, closed] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        closed_at: { type: [string, "null"], format: date-time }

    SupportTicketMessage:
      type: object
      required: [id, ticket_id, sender_type, sender_id, sender_name, body, created_at]
      properties:
        id: { type: string }
        ticket_id: { type: string }
        sender_type: { type: string, enum: [customer, agent, system] }
        sender_id: { type: string }
        sender_name: { type: string }
        body: { type: string }
        created_at: { type: string, format: date-time }

    SupportTicketDetail:
      type: object
      required: [ticket, messages]
      properties:
        ticket: { $ref: "#/components/schemas/SupportTicket" }
        messages:
          type: array
          items: { $ref: "#/components/schemas/SupportTicketMessage" }

    CreateSupportTicketRequest:
      type: object
      required: [subject, category, message]
      properties:
        subject: { type: string }
        category: { type: string, enum: [general, billing, deliverability, api, abuse] }
        priority: { type: string, enum: [low, normal, high, urgent], default: normal }
        message: { type: string }

    SupportTicketReplyRequest:
      type: object
      required: [message]
      properties:
        message: { type: string }

    AdminTicketReplyRequest:
      type: object
      required: [message]
      properties:
        message: { type: string }
        status_change: { type: string, enum: [open, in_progress, waiting_customer, closed] }

    AdminUpdateTicketRequest:
      type: object
      properties:
        status: { type: string, enum: [open, in_progress, waiting_customer, closed] }
        priority: { type: string, enum: [low, normal, high, urgent] }

    AdminImpersonateRequest:
      type: object
      required: [target_account_id, reason]
      properties:
        target_account_id: { type: string }
        target_user_id: { type: string }
        reason: { type: string }

    AdminImpersonateResponse:
      type: object
      required: [token, target_account_id, expires_at]
      properties:
        token: { type: string }
        target_account_id: { type: string }
        target_user_id: { type: [string, "null"] }
        admin_user_id: { type: string }
        expires_at: { type: string, format: date-time }

    Subaccount:
      type: object
      required: [id, parent_account_id, name, status, rate_limit_rps, rate_limit_burst, daily_limit, hourly_limit, created_at, updated_at]
      properties:
        id: { type: string }
        parent_account_id: { type: string }
        name: { type: string }
        status: { type: string, enum: [active, suspended] }
        rate_limit_rps: { type: integer }
        rate_limit_burst: { type: integer }
        daily_limit: { type: integer }
        hourly_limit: { type: integer }
        subaccount_quota: { type: integer, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SubaccountInput:
      type: object
      required: [name]
      properties:
        name: { type: string, minLength: 1, maxLength: 200 }
        daily_limit: { type: integer, minimum: 0 }
        hourly_limit: { type: integer, minimum: 0 }
        rate_limit_rps: { type: integer, minimum: 1 }
        subaccount_quota: { type: integer, minimum: 0 }

    SubaccountUpdate:
      type: object
      properties:
        name: { type: string, minLength: 1, maxLength: 200 }
        status: { type: string, enum: [active, suspended] }
        daily_limit: { type: integer, minimum: 0 }
        hourly_limit: { type: integer, minimum: 0 }
        rate_limit_rps: { type: integer, minimum: 1 }
        subaccount_quota: { type: integer, minimum: 0 }

    SubaccountStats:
      type: object
      required: [subaccount_id, sent_total, delivered, bounced, complained, open_rate, click_rate]
      properties:
        subaccount_id: { type: string }
        sent_total: { type: integer }
        delivered: { type: integer }
        bounced: { type: integer }
        complained: { type: integer }
        open_rate: { type: number, format: float }
        click_rate: { type: number, format: float }

    DMARCPolicyTag:
      type: object
      properties:
        v: { type: string }
        p: { type: string, enum: [none, quarantine, reject] }
        sp: { type: string }
        pct: { type: integer }
        rua: { type: string }
        ruf: { type: string }
        adkim: { type: string }
        aspf: { type: string }
        fo: { type: string }
        ri: { type: integer }


    BimiConfig:
      type: object
      required: [id, account_id, domain_id, selector, svg_url, status, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        domain_id: { type: string }
        selector: { type: string }
        svg_url: { type: string }
        svg_content: { type: string }
        vmc_url: { type: string }
        status: { type: string, enum: [pending, verified, warning, error] }
        last_checked_at: { type: string, format: date-time }
        last_check_result: { $ref: "#/components/schemas/BimiCheckResult" }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SetBimiInput:
      type: object
      required: [svg_url]
      properties:
        selector: { type: string, default: "default" }
        svg_url: { type: string, format: uri }
        vmc_url: { type: string, format: uri }

    BimiSvgValidation:
      type: object
      required: [valid, is_square, has_tiny_ps_profile, has_title, size_bytes, errors, warnings]
      properties:
        valid: { type: boolean }
        width: { type: integer }
        height: { type: integer }
        is_square: { type: boolean }
        has_tiny_ps_profile: { type: boolean }
        has_title: { type: boolean }
        title: { type: string }
        size_bytes: { type: integer }
        errors: { type: array, items: { type: string } }
        warnings: { type: array, items: { type: string } }

    BimiVmcValidation:
      type: object
      required: [valid, is_expired, domain_matches, has_bimi_oid, errors, warnings]
      properties:
        valid: { type: boolean }
        issuer: { type: string }
        subject: { type: string }
        serial_number: { type: string }
        not_before: { type: string, format: date-time }
        not_after: { type: string, format: date-time }
        is_expired: { type: boolean }
        san_domains: { type: array, items: { type: string } }
        domain_matches: { type: boolean }
        has_bimi_oid: { type: boolean }
        errors: { type: array, items: { type: string } }
        warnings: { type: array, items: { type: string } }

    BimiClientSupport:
      type: object
      required: [client, supported, badge, notes]
      properties:
        client: { type: string }
        supported: { type: boolean }
        badge: { type: string }
        notes: { type: string }

    BimiCheckResult:
      type: object
      required: [domain, selector, eligible, overall_status, spf_ready, dkim_ready, dmarc_ready, dmarc_policy, dmarc_pct, dns_record_found, dns_host, client_support, recommendations, checked_at]
      properties:
        domain: { type: string }
        selector: { type: string }
        eligible: { type: boolean }
        overall_status: { type: string, enum: [verified, warning, error, unconfigured] }
        spf_ready: { type: boolean }
        dkim_ready: { type: boolean }
        dmarc_ready: { type: boolean }
        dmarc_policy: { type: string }
        dmarc_pct: { type: integer }
        dmarc_reason: { type: string }
        dns_record_found: { type: boolean }
        dns_host: { type: string }
        dns_value: { type: string }
        svg_url: { type: string }
        vmc_url: { type: string }
        svg_validation: { $ref: "#/components/schemas/BimiSvgValidation" }
        vmc_validation: { $ref: "#/components/schemas/BimiVmcValidation" }
        client_support: { type: array, items: { $ref: "#/components/schemas/BimiClientSupport" } }
        recommendations: { type: array, items: { type: string } }
        checked_at: { type: string, format: date-time }

    DMARCPolicyAudit:
      type: object
      required: [domain_name, record_found, protection_score, risk_level, status_label, recommendations, suggested_record]
      properties:
        domain_name: { type: string }
        record_found: { type: boolean }
        raw_record: { type: string }
        policy: { $ref: "#/components/schemas/DMARCPolicyTag" }
        protection_score: { type: integer, description: "Score from 0 to 100" }
        risk_level: { type: string, enum: [critical, high, medium, low, safe] }
        status_label: { type: string }
        recommendations:
          type: array
          items: { type: string }
        suggested_record: { type: string }

    DMARCReport:
      type: object
      required: [id, account_id, domain_id, domain_name, org_name, email, report_id, begin_date, end_date, policy_p, policy_pct, total_messages, passed_messages, failed_messages, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        domain_id: { type: string }
        domain_name: { type: string }
        org_name: { type: string }
        email: { type: string }
        report_id: { type: string }
        begin_date: { type: string, format: date-time }
        end_date: { type: string, format: date-time }
        policy_p: { type: string }
        policy_sp: { type: string }
        policy_pct: { type: integer }
        total_messages: { type: integer }
        passed_messages: { type: integer }
        failed_messages: { type: integer }
        created_at: { type: string, format: date-time }

    DMARCOrgStats:
      type: object
      required: [org_name, total, passed, failed, pass_rate]
      properties:
        org_name: { type: string }
        total: { type: integer }
        passed: { type: integer }
        failed: { type: integer }
        pass_rate: { type: number, format: float }

    DMARCSourceIPStats:
      type: object
      required: [source_ip, total, dkim_pass, spf_pass, dmarc_pass, disposition]
      properties:
        source_ip: { type: string }
        total: { type: integer }
        dkim_pass: { type: integer }
        spf_pass: { type: integer }
        dmarc_pass: { type: integer }
        disposition: { type: string }

    DMARCAggregateStats:
      type: object
      required: [domain_id, domain_name, reports_count, total_messages, passed_messages, failed_messages, pass_rate, quarantined, rejected, org_breakdown, ip_breakdown]
      properties:
        domain_id: { type: string }
        domain_name: { type: string }
        reports_count: { type: integer }
        total_messages: { type: integer }
        passed_messages: { type: integer }
        failed_messages: { type: integer }
        pass_rate: { type: number, format: float }
        quarantined: { type: integer }
        rejected: { type: integer }
        org_breakdown:
          type: array
          items: { $ref: "#/components/schemas/DMARCOrgStats" }
        ip_breakdown:
          type: array
          items: { $ref: "#/components/schemas/DMARCSourceIPStats" }

    MessageStream:
      type: object
      required: [id, account_id, name, stream_type, retention_days, status, created_at, updated_at]
      properties:
        id: { type: string, example: "outbound" }
        account_id: { type: string }
        name: { type: string, example: "Transactional Stream" }
        stream_type: { type: string, enum: [transactional, broadcast] }
        description: { type: string }
        ip_pool: { type: string, example: "shared-tx" }
        retention_days: { type: integer, example: 7 }
        status: { type: string, enum: [active, paused, archived] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateStreamInput:
      type: object
      required: [name, stream_type]
      properties:
        name: { type: string, minLength: 1, maxLength: 100, example: "Order Notifications" }
        stream_type: { type: string, enum: [transactional, broadcast] }
        description: { type: string }
        ip_pool: { type: string }
        retention_days: { type: integer, minimum: 0, maximum: 365, default: 7 }

    UpdateStreamInput:
      type: object
      properties:
        name: { type: string, minLength: 1, maxLength: 100 }
        description: { type: string }
        ip_pool: { type: string }
        retention_days: { type: integer, minimum: 0, maximum: 365 }
        status: { type: string, enum: [active, paused, archived] }

    StreamStats:
      type: object
      required: [stream_id, name, stream_type, sent, delivered, bounced, complained, opened, clicked, bounce_rate, complaint_rate, open_rate, click_rate, health_status]
      properties:
        stream_id: { type: string }
        name: { type: string }
        stream_type: { type: string, enum: [transactional, broadcast] }
        sent: { type: integer }
        delivered: { type: integer }
        bounced: { type: integer }
        complained: { type: integer }
        opened: { type: integer }
        clicked: { type: integer }
        bounce_rate: { type: number, format: float }
        complaint_rate: { type: number, format: float }
        open_rate: { type: number, format: float }
        click_rate: { type: number, format: float }
        health_status: { type: string, enum: [good, fair, poor] }

    MessageContent:
      type: object
      required: [message_id, subject, from, to]
      properties:
        message_id: { type: string }
        subject: { type: string }
        from: { type: string }
        to: { type: string }
        headers:
          type: object
          additionalProperties: { type: string }
        html: { type: string }
        text: { type: string }
        size_bytes: { type: integer }

    ResendResult:
      type: object
      required: [message_id, original_message_id, recipient, status]
      properties:
        message_id: { type: string }
        original_message_id: { type: string }
        recipient: { type: string, format: email }
        status: { type: string, example: "accepted" }

    # Multi-DC Delivery Sites & Failover (MTA-13)

    DeliverabilityAIRecommendation:
      type: object
      required: [id, category, severity, title, description, impact, steps, status, created_at]
      properties:
        id: { type: string }
        category:
          type: string
          enum: [isp_reputation, authentication, list_hygiene, content_triggers, sending_schedule, warmup]
        severity:
          type: string
          enum: [critical, warning, opportunity, info]
        title: { type: string }
        description: { type: string }
        impact: { type: string }
        steps:
          type: array
          items: { type: string }
        action_type:
          type: string
          nullable: true
          enum: [enable_sunset, enable_sto, fix_dmarc, throttle_isp, clean_list, verify_bimi, custom]
        action_target_url: { type: string, nullable: true }
        action_payload: { type: object }
        status:
          type: string
          enum: [active, applied, dismissed]
        metrics_snapshot: { type: object }
        created_at: { type: string, format: date-time }
        applied_at: { type: string, format: date-time, nullable: true }

    DeliverabilityAIRecommendationList:
      type: object
      required: [data, total, active_count]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/DeliverabilityAIRecommendation" }
        total: { type: integer }
        active_count: { type: integer }

    DeliverabilityAISummary:
      type: object
      required: [health_score, critical_count, warning_count, opportunity_count, potential_inbox_lift, summary_text, last_evaluated_at]
      properties:
        health_score: { type: integer, minimum: 0, maximum: 100 }
        critical_count: { type: integer }
        warning_count: { type: integer }
        opportunity_count: { type: integer }
        potential_inbox_lift: { type: integer }
        summary_text: { type: string }
        last_evaluated_at: { type: string, format: date-time }
        recommendations:
          type: array
          items: { $ref: "#/components/schemas/DeliverabilityAIRecommendation" }

    DeliverabilityAIGenerateRequest:
      type: object
      properties:
        force_refresh: { type: boolean, default: true }

    DeliverabilityAIApplyResponse:
      type: object
      required: [status, id, message]
      properties:
        status: { type: string, example: "applied" }
        id: { type: string }
        message: { type: string }
        action_executed: { type: string }


    ChatChannel:
      type: object
      required: [id, account_id, name, provider, endpoint_url, status, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        provider:
          type: string
          enum: [maxchat, max, whatsapp, telegram, livechat, generic_webhook, vk, webpush]
        endpoint_url: { type: string }
        status:
          type: string
          enum: [active, paused, error]
        settings: { type: object }
        last_error: { type: string, nullable: true }
        created_at: { type: string, format: date-time }

    ChatChannelInput:
      type: object
      required: [name, provider, endpoint_url]
      properties:
        name: { type: string, example: "Основной канал MaxChat" }
        provider:
          type: string
          enum: [maxchat, max, whatsapp, telegram, livechat, generic_webhook, vk, webpush]
        endpoint_url: { type: string, example: "https://api.maxchat.ru/v1/messages" }
        api_key: { type: string }
        webhook_secret: { type: string }
        settings: { type: object }

    ChatChannelPatchInput:
      type: object
      properties:
        name: { type: string }
        endpoint_url: { type: string }
        status: { type: string, enum: [active, paused] }
        settings: { type: object }

    ChatChannelList:
      type: object
      required: [data, total]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/ChatChannel" }
        total: { type: integer }

    CascadeStep:
      type: object
      required: [channel, wait_timeout_seconds, fallback_condition, content]
      properties:
        channel:
          type: string
          enum: [chat, max, telegram, whatsapp, sms, email]
        channel_id: { type: string, nullable: true }
        wait_timeout_seconds: { type: integer, example: 900 }
        fallback_condition:
          type: string
          enum: [not_delivered, not_read]
          default: not_read
        content:
          type: object
          properties:
            text: { type: string }
            subject: { type: string }
            html: { type: string }
            button_url: { type: string }
            button_text: { type: string }

    CascadeCampaign:
      type: object
      required: [id, account_id, name, status, steps, stats, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        status:
          type: string
          enum: [draft, scheduled, running, paused, completed, cancelled]
        list_id: { type: string, nullable: true }
        segment_id: { type: string, nullable: true }
        steps:
          type: array
          items: { $ref: "#/components/schemas/CascadeStep" }
        stats: { type: object }
        created_at: { type: string, format: date-time }
        started_at: { type: string, format: date-time, nullable: true }
        finished_at: { type: string, format: date-time, nullable: true }

    CascadeCampaignInput:
      type: object
      required: [name, steps]
      properties:
        name: { type: string, example: "Каскад: Заказ в обработке" }
        list_id: { type: string }
        segment_id: { type: string }
        steps:
          type: array
          items: { $ref: "#/components/schemas/CascadeStep" }

    CascadeCampaignList:
      type: object
      required: [data, total]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/CascadeCampaign" }
        total: { type: integer }

    CascadeStatsResponse:
      type: object
      required: [entered, completed, by_channel, cost_saved_rub, savings_percent]
      properties:
        entered: { type: integer }
        completed: { type: integer }
        by_channel: { type: object }
        cost_saved_rub: { type: number }
        savings_percent: { type: number }

    DeliverySite:
      type: object
      required: [id, code, name, region, endpoint_url, role, status, weight, priority, circuit_state, created_at, updated_at]
      properties:
        id: { type: string, example: "01J9X8Y7Z6A5B4C3D2E1F0MTA1" }
        code: { type: string, example: "dc-spb" }
        name: { type: string, example: "Санкт-Петербург (Selectel Primary)" }
        region: { type: string, example: "ru-spb" }
        endpoint_url: { type: string, example: "http://mta-spb.internal:8000/api/inject/v1" }
        health_url: { type: string, example: "http://mta-spb.internal:8000/healthz" }
        role:
          type: string
          enum: [primary, secondary, standby, canary]
          example: "primary"
        status:
          type: string
          enum: [active, degraded, draining, offline, maintenance]
          example: "active"
        weight: { type: integer, minimum: 1, maximum: 100, example: 70 }
        priority: { type: integer, minimum: 1, maximum: 10, example: 1 }
        is_active: { type: boolean, example: true }
        latency_ms: { type: number, format: float, example: 4.8 }
        error_rate: { type: number, format: float, example: 0.02 }
        consecutive_failures: { type: integer, example: 0 }
        consecutive_successes: { type: integer, example: 450 }
        circuit_state:
          type: string
          enum: [closed, open, half_open]
          example: "closed"
        last_heartbeat_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    DeliverySiteInput:
      type: object
      required: [code, name, region, endpoint_url, role, weight, priority]
      properties:
        code: { type: string, example: "dc-hel" }
        name: { type: string, example: "Хельсинки (Hetzner Standby)" }
        region: { type: string, example: "eu-hel" }
        endpoint_url: { type: string, example: "http://mta-hel.internal:8000/api/inject/v1" }
        health_url: { type: string, example: "http://mta-hel.internal:8000/healthz" }
        role:
          type: string
          enum: [primary, secondary, standby, canary]
          default: "secondary"
        status:
          type: string
          enum: [active, degraded, draining, offline, maintenance]
          default: "active"
        weight: { type: integer, default: 50 }
        priority: { type: integer, default: 2 }
        is_active: { type: boolean, default: true }

    DeliverySiteUpdateInput:
      type: object
      properties:
        name: { type: string }
        endpoint_url: { type: string }
        health_url: { type: string }
        role: { type: string, enum: [primary, secondary, standby, canary] }
        status: { type: string, enum: [active, degraded, draining, offline, maintenance] }
        weight: { type: integer, minimum: 1, maximum: 100 }
        priority: { type: integer, minimum: 1, maximum: 10 }
        is_active: { type: boolean }

    SiteHealthReport:
      type: object
      required: [site_id, site_code, status, healthy, latency_ms, http_status, circuit_state, checked_at]
      properties:
        site_id: { type: string }
        site_code: { type: string }
        status: { type: string }
        healthy: { type: boolean }
        latency_ms: { type: number, format: float }
        http_status: { type: integer }
        error: { type: string }
        circuit_state: { type: string }
        checked_at: { type: string, format: date-time }

    FailoverRequest:
      type: object
      required: [from_site, to_site, reason]
      properties:
        from_site: { type: string, description: "Source site code or ID" }
        to_site: { type: string, description: "Target site code or ID" }
        reason: { type: string, example: "Плановые регламентные работы в ДЦ СПб" }
        drain: { type: boolean, default: true, description: "Set source site to draining status" }

    FailoverEvent:
      type: object
      required: [id, source_site_id, source_site_code, target_site_id, target_site_code, trigger_type, reason, status, operator, created_at]
      properties:
        id: { type: string }
        source_site_id: { type: string }
        source_site_code: { type: string }
        target_site_id: { type: string }
        target_site_code: { type: string }
        trigger_type:
          type: string
          enum: [health_check_failure, circuit_breaker_tripped, manual_operator, auto_failback, drain_evacuation]
        reason: { type: string }
        status:
          type: string
          enum: [initiated, completed, reverted]
        operator: { type: string }
        metadata: { type: object }
        created_at: { type: string, format: date-time }

    # ---------------- SMS Channel Schemas (CHN-01) ----------------
    SMSRequest:
      type: object
      required: [to, text]
      properties:
        to:
          type: string
          example: "+79991234567"
          description: Recipient phone number in international E.164 format
        text:
          type: string
          example: "Ваш код подтверждения: 4821"
          description: Message text
        sender_id:
          type: string
          description: Optional registered alphanumeric sender ID (ULID)
        sender_name:
          type: string
          example: "MYBRAND"
          description: Optional alphanumeric sender name
        tag:
          type: string
          example: "auth-otp"
        metadata:
          type: object
          additionalProperties: true

    SMSMessage:
      type: object
      required: [id, account_id, recipient, sender_name, text, encoding, segments_count, chars_count, status, provider, cost, sandbox, created_at]
      properties:
        id: { type: string, example: "01J9TESTSMS000000000000001" }
        account_id: { type: string }
        recipient: { type: string, example: "+79991234567" }
        sender_id: { type: [string, "null"] }
        sender_name: { type: string, example: "MYBRAND" }
        text: { type: string, example: "Ваш код подтверждения: 4821" }
        encoding:
          type: string
          enum: [gsm0338, ucs2]
          example: "ucs2"
        segments_count: { type: integer, example: 1 }
        chars_count: { type: integer, example: 28 }
        status:
          type: string
          enum: [accepted, sending, delivered, undelivered, failed, rejected, dropped]
          example: "delivered"
        provider:
          type: string
          enum: [mock, smsc, smsru]
          example: "smsc"
        provider_message_id: { type: [string, "null"], example: "smsc-123456" }
        error_code: { type: [string, "null"] }
        error_message: { type: [string, "null"] }
        cost: { type: number, format: float, example: 2.80 }
        sandbox: { type: boolean, example: false }
        tag: { type: [string, "null"], example: "auth-otp" }
        metadata: { type: object }
        created_at: { type: string, format: date-time }
        sent_at: { type: [string, "null"], format: date-time }
        delivered_at: { type: [string, "null"], format: date-time }

    SMSCalculation:
      type: object
      required: [text_length, encoding, segments_count, chars_per_segment, remaining_in_segment, estimated_cost]
      properties:
        text_length: { type: integer, example: 28 }
        encoding: { type: string, enum: [gsm0338, ucs2], example: "ucs2" }
        segments_count: { type: integer, example: 1 }
        chars_per_segment: { type: integer, example: 70 }
        remaining_in_segment: { type: integer, example: 42 }
        estimated_cost: { type: number, format: float, example: 2.80 }

    SMSSenderID:
      type: object
      required: [id, account_id, name, status, operators, created_at, updated_at]
      properties:
        id: { type: string, example: "01J9SENDER0000000000000001" }
        account_id: { type: string }
        name: { type: string, example: "MYBRAND" }
        status:
          type: string
          enum: [pending, approved, rejected, suspended]
          example: "approved"
        rejection_reason: { type: [string, "null"] }
        operators:
          type: array
          items: { type: string }
          example: ["mts", "megafon", "beeline", "t2"]
        comment: { type: [string, "null"], example: "Торговый знак №123456" }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SMSSenderIDInput:
      type: object
      required: [name]
      properties:
        name:
          type: string
          example: "MYBRAND"
          description: Alphanumeric sender ID (3-11 characters, Latin letters, numbers, dot, hyphen)
        operators:
          type: array
          items: { type: string }
          description: Target mobile network operators (defaults to all)
          example: ["mts", "megafon", "beeline", "t2"]
        comment:
          type: string
          example: "Свидетельство на товарный знак №789123"

    TelegramBot:
      type: object
      required: [id, account_id, username, first_name, status, is_default, created_at, updated_at]
      properties:
        id: { type: string, example: "01J9TGBOT0000000000000001" }
        account_id: { type: string }
        username: { type: string, example: "EspNotificationBot" }
        first_name: { type: string, example: "ESP Notifications" }
        status:
          type: string
          enum: [active, invalid_token, paused]
          example: "active"
        is_default: { type: boolean, example: true }
        webhook_url: { type: [string, "null"], example: "https://api.esp-platform.com/v1/telegram/webhooks/01J9TGBOT0000000000000001" }
        deep_link: { type: string, example: "https://t.me/EspNotificationBot?start=" }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    TelegramBotInput:
      type: object
      required: [token]
      properties:
        token:
          type: string
          description: Bot token from @BotFather
          example: "123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ"
        is_default:
          type: boolean
          default: false
          example: true

    TelegramSubscriber:
      type: object
      required: [id, account_id, bot_id, chat_id, status, created_at, updated_at]
      properties:
        id: { type: string, example: "01J9TGUSER000000000000001" }
        account_id: { type: string }
        bot_id: { type: string, example: "01J9TGBOT0000000000000001" }
        chat_id: { type: integer, format: int64, example: 987654321 }
        contact_id: { type: [string, "null"], example: "01M45KVSBJZXKFA3HXKGEFF6XZ" }
        username: { type: [string, "null"], example: "alex_marketer" }
        first_name: { type: [string, "null"], example: "Алексей" }
        last_name: { type: [string, "null"], example: "Смирнов" }
        language_code: { type: [string, "null"], example: "ru" }
        status:
          type: string
          enum: [active, unsubscribed, blocked_bot]
          example: "active"
        source: { type: [string, "null"], example: "deep_link" }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    TelegramInlineButton:
      type: object
      required: [text]
      properties:
        text: { type: string, example: "Открыть в браузере" }
        url: { type: string, example: "https://example.com/order/123" }
        callback_data: { type: string, example: "confirm_order_123" }

    TelegramSendRequest:
      type: object
      required: [text]
      properties:
        bot_id:
          type: string
          description: Specific bot ID to send from (defaults to default bot)
        chat_id:
          type: integer
          format: int64
          description: Telegram Chat ID of recipient
          example: 987654321
        contact_id:
          type: string
          description: ESP contact ID (if already bound to Telegram)
          example: "01M45KVSBJZXKFA3HXKGEFF6XZ"
        text:
          type: string
          description: Message text (supports HTML or MarkdownV2 formatting)
          example: "<b>Ваш заказ #1059 принят!</b>\nКурьер прибудет к 18:00."
        parse_mode:
          type: string
          enum: [HTML, MarkdownV2, Plain]
          default: HTML
          example: "HTML"
        photo_url:
          type: string
          description: Optional photo/banner URL attached to message
          example: "https://cdn.example.com/receipt.jpg"
        inline_buttons:
          type: array
          description: 2D array representing rows of inline buttons
          items:
            type: array
            items: { $ref: "#/components/schemas/TelegramInlineButton" }

    TelegramBroadcastRequest:
      type: object
      required: [text]
      properties:
        bot_id:
          type: string
          description: Specific bot ID to broadcast from
        text:
          type: string
          description: Broadcast message text
          example: "🔥 <b>Скидки до 40% на все категории!</b>\nТолько в эти выходные."
        parse_mode:
          type: string
          enum: [HTML, MarkdownV2, Plain]
          default: HTML
        photo_url:
          type: string
          description: Optional image URL
        inline_buttons:
          type: array
          items:
            type: array
            items: { $ref: "#/components/schemas/TelegramInlineButton" }
        tag:
          type: string
          description: Optional segment or tag filter
          example: "promo"

    TelegramBroadcastResponse:
      type: object
      required: [total_subscribers, queued, sent, failed]
      properties:
        total_subscribers: { type: integer, example: 1250 }
        queued: { type: integer, example: 1250 }
        sent: { type: integer, example: 1245 }
        failed: { type: integer, example: 5 }

    TelegramMessage:
      type: object
      required: [id, account_id, bot_id, chat_id, text, status, created_at]
      properties:
        id: { type: string, example: "01J9TGMESG0000000000000001" }
        account_id: { type: string }
        bot_id: { type: string }
        chat_id: { type: integer, format: int64, example: 987654321 }
        subscriber_id: { type: [string, "null"] }
        telegram_message_id: { type: [integer, "null"], format: int64, example: 45012 }
        parse_mode: { type: string, example: "HTML" }
        text: { type: string }
        inline_buttons:
          type: array
          items:
            type: array
            items: { $ref: "#/components/schemas/TelegramInlineButton" }
        status:
          type: string
          enum: [pending, sent, delivered, failed, blocked_by_user]
          example: "delivered"
        error_message: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
        sent_at: { type: [string, "null"], format: date-time }

    # ---------------- Integrations (CRM & No-Code platforms) [INT-03] ----------------
    CRMProvider:
      type: string
      enum: [amocrm, bitrix24_crm, retailcrm, albato, apixdrive, zapier, make]
      example: "amocrm"

    CRMConnectionStatus:
      type: string
      enum: [active, paused, error]
      example: "active"

    CRMFieldMapping:
      type: object
      properties:
        email: { type: string, example: "EMAIL" }
        first_name: { type: string, example: "NAME" }
        last_name: { type: string, example: "LAST_NAME" }
        phone: { type: string, example: "PHONE" }
        custom_fields:
          type: object
          additionalProperties: { type: string }
          example: { "city": "CITY", "order_sum": "OPPORTUNITY" }

    CRMConnection:
      type: object
      required: [id, account_id, provider, name, status, webhook_url, created_at, updated_at]
      properties:
        id: { type: string, example: "01J9CRMCONN00000000000001" }
        account_id: { type: string }
        provider: { $ref: "#/components/schemas/CRMProvider" }
        name: { type: string, example: "amoCRM Основная воронка" }
        list_id: { type: [string, "null"], example: "01J9LIST0000000000000001" }
        status: { $ref: "#/components/schemas/CRMConnectionStatus" }
        webhook_url: { type: string, example: "http://localhost:18080/v1/integrations/crm/webhooks/amocrm?token=crm_sec_01J9..." }
        token_preview: { type: string, example: "crm_sec_***abc" }
        field_mapping: { $ref: "#/components/schemas/CRMFieldMapping" }
        settings:
          type: object
          properties:
            auto_subscribe: { type: boolean, example: true }
            lead_pipeline_id: { type: string, example: "748291" }
            trigger_stage_id: { type: string, example: "142" }
            tag_on_import: { type: string, example: "crm_lead" }
        sync_count: { type: integer, example: 142 }
        last_sync_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CRMConnectionInput:
      type: object
      required: [provider, name]
      properties:
        provider: { $ref: "#/components/schemas/CRMProvider" }
        name: { type: string, example: "Битрикс24 Синхронизация лидов" }
        list_id: { type: string }
        field_mapping: { $ref: "#/components/schemas/CRMFieldMapping" }
        settings: { type: object }

    CRMSyncEvent:
      type: object
      required: [id, account_id, provider, event_type, status, created_at]
      properties:
        id: { type: string, example: "01J9CRMEVT00000000000001" }
        account_id: { type: string }
        connection_id: { type: [string, "null"] }
        provider: { $ref: "#/components/schemas/CRMProvider" }
        event_type: { type: string, example: "contact.add" }
        status:
          type: string
          enum: [processed, ignored, failed]
          example: "processed"
        contact_email: { type: [string, "null"], example: "lead@example.com" }
        contact_id: { type: [string, "null"] }
        action_taken: { type: string, example: "contact_created" }
        payload: { type: object }
        error_message: { type: [string, "null"] }
        created_at: { type: string, format: date-time }

    CRMSimulateRequest:
      type: object
      required: [provider, event_type, email]
      properties:
        provider: { $ref: "#/components/schemas/CRMProvider" }
        connection_id: { type: string }
        event_type: { type: string, example: "lead.stage_change" }
        email: { type: string, format: email, example: "alex@client.ru" }
        first_name: { type: string, example: "Алексей" }
        last_name: { type: string, example: "Смирнов" }
        phone: { type: string, example: "+79991234567" }
        deal_title: { type: string, example: "Заказ №4510" }
        deal_status: { type: string, example: "won" }
        deal_amount: { type: number, example: 15400.0 }
        custom_fields:
          type: object
          additionalProperties: { type: string }

    CRMSimulateResponse:
      type: object
      required: [success, event_id, action_taken]
      properties:
        success: { type: boolean, example: true }
        event_id: { type: string }
        contact_id: { type: [string, "null"] }
        action_taken: { type: string, example: "contact_created" }
        details: { type: string, example: "Контакт alex@client.ru успешно создан и привязан к списку" }

    # ---------------- Migrations (Unisender, SendPulse, Mailchimp) [INT-04] ----------------
    CompetitorPlatform:
      type: string
      enum: [unisender, sendpulse, mailchimp]
      example: "unisender"

    MigrationJobStatus:
      type: string
      enum: [pending, running, completed, failed, cancelled]
      example: "completed"

    MigrationValidateInput:
      type: object
      required: [platform, api_key]
      properties:
        platform: { $ref: "#/components/schemas/CompetitorPlatform" }
        api_key: { type: string, example: "6abc1234567890abcdef1234567890ab" }
        api_secret: { type: string, description: "Required for SendPulse (client_secret)" }

    MigrationAvailableList:
      type: object
      required: [id, name, total_contacts]
      properties:
        id: { type: string, example: "1234567" }
        name: { type: string, example: "Основная база клиентов" }
        total_contacts: { type: integer, example: 5420 }

    MigrationAvailableTemplate:
      type: object
      required: [id, name]
      properties:
        id: { type: string, example: "tpl_9871" }
        name: { type: string, example: "Промо-рассылка Чёрная Пятница" }
        preview_url: { type: [string, "null"] }

    MigrationSummary:
      type: object
      required: [platform, valid, available_lists, available_templates]
      properties:
        platform: { $ref: "#/components/schemas/CompetitorPlatform" }
        valid: { type: boolean, example: true }
        account_email: { type: string, example: "owner@company.ru" }
        available_lists:
          type: array
          items: { $ref: "#/components/schemas/MigrationAvailableList" }
        available_templates:
          type: array
          items: { $ref: "#/components/schemas/MigrationAvailableTemplate" }

    MigrationJobInput:
      type: object
      required: [platform, api_key]
      properties:
        platform: { $ref: "#/components/schemas/CompetitorPlatform" }
        api_key: { type: string }
        api_secret: { type: string }
        import_lists: { type: boolean, default: true }
        list_ids:
          type: array
          items: { type: string }
          description: "Optional list of competitor list IDs to migrate (empty = all)"
        import_templates: { type: boolean, default: true }
        template_ids:
          type: array
          items: { type: string }
          description: "Optional list of template IDs to migrate (empty = all)"
        tag_imported: { type: string, default: "migrated" }
        auto_subscribe: { type: boolean, default: true }

    MigrationJob:
      type: object
      required: [id, account_id, source_platform, credentials_preview, status, total_items, processed_items, imported_contacts, imported_templates, errors_count, created_at, updated_at]
      properties:
        id: { type: string, example: "01J9MIGJOB000000000000001" }
        account_id: { type: string }
        source_platform: { $ref: "#/components/schemas/CompetitorPlatform" }
        credentials_preview: { type: string, example: "6abc***0ab" }
        status: { $ref: "#/components/schemas/MigrationJobStatus" }
        import_options: { type: object }
        total_items: { type: integer, example: 5430 }
        processed_items: { type: integer, example: 5430 }
        imported_contacts: { type: integer, example: 5420 }
        imported_templates: { type: integer, example: 10 }
        errors_count: { type: integer, example: 0 }
        error_log:
          type: array
          items: { type: string }
        started_at: { type: [string, "null"], format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    # --- ABS-06: ML Risk Model & Phishing Detection Schemas ---
    RiskFactorItem:
      type: object
      required: [code, impact, description]
      properties:
        code: { type: string, example: "volume_spike" }
        impact: { type: integer, example: 35 }
        description: { type: string, example: "Резкий скачок объема рассылки в 8 раз выше базового" }

    DetectedBrandItem:
      type: object
      required: [brand, official_domains, confidence]
      properties:
        brand: { type: string, example: "Сбер" }
        official_domains:
          type: array
          items: { type: string }
          example: ["sberbank.ru", "sber.ru"]
        confidence: { type: number, format: float, example: 0.95 }
        mismatch_domain: { type: string, example: "sber-security-check.xyz" }

    SuspiciousUrlItem:
      type: object
      required: [url, reason, severity]
      properties:
        url: { type: string, example: "http://sber-security-check.xyz/login" }
        anchor_text: { type: string, example: "Войти в СберБанк Онлайн" }
        reason: { type: string, example: "Несовпадение отображаемого текста ссылки и реального адреса перехода" }
        severity: { type: string, enum: [low, medium, high, critical], example: "high" }

    AccountRiskStatus:
      type: object
      required: [account_id, risk_score, risk_level, status, factors, metrics, ai_explanation, last_evaluated_at]
      properties:
        account_id: { type: string, example: "01H00000000000000000000000" }
        risk_score: { type: integer, minimum: 0, maximum: 100, example: 15 }
        risk_level: { type: string, enum: [low, medium, high, critical], example: "low" }
        status: { type: string, enum: [good_standing, under_review, restricted, blocked], example: "good_standing" }
        factors:
          type: array
          items: { $ref: "#/components/schemas/RiskFactorItem" }
        metrics:
          type: object
          properties:
            kyc_verified: { type: boolean, example: true }
            account_age_days: { type: integer, example: 45 }
            bounce_rate_pct: { type: number, format: float, example: 0.8 }
            complaint_rate_pct: { type: number, format: float, example: 0.02 }
            velocity_ratio: { type: number, format: float, example: 1.1 }
            verified_domains_count: { type: integer, example: 2 }
        ai_explanation: { type: string, example: "Поведение аккаунта штатное. Все домены подтверждены, спам-жалобы ниже порога 0.05%." }
        last_evaluated_at: { type: string, format: date-time }

    RiskEvaluationRequest:
      type: object
      required: [from_email, subject, html]
      properties:
        campaign_id: { type: string, nullable: true }
        from_email: { type: string, example: "security@company.ru" }
        from_name: { type: string, example: "Служба безопасности" }
        subject: { type: string, example: "Срочно подтвердите данные вашей учетной записи" }
        html: { type: string, example: "<p>Пройдите верификацию <a href='http://sber-sec.xyz'>здесь</a></p>" }
        text: { type: string }
        target_recipients_count: { type: integer, example: 10000 }

    RiskEvaluationResult:
      type: object
      required: [risk_score, risk_level, action_recommended, behavioral_score, phishing_score, brand_impersonation_score, domain_mismatch_score, detected_brands, suspicious_urls, risk_factors, ai_explanation]
      properties:
        assessment_id: { type: string, example: "01J9RISK000000000000000001" }
        risk_score: { type: integer, minimum: 0, maximum: 100, example: 88 }
        risk_level: { type: string, enum: [low, medium, high, critical], example: "high" }
        action_recommended: { type: string, enum: [allow, throttle, review, block], example: "block" }
        behavioral_score: { type: integer, example: 25 }
        phishing_score: { type: integer, example: 92 }
        brand_impersonation_score: { type: integer, example: 85 }
        domain_mismatch_score: { type: integer, example: 70 }
        detected_brands:
          type: array
          items: { $ref: "#/components/schemas/DetectedBrandItem" }
        suspicious_urls:
          type: array
          items: { $ref: "#/components/schemas/SuspiciousUrlItem" }
        risk_factors:
          type: array
          items: { $ref: "#/components/schemas/RiskFactorItem" }
        ai_explanation: { type: string, example: "Обнаружена попытка подделки бренда Сбербанк с домена не из белого списка." }

    AccountRiskAssessmentRecord:
      type: object
      required: [id, account_id, risk_score, risk_level, action_taken, behavioral_score, phishing_score, evaluated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        campaign_id: { type: string, nullable: true }
        risk_score: { type: integer }
        risk_level: { type: string, enum: [low, medium, high, critical] }
        action_taken: { type: string, enum: [allowed, throttled, blocked, flagged] }
        behavioral_score: { type: integer }
        phishing_score: { type: integer }
        brand_impersonation_score: { type: integer }
        domain_mismatch_score: { type: integer }
        detected_brands:
          type: array
          items: { $ref: "#/components/schemas/DetectedBrandItem" }
        suspicious_urls:
          type: array
          items: { $ref: "#/components/schemas/SuspiciousUrlItem" }
        risk_factors:
          type: array
          items: { $ref: "#/components/schemas/RiskFactorItem" }
        ai_summary: { type: string }
        evaluated_at: { type: string, format: date-time }
        overridden_by: { type: string, nullable: true }
        overridden_at: { type: string, nullable: true, format: date-time }

    # --- SEC-05: Penetration Testing & Bug Bounty Schemas ---
    BugBountyPolicy:
      type: object
      required: [program_name, safe_harbor, scope_in, scope_out, reward_tiers]
      properties:
        program_name: { type: string, example: "Antigravity ESP Bug Bounty & VDP" }
        safe_harbor: { type: string }
        scope_in:
          type: array
          items: { type: string }
        scope_out:
          type: array
          items: { type: string }
        reward_tiers:
          type: array
          items:
            type: object
            properties:
              severity: { type: string, enum: [critical, high, medium, low] }
              reward_range_rub: { type: string, example: "100 000 — 300 000 ₽" }
              description: { type: string }

    BugBountyReportInput:
      type: object
      required: [reporter_email, title, severity, category, target_endpoint, description, steps_to_reproduce]
      properties:
        reporter_email: { type: string, example: "researcher@whitehat.ru" }
        reporter_name: { type: string, example: "Алексей Смирнов" }
        title: { type: string, example: "Недостаточная изоляция кампаний в draft статусе" }
        severity: { type: string, enum: [critical, high, medium, low, info], example: "high" }
        category: { type: string, enum: [idor, injection, ssrf, auth_bypass, xss, rate_limit, other], example: "idor" }
        target_endpoint: { type: string, example: "POST /v1/campaigns/{id}/test" }
        description: { type: string, example: "Возможность отправить тест кампании..." }
        steps_to_reproduce: { type: string, example: "1. Выпустить API-ключ в аккаунте B..." }
        poc: { type: string, example: "curl -X POST ..." }

    BugBountyReport:
      type: object
      required: [id, reporter_email, title, severity, category, target_endpoint, status, reward_rub, created_at]
      properties:
        id: { type: string, example: "01J9BBR000000000000000001" }
        reporter_email: { type: string }
        reporter_name: { type: string }
        title: { type: string }
        severity: { type: string, enum: [critical, high, medium, low, info] }
        category: { type: string }
        target_endpoint: { type: string }
        description: { type: string }
        steps_to_reproduce: { type: string }
        poc: { type: string }
        status: { type: string, enum: [new, triaged, in_progress, resolved, rejected, duplicate] }
        reward_rub: { type: integer, example: 50000 }
        admin_notes: { type: string }
        triaged_by: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        resolved_at: { type: string, nullable: true, format: date-time }

    BugBountyTriageInput:
      type: object
      required: [status]
      properties:
        status: { type: string, enum: [new, triaged, in_progress, resolved, rejected, duplicate] }
        reward_rub: { type: integer, minimum: 0 }
        admin_notes: { type: string }

    PentestCheckResult:
      type: object
      required: [check_id, name, category, passed, description]
      properties:
        check_id: { type: string, example: "CHECK_IDOR_TENANT_ISOLATION" }
        name: { type: string, example: "Мультитенантная BOLA / IDOR изоляция ресурсов" }
        category: { type: string, example: "A01:2021-Broken Access Control" }
        passed: { type: boolean, example: true }
        description: { type: string }
        details: { type: string }

    PentestRunSummary:
      type: object
      required: [run_id, suite_name, total_tests, passed_tests, failed_tests, duration_ms, checks, overall_status]
      properties:
        run_id: { type: string, example: "01J9PENTEST0000000000001" }
        suite_name: { type: string, example: "OWASP ASVS & Pre-Launch Security Suite" }
        total_tests: { type: integer, example: 10 }
        passed_tests: { type: integer, example: 10 }
        failed_tests: { type: integer, example: 0 }
        duration_ms: { type: integer, example: 450 }
        checks:
          type: array
          items: { $ref: "#/components/schemas/PentestCheckResult" }
        overall_status: { type: string, enum: [passed, failed], example: "passed" }
        executed_at: { type: string, format: date-time }

    PentestRunRecord:
      type: object
      required: [id, suite_name, total_tests, passed_tests, failed_tests, executed_by, duration_ms, created_at]
      properties:
        id: { type: string }
        suite_name: { type: string }
        total_tests: { type: integer }
        passed_tests: { type: integer }
        failed_tests: { type: integer }
        vulnerabilities_found: { type: array, items: { type: object } }
        executed_by: { type: string }
        duration_ms: { type: integer }
        created_at: { type: string, format: date-time }

    DCRegion:
      type: object
      required: [id, name, provider, location, role, endpoint_url, health_status, replication_lag_bytes, replication_lag_sec, last_heartbeat_at]
      properties:
        id: { type: string, example: "dc-msk-01" }
        name: { type: string, example: "Selectel Москва (Берзарина, MSK-1)" }
        provider: { type: string, example: "Selectel" }
        location: { type: string, example: "Москва, Россия" }
        role: { type: string, enum: [primary, standby, fenced, maintenance], example: "primary" }
        endpoint_url: { type: string, example: "https://msk01.mail.esp.local" }
        health_status: { type: string, enum: [healthy, degraded, unreachable, unknown], example: "healthy" }
        replication_lag_bytes: { type: integer, example: 0 }
        replication_lag_sec: { type: number, format: double, example: 0.0 }
        last_heartbeat_at: { type: string, format: date-time }

    MultiDCClusterStatus:
      type: object
      required: [primary_region, cluster_mode, total_regions, healthy_regions, replication_healthy, regions]
      properties:
        primary_region: { type: string, example: "dc-msk-01" }
        cluster_mode: { type: string, enum: [active_passive, active_active_partitioned, degraded], example: "active_passive" }
        total_regions: { type: integer, example: 3 }
        healthy_regions: { type: integer, example: 3 }
        replication_healthy: { type: boolean, example: true }
        max_replication_lag_sec: { type: number, format: double, example: 0.25 }
        regions:
          type: array
          items:
            $ref: "#/components/schemas/DCRegion"

    DCSwitchoverInput:
      type: object
      required: [target_region]
      properties:
        target_region: { type: string, example: "dc-spb-01" }
        drain_timeout_sec: { type: integer, default: 30, example: 30 }
        force: { type: boolean, default: false }

    DRDrillStepLog:
      type: object
      required: [step, status, duration_ms, details]
      properties:
        step: { type: string, example: "DC-1 Fencing & Health Check" }
        status: { type: string, enum: [passed, failed, skipped], example: "passed" }
        duration_ms: { type: integer, example: 340 }
        details: { type: string, example: "Replication catchup confirmed, RPO = 0 bytes" }

    DRDrillRunInput:
      type: object
      properties:
        drill_type: { type: string, enum: [failover_simulation, network_partition_fence, backup_restore_drill, read_replica_takeover], default: "failover_simulation" }
        target_region: { type: string, example: "dc-spb-01" }

    DRDrillRun:
      type: object
      required: [id, drill_type, initiated_by, primary_region, target_region, status, actual_rto_sec, target_rto_sec, actual_rpo_bytes, step_logs, started_at]
      properties:
        id: { type: string, example: "dr_drill_2026_10_06_01" }
        drill_type: { type: string, example: "failover_simulation" }
        initiated_by: { type: string, example: "sre_lead" }
        primary_region: { type: string, example: "dc-msk-01" }
        target_region: { type: string, example: "dc-spb-01" }
        status: { type: string, enum: [in_progress, passed, failed, cancelled], example: "passed" }
        actual_rto_sec: { type: number, format: double, example: 14.2 }
        target_rto_sec: { type: number, format: double, example: 60.0 }
        actual_rpo_bytes: { type: integer, example: 0 }
        step_logs:
          type: array
          items:
            $ref: "#/components/schemas/DRDrillStepLog"
        error_message: { type: string, nullable: true }
        started_at: { type: string, format: date-time }
        completed_at: { type: string, format: date-time, nullable: true }

    # ---------------- AI Security Gateway Schemas (AI-01) ----------------
    PIIEntity:
      type: object
      required: [type, original, token]
      properties:
        type: { type: string, example: "email" }
        original: { type: string, example: "client@mail.ru" }
        token: { type: string, example: "[EMAIL_REF_A1_1]" }
        start_index: { type: integer }
        end_index: { type: integer }

    AISanitizeRequest:
      type: object
      required: [text]
      properties:
        text: { type: string, example: "Клиент: Иван, email: ivan@mail.ru" }
        account_id: { type: string }
        user_id: { type: string }

    AISanitizeResponse:
      type: object
      required: [original_length, sanitized_length, sanitized_text, entities, entity_count, mapping]
      properties:
        original_length: { type: integer, example: 34 }
        sanitized_length: { type: integer, example: 38 }
        sanitized_text: { type: string, example: "Клиент: Иван, email: [EMAIL_REF_A1_1]" }
        entities:
          type: array
          items: { $ref: "#/components/schemas/PIIEntity" }
        entity_count: { type: integer, example: 1 }
        mapping:
          type: object
          additionalProperties: { type: string }

    AIDesanitizeRequest:
      type: object
      required: [text, mapping]
      properties:
        text: { type: string, example: "Ответ отправлен на [EMAIL_REF_A1_1]" }
        mapping:
          type: object
          additionalProperties: { type: string }

    AIDesanitizeResponse:
      type: object
      required: [restored_text, restored_count]
      properties:
        restored_text: { type: string, example: "Ответ отправлен на ivan@mail.ru" }
        restored_count: { type: integer, example: 1 }

    AIPromptValidationRequest:
      type: object
      required: [prompt]
      properties:
        prompt: { type: string, example: "Составь текст письма" }
        account_id: { type: string }
        user_id: { type: string }

    AIPromptValidationResponse:
      type: object
      required: [is_safe, injection_detected, sql_blocked, risk_score, violations, sanitized_prompt, entities, status]
      properties:
        is_safe: { type: boolean, example: true }
        injection_detected: { type: boolean, example: false }
        sql_blocked: { type: boolean, example: false }
        risk_score: { type: number, format: double, example: 0.05 }
        violations:
          type: array
          items: { type: string }
        sanitized_prompt: { type: string }
        entities:
          type: array
          items: { $ref: "#/components/schemas/PIIEntity" }
        status: { type: string, enum: [clean, sanitized, blocked_injection, blocked_sql], example: "sanitized" }

    AICircuitBreaker:
      type: object
      required: [service_name, state, failure_count, failure_threshold, recovery_time_sec, total_requests, total_errors, updated_at]
      properties:
        service_name: { type: string, example: "llm_primary_openai" }
        state: { type: string, enum: [closed, half_open, open], example: "closed" }
        failure_count: { type: integer, example: 0 }
        failure_threshold: { type: integer, example: 5 }
        recovery_time_sec: { type: integer, example: 60 }
        last_failure_at: { type: string, format: date-time, nullable: true }
        opened_at: { type: string, format: date-time, nullable: true }
        total_requests: { type: integer, example: 1420 }
        total_errors: { type: integer, example: 3 }
        updated_at: { type: string, format: date-time }

    AIAuditLog:
      type: object
      required: [id, account_id, action, prompt_preview, entities_detected, pii_sanitized, injection_detected, sql_ddl_blocked, circuit_breaker_tripped, status, risk_score, tokens_estimated, latency_ms, created_at]
      properties:
        id: { type: string, example: "ai_sec_sample_01" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        user_id: { type: string, nullable: true }
        action: { type: string, example: "validate_prompt" }
        prompt_preview: { type: string }
        entities_detected:
          type: array
          items: { $ref: "#/components/schemas/PIIEntity" }
        pii_sanitized: { type: boolean, example: true }
        injection_detected: { type: boolean, example: false }
        sql_ddl_blocked: { type: boolean, example: false }
        circuit_breaker_tripped: { type: boolean, example: false }
        status: { type: string, enum: [clean, sanitized, blocked_injection, blocked_sql, circuit_open, error], example: "sanitized" }
        risk_score: { type: number, format: double, example: 0.05 }
        tokens_estimated: { type: integer, example: 142 }
        latency_ms: { type: integer, example: 8 }
        client_ip: { type: string, nullable: true }
        created_at: { type: string, format: date-time }

    ProviderMetricSnapshot:
      type: object
      required: [id, account_id, provider, sent_count, delivered_count, bounce_rate, complaint_rate, deferral_rate, status, snapshot_time]
      properties:
        id: { type: string, example: "01M48FA8R96SMW71Z5TB32086K" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        provider: { type: string, example: "gmail" }
        sent_count: { type: integer, example: 45000 }
        delivered_count: { type: integer, example: 44780 }
        bounced_hard: { type: integer, example: 110 }
        bounced_soft: { type: integer, example: 110 }
        complained_count: { type: integer, example: 8 }
        deferred_count: { type: integer, example: 320 }
        bounce_rate: { type: number, format: double, example: 0.244 }
        complaint_rate: { type: number, format: double, example: 0.0178 }
        deferral_rate: { type: number, format: double, example: 0.711 }
        status: { type: string, enum: [healthy, degraded, critical], example: "healthy" }
        snapshot_time: { type: string, format: date-time }

    WarmupAdjustment:
      type: object
      required: [id, account_id, ip_address, domain, previous_volume, calculated_volume, adjustment_factor, reason, status, created_at]
      properties:
        id: { type: string, example: "01M48FA8SYMR2YA685XMG7MA53" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        ip_address: { type: string, example: "185.129.100.10" }
        domain: { type: string, example: "esp-platform.ru" }
        previous_volume: { type: integer, example: 5000 }
        calculated_volume: { type: integer, example: 6500 }
        adjustment_factor: { type: number, format: double, example: 1.30 }
        reason: { type: string, example: "Ускорение прогрева x1.3: отличные показатели доставляемости" }
        status: { type: string, enum: [applied, pending_approval, rejected], example: "applied" }
        applied_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }

    TimewebProvisionAction:
      type: object
      required: [id, account_id, timeweb_server_id, ip_address, ptr_record, pool_id, cost_rub, status, requires_operator_approval, created_at]
      properties:
        id: { type: string, example: "01M48FAEVMS85QEX80Y9KKR555" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        timeweb_server_id: { type: string, example: "tw-srv-prod-01" }
        ip_address: { type: string, example: "185.129.100.31" }
        ptr_record: { type: string, example: "mail7.esp-platform.ru" }
        pool_id: { type: string, example: "pool_shared_mkt" }
        cost_rub: { type: number, format: double, example: 160.00 }
        status: { type: string, enum: [ordered, ptr_configured, added_to_pool, failed, cancelled], example: "added_to_pool" }
        error_message: { type: string, nullable: true }
        requires_operator_approval: { type: boolean, example: false }
        approved_by: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        completed_at: { type: string, format: date-time, nullable: true }

    DelistingRequest:
      type: object
      required: [id, account_id, ip_address, blacklist_name, status, appeal_draft, created_at]
      properties:
        id: { type: string, example: "01M48FCZN5F7HH791P7RRCVCV5" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        ip_address: { type: string, example: "185.129.100.15" }
        blacklist_name: { type: string, example: "Spamhaus ZEN" }
        status: { type: string, enum: [quarantined, draft_ready, submitted, delisted, rejected], example: "draft_ready" }
        appeal_draft: { type: string }
        submitted_at: { type: string, format: date-time, nullable: true }
        delisted_at: { type: string, format: date-time, nullable: true }
        notes: { type: string, nullable: true }
        created_at: { type: string, format: date-time }

    AutopilotConfig:
      type: object
      required: [account_id, auto_warmup_enabled, auto_provision_enabled, max_ips_per_day, monthly_budget_rub, timeweb_server_id, ptr_zone, target_pool_id, default_warmup_plan_id, pool_utilization_threshold]
      properties:
        id: { type: string, example: "cfg_01" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        auto_warmup_enabled: { type: boolean, example: true }
        auto_provision_enabled: { type: boolean, example: false }
        max_ips_per_day: { type: integer, example: 2 }
        monthly_budget_rub: { type: number, format: double, example: 10000.00 }
        timeweb_server_id: { type: string, example: "tw-srv-prod-01" }
        ptr_zone: { type: string, example: "esp-platform.ru" }
        target_pool_id: { type: string, example: "pool_shared_mkt" }
        default_warmup_plan_id: { type: string, example: "standard_30d" }
        pool_utilization_threshold: { type: number, format: double, example: 80.00 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SREEvaluationResult:
      type: object
      required: [evaluated_at, provider_snapshots, warmup_adjustments, pool_utilization_pct, provision_triggered, quarantined_ips_count, summary_notice]
      properties:
        evaluated_at: { type: string, format: date-time }
        provider_snapshots:
          type: array
          items: { $ref: "#/components/schemas/ProviderMetricSnapshot" }
        warmup_adjustments:
          type: array
          items: { $ref: "#/components/schemas/WarmupAdjustment" }
        pool_utilization_pct: { type: number, format: double, example: 84.67 }
        provision_triggered: { type: boolean, example: false }
        provision_action: { $ref: "#/components/schemas/TimewebProvisionAction" }
        quarantined_ips_count: { type: integer, example: 0 }
        delistings_generated:
          type: array
          items: { $ref: "#/components/schemas/DelistingRequest" }
        summary_notice: { type: string, example: "Утилизация пула 84.7% превысила порог 80.0%." }

    OrderIPInput:
      type: object
      properties:
        server_id: { type: string, example: "tw-srv-prod-01" }
        pool_id: { type: string, example: "pool_shared_mkt" }
        ptr: { type: string, example: "mail9.esp-platform.ru" }

    AuditorAuditRequest:
      type: object
      required: [subject, html_content, from_email]
      properties:
        campaign_id: { type: string, example: "cmp_01" }
        subject: { type: string, example: "Новые предложения недели" }
        html_content: { type: string, example: "<p>Привет! <a href='{{unsubscribe_url}}'>Отписаться</a> ООО Ромашка, Москва</p>" }
        text_content: { type: string }
        from_email: { type: string, format: email, example: "marketing@company.ru" }
        recipient_count: { type: integer, example: 5000 }
        account_age_days: { type: integer, example: 14 }

    AuditorDecisionRequest:
      type: object
      properties:
        notes: { type: string, example: "Кампания проверена вручную, признаков скама нет." }

    AuditFinding:
      type: object
      required: [category, severity, description]
      properties:
        category: { type: string, example: "brand_impersonation" }
        severity: { type: string, enum: [low, medium, high, critical], example: "critical" }
        description: { type: string, example: "Обнаружены упоминания бренда Сбербанк при отправке с неавторизованного домена" }
        evidence: { type: string, example: "Бренд: Сбербанк, Отправитель: test@bad.com" }

    CampaignAuditReport:
      type: object
      required: [id, account_id, campaign_id, campaign_number, subject, verdict, score, phishing_risk, spam_risk, has_unsubscribe_link, has_physical_address, audited_links_count, suspicious_links_count, base_quality_score, findings, summary_notice, operator_decision, created_at]
      properties:
        id: { type: string, example: "01M48FXR2MMGZXMT63ZB15HJ7E" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        campaign_id: { type: string, example: "cmp_welc_01" }
        campaign_number: { type: integer, example: 1 }
        subject: { type: string, example: "Добро пожаловать в сервис" }
        verdict: { type: string, enum: [auto_approve, escalate_to_human, rejected], example: "auto_approve" }
        score: { type: integer, example: 94 }
        phishing_risk: { type: number, format: double, example: 0.0 }
        spam_risk: { type: number, format: double, example: 4.5 }
        has_unsubscribe_link: { type: boolean, example: true }
        has_physical_address: { type: boolean, example: true }
        audited_links_count: { type: integer, example: 2 }
        suspicious_links_count: { type: integer, example: 0 }
        base_quality_score: { type: number, format: double, example: 98.0 }
        findings:
          type: array
          items: { $ref: "#/components/schemas/AuditFinding" }
        summary_notice: { type: string, example: "Кампания успешно прошла автоматический аудит безопасности." }
        operator_decision: { type: string, enum: [pending, approved, rejected], example: "approved" }
        reviewed_by: { type: string, nullable: true }
        reviewed_at: { type: string, format: date-time, nullable: true }
        operator_notes: { type: string, nullable: true }
        created_at: { type: string, format: date-time }

    AuditorConfig:
      type: object
      required: [id, account_id, enabled, first_n_campaigns, auto_approve_threshold_score, dnsbl_check_enabled, require_physical_address, require_unsubscribe, created_at, updated_at]
      properties:
        id: { type: string, example: "cfg_auditor_01" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        enabled: { type: boolean, example: true }
        first_n_campaigns: { type: integer, example: 3 }
        auto_approve_threshold_score: { type: integer, example: 80 }
        dnsbl_check_enabled: { type: boolean, example: true }
        require_physical_address: { type: boolean, example: true }
        require_unsubscribe: { type: boolean, example: true }
        created_at: { type: string, format: date-time }
    SupportAnalyzeRequest:
      type: object
      required: [subject, message]
      properties:
        ticket_id: { type: string, example: "tkt_01" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        subject: { type: string, example: "Не уходят письма с домена shop.ru (Reg.ru)" }
        message: { type: string, example: "Почтовые службы отклоняют наши письма с ошибкой 550 5.7.1. Помогите настроить SPF и DKIM." }
        customer_email: { type: string, format: email, example: "admin@shop.ru" }

    SupportDNSDiagnoseRequest:
      type: object
      required: [domain]
      properties:
        domain: { type: string, example: "shop.ru" }
        registrar: { type: string, example: "regru" }

    DNSRecordSpec:
      type: object
      required: [type, host, value, ttl, explanation]
      properties:
        type: { type: string, example: "TXT" }
        host: { type: string, example: "@" }
        value: { type: string, example: "v=spf1 include:spf.esp-platform.ru ~all" }
        ttl: { type: integer, example: 3600 }
        explanation: { type: string, example: "SPF запись платформы" }

    DNSSetupInstruction:
      type: object
      required: [registrar, registrar_title, steps, required_records]
      properties:
        registrar: { type: string, example: "regru" }
        registrar_title: { type: string, example: "Reg.ru" }
        control_panel_url: { type: string, example: "https://www.reg.ru/user/account" }
        steps:
          type: array
          items: { type: string }
        required_records:
          type: array
          items: { $ref: "#/components/schemas/DNSRecordSpec" }

    SupportTicketAnalysis:
      type: object
      required: [id, account_id, category, priority, detected_domain, detected_registrar, is_critical, escalate_to_human, escalation_reason, confidence, dns_health, dns_score, action_taken, generated_reply, knowledge_articles, latency_ms, pii_entities_count, created_at]
      properties:
        id: { type: string, example: "01M48HWF2F8J1RARKF4PGF2Y6E" }
        ticket_id: { type: string, example: "tkt_01" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        category: { type: string, example: "dns_deliverability" }
        priority: { type: string, enum: [low, normal, high, urgent], example: "normal" }
        detected_domain: { type: string, example: "flowers-msk.ru" }
        detected_registrar: { type: string, example: "beget" }
        is_critical: { type: boolean, example: false }
        escalate_to_human: { type: boolean, example: false }
        escalation_reason: { type: string, example: "" }
        confidence: { type: number, format: double, example: 0.92 }
        dns_health: { type: string, enum: [healthy, warning, critical, unknown], example: "critical" }
        dns_score: { type: integer, example: 5 }
        action_taken: { type: string, enum: [auto_replied, escalated_to_human, skipped], example: "auto_replied" }
        generated_reply: { type: string, example: "Здравствуйте! Мы провели автоматическую DNS-диагностику..." }
        instructions:
          type: array
          items: { $ref: "#/components/schemas/DNSSetupInstruction" }
        knowledge_articles:
          type: array
          items: { type: string }
        latency_ms: { type: integer, example: 21 }
        pii_entities_count: { type: integer, example: 0 }
        created_at: { type: string, format: date-time }

    SupportConfig:
      type: object
      required: [id, account_id, enabled, auto_reply_enabled, auto_escalation_enabled, confidence_threshold, max_auto_replies_per_ticket, created_at, updated_at]
      properties:
        id: { type: string, example: "cfg_support_01" }
        account_id: { type: string, example: "00000000-0000-0000-0000-000000000001" }
        enabled: { type: boolean, example: true }
        auto_reply_enabled: { type: boolean, example: true }
        auto_escalation_enabled: { type: boolean, example: true }
        confidence_threshold: { type: number, format: double, example: 0.75 }
        max_auto_replies_per_ticket: { type: integer, example: 2 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    KnowledgeArticle:
      type: object
      required: [id, title, slug, content, tags, helpful_count, not_helpful_count, hit_count, created_at, updated_at]
      properties:
        id: { type: string }
        title: { type: string }
        slug: { type: string }
        content: { type: string }
        tags:
          type: array
          items: { type: string }
        helpful_count: { type: integer }
        not_helpful_count: { type: integer }
        hit_count: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    KnowledgeArticleInput:
      type: object
      required: [title, content]
      properties:
        title: { type: string }
        slug: { type: string }
        content: { type: string }
        tags:
          type: array
          items: { type: string }

    SemanticCacheEntry:
      type: object
      required: [id, query_pattern, answer_markdown, hit_count, created_at, updated_at]
      properties:
        id: { type: string }
        query_pattern: { type: string }
        answer_markdown: { type: string }
        category: { type: string }
        keywords:
          type: array
          items: { type: string }
        hit_count: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    SemanticCacheInput:
      type: object
      required: [query_pattern, answer_markdown]
      properties:
        query_pattern: { type: string }
        answer_markdown: { type: string }
        category: { type: string }
        keywords:
          type: array
          items: { type: string }

    GapRecommendation:
      type: object
      required: [id, topic, count, suggested_title, draft_content, status, created_at]
      properties:
        id: { type: string }
        topic: { type: string }
        count: { type: integer }
        suggested_title: { type: string }
        draft_content: { type: string }
        status: { type: string }
        created_at: { type: string, format: date-time }

    PromptTemplate:
      type: object
      required: [id, template_key, name, system_prompt, version, updated_at]
      properties:
        id: { type: string }
        template_key: { type: string }
        name: { type: string }
        system_prompt: { type: string }
        version: { type: integer }
        updated_at: { type: string, format: date-time }

    PromptTemplateInput:
      type: object
      required: [system_prompt]
      properties:
        system_prompt: { type: string }

    BrandColors:
      type: object
      required: [primary, secondary, background, card_background, text, muted_text, link]
      properties:
        primary: { type: string }
        secondary: { type: string }
        background: { type: string }
        card_background: { type: string }
        text: { type: string }
        muted_text: { type: string }
        link: { type: string }
        forbidden_colors:
          type: array
          items: { type: string }

    BrandTypography:
      type: object
      required: [font_family, heading_font]
      properties:
        font_family: { type: string }
        heading_font: { type: string }

    BrandFooterInfo:
      type: object
      properties:
        company_legal_name: { type: string }
        physical_address: { type: string }
        website_url: { type: string }
        social_links:
          type: array
          items:
            type: object
            properties:
              platform: { type: string }
              url: { type: string }

    Brandbook:
      type: object
      required: [account_id, brand_name, colors, typography, tone_of_voice, footer_info]
      properties:
        account_id: { type: string }
        brand_name: { type: string }
        logo_url: { type: string }
        colors: { $ref: "#/components/schemas/BrandColors" }
        typography: { $ref: "#/components/schemas/BrandTypography" }
        tone_of_voice: { type: string }
        footer_info: { $ref: "#/components/schemas/BrandFooterInfo" }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    BrandbookInput:
      type: object
      properties:
        brand_name: { type: string }
        logo_url: { type: string }
        colors: { $ref: "#/components/schemas/BrandColors" }
        typography: { $ref: "#/components/schemas/BrandTypography" }
        tone_of_voice: { type: string }
        footer_info: { $ref: "#/components/schemas/BrandFooterInfo" }

    EmailBlock:
      type: object
      required: [id, type, data]
      properties:
        id: { type: string }
        type: { type: string }
        data: { type: object }

    EmailColumn:
      type: object
      required: [id, width, blocks]
      properties:
        id: { type: string }
        width: { type: string }
        padding: { type: integer }
        backgroundColor: { type: string }
        blocks:
          type: array
          items: { $ref: "#/components/schemas/EmailBlock" }

    EmailSection:
      type: object
      required: [id, layout, columns]
      properties:
        id: { type: string }
        backgroundColor: { type: string }
        paddingTop: { type: integer }
        paddingBottom: { type: integer }
        paddingLeft: { type: integer }
        paddingRight: { type: integer }
        layout: { type: string }
        columns:
          type: array
          items: { $ref: "#/components/schemas/EmailColumn" }

    GlobalEmailStyles:
      type: object
      properties:
        backgroundColor: { type: string }
        contentBackgroundColor: { type: string }
        contentWidth: { type: integer }
        fontFamily: { type: string }
        textColor: { type: string }
        linkColor: { type: string }

    EmailDocument:
      type: object
      required: [id, subject, styles, sections]
      properties:
        id: { type: string }
        subject: { type: string }
        previewText: { type: string }
        styles: { $ref: "#/components/schemas/GlobalEmailStyles" }
        sections:
          type: array
          items: { $ref: "#/components/schemas/EmailSection" }

    StudioProject:
      type: object
      required: [id, account_id, name, document, html, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        name: { type: string }
        brief: { type: string }
        campaign_type: { type: string }
        subject_variants:
          type: array
          items: { type: string }
        document: { $ref: "#/components/schemas/EmailDocument" }
        html: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    StudioProjectInput:
      type: object
      required: [name, document, html]
      properties:
        name: { type: string }
        brief: { type: string }
        campaign_type: { type: string }
        subject_variants:
          type: array
          items: { type: string }
        document: { $ref: "#/components/schemas/EmailDocument" }
        html: { type: string }

    StudioGenerateRequest:
      type: object
      required: [brief]
      properties:
        brief: { type: string }
        campaign_type: { type: string }
        target_audience: { type: string }
        generate_image: { type: boolean }
        image_prompt_custom: { type: string }

    StudioGenerateResponse:
      type: object
      required: [subject_variants, preview_text, document, html, used_brandbook]
      properties:
        subject_variants:
          type: array
          items: { type: string }
        preview_text: { type: string }
        document: { $ref: "#/components/schemas/EmailDocument" }
        html: { type: string }
        generated_image_url: { type: string }
        generated_image_id: { type: string }
        used_brandbook: { $ref: "#/components/schemas/Brandbook" }

    StudioPatchRequest:
      type: object
      required: [document]
      properties:
        document: { $ref: "#/components/schemas/EmailDocument" }
        prompt: { type: string }
        patches:
          type: array
          items:
            type: object

    StudioPatchResponse:
      type: object
      required: [document, html, explanation]
      properties:
        document: { $ref: "#/components/schemas/EmailDocument" }
        html: { type: string }
        explanation: { type: string }
        patches_applied:
          type: array
          items:
            type: object

    StudioGenerateImageRequest:
      type: object
      required: [prompt]
      properties:
        prompt: { type: string }
        title_text: { type: string }
        badge_text: { type: string }
        aspect_ratio: { type: string }

    StudioGenerateImageResponse:
      type: object
      required: [media_id, url, filename, content_type, size, prompt]
      properties:
        media_id: { type: string }
        url: { type: string }
        filename: { type: string }
        content_type: { type: string }
        size: { type: integer }
        prompt: { type: string }

    # AI Email Assistant (EDT-07)
    AISubjectStrategy:
      type: string
      enum: [curiosity, urgency, benefit, personalized, minimal]

    AISubjectVariant:
      type: object
      required: [subject, strategy, strategy_title, predicted_open_rate, score, char_count, highlights]
      properties:
        subject: { type: string }
        strategy: { $ref: "#/components/schemas/AISubjectStrategy" }
        strategy_title: { type: string }
        predicted_open_rate: { type: number, format: float }
        score: { type: integer }
        char_count: { type: integer }
        highlights:
          type: array
          items: { type: string }

    AIGenerateSubjectsRequest:
      type: object
      required: [brief]
      properties:
        brief: { type: string }
        brand_name: { type: string }
        campaign_type: { type: string }
        target_audience: { type: string }
        preview_text: { type: string }

    AIGenerateSubjectsResponse:
      type: object
      required: [subjects, benchmark_open_rate, cached]
      properties:
        subjects:
          type: array
          items: { $ref: "#/components/schemas/AISubjectVariant" }
        recommended_subject: { type: string }
        suggested_preview_text: { type: string }
        benchmark_open_rate: { type: number, format: float }
        cached: { type: boolean }
        tokens_saved: { type: integer }

    AIScoreFactor:
      type: object
      required: [name, label, impact, score, description]
      properties:
        name: { type: string }
        label: { type: string }
        impact: { type: string, enum: [positive, neutral, negative] }
        score: { type: integer }
        description: { type: string }

    AIScoreSubjectRequest:
      type: object
      required: [subject]
      properties:
        subject: { type: string }
        preview_text: { type: string }
        industry: { type: string }

    AIScoreSubjectResponse:
      type: object
      required: [score, predicted_open_rate, industry_benchmark_rate, delta_rate, grade, factors, recommendations, cached]
      properties:
        subject: { type: string }
        preview_text: { type: string }
        score: { type: integer }
        predicted_open_rate: { type: number, format: float }
        industry_benchmark_rate: { type: number, format: float }
        delta_rate: { type: number, format: float }
        grade: { type: string, enum: [A, B, C, D, F] }
        char_count: { type: integer }
        word_count: { type: integer }
        factors:
          type: array
          items: { $ref: "#/components/schemas/AIScoreFactor" }
        recommendations:
          type: array
          items: { type: string }
        cached: { type: boolean }
        tokens_saved: { type: integer }

    AIGenerateTextRequest:
      type: object
      required: [prompt]
      properties:
        prompt: { type: string }
        type: { type: string, enum: [paragraph, headline, button_cta, bullet_points, intro] }
        tone: { type: string, enum: [friendly, professional, urgent, engaging, concise] }
        length: { type: string, enum: [short, medium, long] }

    AIGenerateTextResponse:
      type: object
      required: [text, cached]
      properties:
        text: { type: string }
        character_count: { type: integer }
        word_count: { type: integer }
        cached: { type: boolean }
        tokens_saved: { type: integer }

    AIRewriteTextRequest:
      type: object
      required: [text, tone]
      properties:
        text: { type: string }
        tone: { type: string, enum: [friendly, professional, urgent, engaging, concise] }
        mode: { type: string, enum: [change_tone, shorten, expand, fix_grammar, optimize_cta] }

    AIRewriteTextResponse:
      type: object
      required: [original_text, rewritten_text, tone, cached]
      properties:
        original_text: { type: string }
        rewritten_text: { type: string }
        tone: { type: string }
        mode: { type: string }
        cached: { type: boolean }
        tokens_saved: { type: integer }

    AIGenerateTemplateRequest:
      type: object
      required: [description]
      properties:
        description: { type: string }
        campaign_type: { type: string }
        brand_name: { type: string }
        primary_color: { type: string }

    AIGenerateTemplateResponse:
      type: object
      required: [document, html, subject_suggestions, cached]
      properties:
        document: { $ref: "#/components/schemas/EmailDocument" }
        html: { type: string }
        subject_suggestions:
          type: array
          items: { type: string }
        explanation: { type: string }
        cached: { type: boolean }

    AIBlockCapability:
      type: object
      required: [type, name, description, category, default_properties]
      properties:
        type: { type: string }
        name: { type: string }
        description: { type: string }
        category: { type: string }
        default_properties: { type: object }

    AIPromptTemplate:
      type: object
      required: [id, agent_type, template_key, name, system_prompt, variables, temperature, max_tokens, is_active, created_at, updated_at]
      properties:
        id: { type: string }
        agent_type: { type: string }
        template_key: { type: string }
        name: { type: string }
        system_prompt: { type: string }
        variables:
          type: array
          items: { type: string }
        temperature: { type: number }
        max_tokens: { type: integer }
        is_active: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    UpdateAIPromptTemplateInput:
      type: object
      properties:
        system_prompt: { type: string }
        temperature: { type: number }
        max_tokens: { type: integer }
        is_active: { type: boolean }

    AICacheStats:
      type: object
      required: [total_entries, total_hits, total_misses, hit_ratio, total_tokens_saved, estimated_cost_saved_usd]
      properties:
        total_entries: { type: integer }
        total_hits: { type: integer }
        total_misses: { type: integer }
        hit_ratio: { type: number, format: float }
        total_tokens_saved: { type: integer }
        estimated_cost_saved_usd: { type: number, format: float }

    ExternalRelay:
      type: object
      required: [id, name, provider, transport, enabled]
      properties:
        id: { type: string }
        account_id: { type: string, nullable: true }
        name: { type: string }
        provider: { type: string, enum: [unisender_go, smtp_generic] }
        enabled: { type: boolean }
        priority: { type: integer }
        transport: { type: string, enum: [http_api, smtp] }
        api_url: { type: string }
        smtp_host: { type: string }
        smtp_port: { type: integer }
        smtp_user: { type: string }
        monthly_limit: { type: integer }
        monthly_used: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    ExternalRelayInput:
      type: object
      required: [name, provider, transport]
      properties:
        account_id: { type: string, nullable: true }
        name: { type: string }
        provider: { type: string, enum: [unisender_go, smtp_generic] }
        enabled: { type: boolean }
        priority: { type: integer }
        transport: { type: string, enum: [http_api, smtp] }
        api_key: { type: string }
        api_url: { type: string }
        smtp_host: { type: string }
        smtp_port: { type: integer }
        smtp_user: { type: string }
        smtp_password: { type: string }
        monthly_limit: { type: integer }

    RelayRoutingRule:
      type: object
      required: [id, relay_id, rule_type, active]
      properties:
        id: { type: string }
        account_id: { type: string, nullable: true }
        relay_id: { type: string }
        rule_type: { type: string, enum: [warmup_spillover, quarantine_failover, bootstrap_all, split_percentage] }
        split_percentage: { type: integer }
        active: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    RelayRoutingRuleInput:
      type: object
      required: [relay_id, rule_type]
      properties:
        id: { type: string }
        account_id: { type: string, nullable: true }
        relay_id: { type: string }
        rule_type: { type: string, enum: [warmup_spillover, quarantine_failover, bootstrap_all, split_percentage] }
        split_percentage: { type: integer }
        active: { type: boolean }

    # ---------------- AMP for Email (TX-16) Schemas ----------------
    AMPValidateRequest:
      type: object
      required: [html]
      properties:
        html:
          type: string
          description: AMP HTML content to validate

    AMPValidationResult:
      type: object
      required: [valid, byte_size, css_byte_size, max_css_bytes]
      properties:
        valid: { type: boolean }
        errors: { type: array, items: { type: string } }
        warnings: { type: array, items: { type: string } }
        components: { type: array, items: { type: string } }
        byte_size: { type: integer }
        css_byte_size: { type: integer }
        max_css_bytes: { type: integer }

    AMPSampleTemplate:
      type: object
      required: [id, title, description, category, amp_html, html_fallback, text_fallback, components]
      properties:
        id: { type: string }
        title: { type: string }
        description: { type: string }
        category: { type: string }
        amp_html: { type: string }
        html_fallback: { type: string }
        text_fallback: { type: string }
        components: { type: array, items: { type: string } }

    AMPSamplesResponse:
      type: object
      required: [samples]
      properties:
        samples:
          type: array
          items: { $ref: "#/components/schemas/AMPSampleTemplate" }

    AMPFormSubmission:
      type: object
      required: [id, account_id, action_name, payload, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        campaign_id: { type: string, nullable: true }
        action_name: { type: string }
        sender_email: { type: string, nullable: true }
        source_origin: { type: string, nullable: true }
        payload: { type: object, additionalProperties: true }
        created_at: { type: string, format: date-time }

    AMPFormSubmissionsResponse:
      type: object
      required: [data, total]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/AMPFormSubmission" }
        total: { type: integer }

    # ---------------- Mailboxes (BOX-01) Schemas ----------------
    Mailbox:
      type: object
      required: [id, account_id, email, quota_bytes, quota_used_bytes, unread_count, total_count, active, created_at, updated_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        email: { type: string, format: email, example: "info@fitonik.ru" }
        quota_bytes: { type: integer }
        quota_used_bytes: { type: integer }
        unread_count: { type: integer }
        total_count: { type: integer }
        active: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateMailboxRequest:
      type: object
      required: [email, password]
      properties:
        email: { type: string, format: email, example: "info@fitonik.ru" }
        password: { type: string, minLength: 6, example: "SecretPass2026!" }
        quota_mb: { type: integer, default: 10240, example: 10240 }

    UpdateMailboxRequest:
      type: object
      properties:
        active: { type: boolean }
        quota_mb: { type: integer }

    MailboxMessage:
      type: object
      required: [id, from, to, subject, date, unread, size, snippet]
      properties:
        id: { type: string }
        from: { type: string }
        to: { type: string }
        subject: { type: string }
        date: { type: string, format: date-time }
        unread: { type: boolean }
        size: { type: integer }
        snippet: { type: string }

    MailboxMessageDetail:
      type: object
      required: [id, from, to, subject, date, unread, size, text_body, html_body, headers]
      properties:
        id: { type: string }
        from: { type: string }
        to: { type: string }
        subject: { type: string }
        date: { type: string, format: date-time }
        unread: { type: boolean }
        size: { type: integer }
        text_body: { type: string }
        html_body: { type: string }
        headers: { type: object, additionalProperties: { type: string } }

    # ---------------- Corporate Domains (BOX-03) Schemas ----------------
    CorporateDNSRecord:
      type: object
      required: [type, host, value]
      properties:
        type: { type: string, example: "MX" }
        host: { type: string, example: "@" }
        value: { type: string, example: "ems.fitonik.ru." }
        priority: { type: integer, example: 10 }
        purpose: { type: string, example: "Входящая почта (MX)" }
        status: { type: string, example: "valid" }
        error: { type: string, nullable: true }

    CorporateDomain:
      type: object
      required: [id, name, status, created_at]
      properties:
        id: { type: string }
        name: { type: string, example: "fitomik.app" }
        status: { type: string, enum: [pending, verified, failed] }
        is_corporate: { type: boolean }
        created_at: { type: string, format: date-time }
        verified_at: { type: string, format: date-time, nullable: true }
        checks:
          type: object
          properties:
            mx: { type: object, properties: { status: { type: string }, error: { type: string } } }
            spf: { type: object, properties: { status: { type: string }, error: { type: string } } }
            dkim: { type: object, properties: { status: { type: string }, error: { type: string } } }
            dmarc: { type: object, properties: { status: { type: string }, error: { type: string } } }
        dns_records:
          type: array
          items:
            $ref: "#/components/schemas/CorporateDNSRecord"

    # ---------------- Egress & Inbox Schemas (EGR-01, INBOX-01) ----------------
    EgressGateway:
      type: object
      required: [id, name, protocol, endpoint_url, target_domains, is_active, health_status, latency_ms]
      properties:
        id: { type: string }
        name: { type: string }
        protocol: { type: string, enum: [cloudflare_worker, https_proxy, socks5, vless_reality] }
        endpoint_url: { type: string }
        auth_token_masked: { type: string }
        target_domains: { type: array, items: { type: string } }
        is_active: { type: boolean }
        priority: { type: integer }
        latency_ms: { type: integer }
        health_status: { type: string, enum: [healthy, degraded, offline] }
        last_health_check_at: { type: string, format: date-time, nullable: true }
        error_message: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateEgressGatewayInput:
      type: object
      required: [name, protocol, endpoint_url, target_domains]
      properties:
        name: { type: string }
        protocol: { type: string, enum: [cloudflare_worker, https_proxy, socks5, vless_reality] }
        endpoint_url: { type: string }
        auth_token: { type: string }
        target_domains: { type: array, items: { type: string } }
        is_active: { type: boolean }
        priority: { type: integer }

    EgressCheckResult:
      type: object
      required: [gateway_id, health_status, latency_ms, tested_domain, checked_at]
      properties:
        gateway_id: { type: string }
        health_status: { type: string }
        latency_ms: { type: integer }
        tested_domain: { type: string }
        http_status: { type: integer }
        error_message: { type: string }
        checked_at: { type: string, format: date-time }

    InboxQuickButton:
      type: object
      required: [id, label, action]
      properties:
        id: { type: string }
        label: { type: string }
        action: { type: string, enum: [cdp, order, close, url, template] }
        value: { type: string }

    InboxSettings:
      type: object
      required: [account_id, platform, telegram_use_topics, max_use_threads]
      properties:
        account_id: { type: string }
        platform: { type: string, enum: [telegram, max, both] }
        telegram_bot_token_encrypted: { type: string }
        telegram_group_id: { type: integer }
        telegram_use_topics: { type: boolean }
        max_bot_token_encrypted: { type: string }
        max_channel_id: { type: string }
        max_use_threads: { type: boolean }
        quick_buttons:
          type: array
          items: { $ref: "#/components/schemas/InboxQuickButton" }
        auto_assign_enabled: { type: boolean }

    InboxThread:
      type: object
      required: [id, account_id, origin_channel, external_chat_id, status, last_message_at, created_at]
      properties:
        id: { type: string }
        account_id: { type: string }
        contact_id: { type: string, nullable: true }
        contact_name: { type: string, nullable: true }
        contact_phone: { type: string, nullable: true }
        contact_email: { type: string, nullable: true }
        origin_channel: { type: string }
        external_chat_id: { type: string }
        telegram_topic_id: { type: integer, nullable: true }
        max_thread_id: { type: string, nullable: true }
        status: { type: string, enum: [open, assigned, closed, snoozed] }
        assigned_operator: { type: string, nullable: true }
        last_message_text: { type: string, nullable: true }
        last_message_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    InboxMessage:
      type: object
      required: [id, thread_id, direction, sender_type, text, created_at]
      properties:
        id: { type: string }
        thread_id: { type: string }
        direction: { type: string, enum: [inbound, outbound] }
        sender_type: { type: string, enum: [client, operator, bot, system] }
        sender_name: { type: string, nullable: true }
        text: { type: string }
        relay_telegram_msg_id: { type: integer, nullable: true }
        relay_max_msg_id: { type: string, nullable: true }
        external_msg_id: { type: string, nullable: true }
        created_at: { type: string, format: date-time }

    InboundInboxPayload:
      type: object
      required: [text]
      properties:
        account_id: { type: string }
        contact_id: { type: string }
        contact_name: { type: string }
        contact_phone: { type: string }
        contact_email: { type: string }
        origin_channel: { type: string }
        external_chat_id: { type: string }
        text: { type: string }
        external_msg_id: { type: string }

