openapi: 3.0.3
info:
  title: SuperSend API
  version: "2.0"
  description: |
    The SuperSend API enables programmatic access to manage contacts, campaigns, teams, senders, and events.

    ## Base URL

    All API requests should be made to:
    ```
    https://api.supersend.io/v2
    ```

    ## Authentication

    Authenticate requests using your API key in the `Authorization` header:
    ```
    Authorization: Bearer YOUR_API_KEY
    ```

    ## Rate Limits

    - Standard: 100 requests/minute
    - Bulk operations: 10 requests/minute
    - List operations: 60 requests/minute

    ## Errors

    All errors follow a consistent format with `type`, `code`, `message`, and `request_id`.

  contact:
    name: SuperSend Support
    email: support@supersend.io
    url: https://supersend.io
  license:
    name: Proprietary
    url: https://supersend.io/terms

servers:
  - url: https://api.supersend.io/v2
    description: Production
  - url: https://staging-api.supersend.io/v2
    description: Staging

tags:
  - name: Health
    description: API health check endpoints
  - name: Contacts
    description: Manage contacts in campaigns
  - name: Campaigns
    description: Manage outreach campaigns
  - name: Teams
    description: Manage teams and team settings
  - name: Senders
    description: Manage email senders
  - name: Events
    description: Activity log and event tracking
  - name: Webhooks
    description: Manage webhooks and delivery logs
  - name: Conversations
    description: Manage conversations and send messages across email, LinkedIn, and Twitter
  - name: Mailboxes
    description: Purchase and manage mailboxes for domains
  - name: Domains
    description: Manage managed domains and purchase new domains
  - name: Intelligence
    description: Composite analysis endpoints for AI agents and in-app chat
  - name: Email validation
    description: Standalone SMTP email verification (consumes global email verification credits)

security:
  - BearerAuth: []

paths:
  /health:
    get:
      tags: [Health]
      summary: Health Check
      description: Returns API health status. Does not require authentication.
      operationId: getHealth
      security: []
      responses:
        "200":
          description: API is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                success: true
                data:
                  status: healthy
                  version: v2
                  timestamp: "2026-01-19T12:00:00.000Z"
                  uptime: 86400
                request_id: req_abc123def456

  # ==================== CONTACTS ====================
  /contacts:
    post:
      tags: [Contacts]
      summary: Create Contact
      description: |
        Creates a new contact or updates an existing one (upsert by email).
        Returns the created/updated contact with campaign assignment.
      operationId: createContact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateContactRequest"
            example:
              email: john@example.com
              first_name: John
              last_name: Doe
              company: Acme Corp
              TeamId: 550e8400-e29b-41d4-a716-446655440000
              CampaignId: 660e8400-e29b-41d4-a716-446655440001
      responses:
        "201":
          description: Contact created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactResponse"
        "200":
          description: Contact updated (existing contact)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

    get:
      tags: [Contacts]
      summary: List Contacts
      description: |
        Lists contacts with filtering, search, and pagination.
        Supports filtering by team, campaign, and status.
      operationId: listContacts
      parameters:
        - $ref: "#/components/parameters/TeamIdQuery"
        - $ref: "#/components/parameters/CampaignIdQuery"
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
        - name: search
          in: query
          description: Search by email, name, company
          schema:
            type: string
        - name: interest
          in: query
          description: |
            Filter by contact status (interest). Comma-separated for multiple values.
            Values: interested, not_interested, meeting_request, meeting_booked, customer, future, follow_up.
            Use empty string or null to filter for contacts with no status.
          schema:
            type: string
      responses:
        "200":
          description: List of contacts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

    delete:
      tags: [Contacts]
      summary: Delete Contact by Identifier
      description: |
        Deletes contacts by email or LinkedIn URL within a team and campaign.
        This is a soft delete operation.
      operationId: deleteContactByIdentifier
      parameters:
        - name: TeamId
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: CampaignId
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: email
          in: query
          description: Email address to delete (required if no linkedin_url)
          schema:
            type: string
            format: email
        - name: linkedin_url
          in: query
          description: LinkedIn URL to delete (required if no email)
          schema:
            type: string
            format: uri
      responses:
        "200":
          description: Contacts deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /contacts/{id}:
    get:
      tags: [Contacts]
      summary: Get Contact
      description: Retrieves a single contact by ID with campaign information.
      operationId: getContact
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Contact details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactResponse"
        "404":
          $ref: "#/components/responses/NotFound"

    patch:
      tags: [Contacts]
      summary: Update Contact
      description: Updates contact fields. Only provided fields are updated.
      operationId: updateContact
      parameters:
        - $ref: "#/components/parameters/IdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateContactRequest"
      responses:
        "200":
          description: Contact updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

    delete:
      tags: [Contacts]
      summary: Delete Contact
      description: Soft deletes a contact by ID.
      operationId: deleteContact
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Contact deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "404":
          $ref: "#/components/responses/NotFound"

  /contacts/bulk:
    post:
      tags: [Contacts]
      summary: Bulk Import Contacts
      description: |
        Import contacts in bulk via JSON array or CSV file upload.
        Processing happens asynchronously - returns an upload ID for tracking.

        **JSON Body:**
        ```json
        {
          "contacts": [
            { "email": "john@example.com", "first_name": "John" },
            { "email": "jane@example.com", "first_name": "Jane" }
          ],
          "TeamId": "uuid",
          "CampaignId": "uuid"
        }
        ```

        **CSV Upload:**
        Use `multipart/form-data` with:
        - `contacts`: CSV file
        - `TeamId`: Team UUID
        - `CampaignId`: Campaign UUID
        - `mapping_fields`: JSON mapping of CSV columns to contact fields
        - `validate_emails`: Optional. Set to `true` to run email verification (consumes credits). Default `false`.

        **Email Validation:**
        Set `validate_emails: true` to verify imported contacts for deliverability (consumes credits).
        Default is false to avoid surprise billing. Applies to both JSON and CSV uploads.

        **Limits:**
        - Maximum 50,000 contacts per request
        - Subject to your organization's contact limit
      operationId: bulkImportContacts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkContactRequest"
            example:
              contacts:
                - email: john@example.com
                  first_name: John
                  last_name: Doe
                  company_name: Acme Corp
                - email: jane@example.com
                  first_name: Jane
                  last_name: Smith
              TeamId: 550e8400-e29b-41d4-a716-446655440000
              CampaignId: 660e8400-e29b-41d4-a716-446655440001
          multipart/form-data:
            schema:
              type: object
              required:
                - contacts
                - TeamId
                - CampaignId
                - mapping_fields
              properties:
                contacts:
                  type: string
                  format: binary
                  description: CSV file with contacts
                TeamId:
                  type: string
                  format: uuid
                  description: Team ID
                CampaignId:
                  type: string
                  format: uuid
                  description: Campaign ID
                mapping_fields:
                  type: string
                  description: JSON object mapping CSV columns to contact fields
                validate_emails:
                  type: string
                  enum: ["true", "false"]
                  description: Set to "true" to run email verification (consumes credits). Default "false".
      responses:
        "202":
          description: Upload accepted for processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkContactResponse"
              example:
                success: true
                data:
                  upload_id: 770e8400-e29b-41d4-a716-446655440002
                  status: pending
                  contact_count: 500
                  message: Upload received. Processing will begin shortly.
                request_id: req_abc123def456
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  # ==================== CAMPAIGNS ====================
  /campaigns:
    post:
      tags: [Campaigns]
      summary: Create Campaign
      description: Creates a new campaign. Campaigns start in inactive/draft status.
      operationId: createCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCampaignRequest"
      responses:
        "201":
          description: Campaign created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignResponse"
        "400":
          $ref: "#/components/responses/BadRequest"

    get:
      tags: [Campaigns]
      summary: List Campaigns
      description: Lists campaigns with filtering and pagination.
      operationId: listCampaigns
      parameters:
        - $ref: "#/components/parameters/TeamIdQuery"
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
        - name: search
          in: query
          description: Search by campaign name
          schema:
            type: string
        - name: status
          in: query
          description: Filter by status
          schema:
            type: string
            enum: [active, inactive]
      responses:
        "200":
          description: List of campaigns
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignListResponse"

  /campaigns/{id}:
    get:
      tags: [Campaigns]
      summary: Get Campaign
      description: Retrieves a single campaign with messages and contact count.
      operationId: getCampaign
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Campaign details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignResponse"
        "404":
          $ref: "#/components/responses/NotFound"

    patch:
      tags: [Campaigns]
      summary: Update Campaign
      description: |
        Updates campaign fields. Only provided fields are updated.
        Some fields cannot be modified on active campaigns.
      operationId: updateCampaign
      parameters:
        - $ref: "#/components/parameters/IdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCampaignRequest"
      responses:
        "200":
          description: Campaign updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /campaigns/{id}/sequence:
    get:
      tags: [Campaigns]
      summary: Get Campaign Sequence
      description: |
        Retrieves the sequence (nodes and edges) for a campaign.
        This endpoint provides detailed access to the visual sequence builder structure.

        The sequence defines the automation flow including:
        - Start and stop nodes
        - Email, LinkedIn, Twitter action nodes
        - Wait/delay nodes
        - Conditional logic (If/Switch nodes)
        - HTTP API request nodes
        - Task and note nodes
      operationId: getCampaignSequence
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Campaign sequence
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignSequenceResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

    patch:
      tags: [Campaigns]
      summary: Update Campaign Sequence
      description: |
        Updates the sequence (nodes and/or edges) for a campaign.

        **Important notes:**
        - Providing `nodes` replaces ALL existing nodes
        - Providing `edges` replaces ALL existing edges
        - Omitting a field leaves it unchanged

        **Validation rules:**
        - If/Switch nodes cannot exceed 10 paths/cases
        - Each path cannot exceed 10 conditions
        - All node and edge IDs should be valid UUIDs
      operationId: updateCampaignSequence
      parameters:
        - $ref: "#/components/parameters/IdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSequenceRequest"
            example:
              nodes:
                - id: "550e8400-e29b-41d4-a716-446655440000"
                  type: startNode
                  data:
                    step_number: 0
                  position:
                    x: 100
                    y: 100
                - id: "550e8400-e29b-41d4-a716-446655440001"
                  type: emailNode
                  data:
                    step_number: 1
                    subject: "Hello {{first_name}}"
                    body: "Just reaching out..."
                  position:
                    x: 100
                    y: 250
              edges:
                - id: "edge-1"
                  source: "550e8400-e29b-41d4-a716-446655440000"
                  target: "550e8400-e29b-41d4-a716-446655440001"
                  type: buttonedge
      responses:
        "200":
          description: Sequence updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignSequenceResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  # ==================== TEAMS ====================
  /teams:
    post:
      tags: [Teams]
      summary: Create Team
      description: Creates a new team. The creating user is added as a member.
      operationId: createTeam
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTeamRequest"
      responses:
        "201":
          description: Team created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TeamResponse"
        "400":
          $ref: "#/components/responses/BadRequest"

    get:
      tags: [Teams]
      summary: List Teams
      description: |
        Lists teams the user has access to.
        Org admins can list all teams with `?all=true`.
      operationId: listTeams
      parameters:
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
        - name: search
          in: query
          description: Search by team name
          schema:
            type: string
        - name: all
          in: query
          description: List all org teams (admin only)
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: List of teams
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TeamListResponse"

  /teams/{id}:
    get:
      tags: [Teams]
      summary: Get Team
      description: Retrieves a team with member information.
      operationId: getTeam
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Team details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TeamDetailResponse"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

    patch:
      tags: [Teams]
      summary: Update Team
      description: Updates team settings. Only provided fields are updated.
      operationId: updateTeam
      parameters:
        - $ref: "#/components/parameters/IdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTeamRequest"
      responses:
        "200":
          description: Team updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TeamResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /teams/{id}/usage:
    get:
      tags: [Teams]
      summary: Get Team Usage
      description: Returns contact usage and capacity for a team.
      operationId: getTeamUsage
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Usage data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TeamUsageResponse"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  # ==================== EMAIL VALIDATION ====================
  /email-validation/verify:
    post:
      tags: [Email validation]
      summary: Verify email (SMTP)
      description: |
        Runs the same deliverability verification used for contacts (SMTP probe via SuperSend's validator).
        **Consumes global email verification credits** (same pool as contact validation and CSV import).

        Optional `TeamId` attributes the credit debit to a team for reporting; omit for org-level-only attribution.

        The response includes email intelligence fields (risk score, flags, provider hints, network metrics) in the
        same payload as the SMTP result—one verification credit unlocks both.
      operationId: verifyEmailAddress
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmailValidationVerifyRequest"
            example:
              email: prospect@example.com
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailValidationVerifyResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: Not enough email verification credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error:
                  type: payment_required_error
                  code: insufficient_credits
                  message: Not enough email verification credits available
                  details:
                    requiredCredits: 1
                    availableCredits: 0
                request_id: req_a1b2c3d4e5f6789012345678
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          description: Validator unavailable or incomplete response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # ==================== INTELLIGENCE ====================
  /intelligence/capacity-summary:
    get:
      tags: [Intelligence]
      summary: Capacity Summary
      description: |
        Returns capacity planning data for a team: campaign completion forecasts,
        allocated capacity, and whether more infrastructure is needed.
        Used by MCP tools and in-app chat.
      operationId: getCapacitySummary
      parameters:
        - name: teamId
          in: query
          required: true
          description: Team UUID (from list teams)
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          description: Page number
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          description: Max campaigns per page
          schema:
            type: integer
            default: 20
        - name: sortBy
          in: query
          description: Sort by name, contactsRemaining, or projectedCompletionDays
          schema:
            type: string
            default: projectedCompletionDays
        - name: sortDirection
          in: query
          description: asc or desc
          schema:
            type: string
            enum: [asc, desc]
            default: asc
      responses:
        "200":
          description: Capacity planning data
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    description: overview, campaigns, pagination, recommendations
                  request_id:
                    type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /intelligence/deliverability-summary:
    get:
      tags: [Intelligence]
      summary: Deliverability Summary
      description: |
        Returns deliverability diagnosis for a team: reply rate, bounce breakdown,
        placement test results, target mix (Gmail/Outlook/corporate), and recommendations.
        Use for questions like "Why is my reply rate low?"

        **Bounce data:** `bounce_categories` and `bounce_types` are both derived from **send/bounce
        events** (`Events.bounce_category` and `Events.bounce_subtype`). Each `bounce_types[]` row
        includes `category` from the event; `percentage` is the share of that subtype within its
        **category bucket** (so lines under a category sum to ~100% when subtypes are fully populated).
      operationId: getDeliverabilitySummary
      parameters:
        - name: teamId
          in: query
          required: true
          description: Team UUID (from list teams)
          schema:
            type: string
            format: uuid
        - name: windowDays
          in: query
          description: Days to look back (1-90, default 30)
          schema:
            type: integer
            minimum: 1
            maximum: 90
            default: 30
      responses:
        "200":
          description: Deliverability diagnosis data
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    description: summary, bounce_by_provider, placement, target_mix, recommendations
                  request_id:
                    type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /intelligence/sender-health-summary:
    get:
      tags: [Intelligence]
      summary: Sender Health Summary
      description: |
        Returns per-sender health metrics for a team: sends, bounce rate, and at-risk flags.
        **Bounce rate and `bounces` count invalid-recipient (list-quality) bounces only** — aligned
        with in-app sender health score and sender list bounce rate. Field **`all_bounces`** (per sender)
        and **`total_all_bounces`** (summary) include every bounced send (soft, policy, etc.) for context.
        Use for "Which senders are at risk?"
      operationId: getSenderHealthSummary
      parameters:
        - name: teamId
          in: query
          required: true
          description: Team UUID (from list teams)
          schema:
            type: string
            format: uuid
        - name: windowDays
          in: query
          description: Days to look back (1-90, default 30)
          schema:
            type: integer
            minimum: 1
            maximum: 90
            default: 30
      responses:
        "200":
          description: Sender health data
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /intelligence/outbound-summary:
    get:
      tags: [Intelligence]
      summary: Outbound Summary
      description: |
        Returns team-level outbound metrics and top campaigns. Use for
        "How's my outreach performing?"
      operationId: getOutboundSummary
      parameters:
        - name: teamId
          in: query
          required: true
          description: Team UUID (from list teams)
          schema:
            type: string
            format: uuid
        - name: windowDays
          in: query
          description: Days to look back (1-90, default 30)
          schema:
            type: integer
            minimum: 1
            maximum: 90
            default: 30
      responses:
        "200":
          description: Outbound summary data
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /intelligence/domain-health-summary:
    get:
      tags: [Intelligence]
      summary: Domain Health Summary
      description: |
        Returns per-domain health metrics for a team: SPF/DKIM/DMARC/MX status,
        blacklist status, and at-risk flags. Use for "Which domains have DNS or deliverability issues?"
      operationId: getDomainHealthSummary
      parameters:
        - name: teamId
          in: query
          required: true
          description: Team UUID (from list teams)
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Domain health data
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  # ==================== SENDERS ====================
  /senders:
    get:
      tags: [Senders]
      summary: List Senders
      description: Lists email senders with filtering and pagination.
      operationId: listSenders
      parameters:
        - $ref: "#/components/parameters/TeamIdQuery"
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
        - name: search
          in: query
          description: Search by email or send_as
          schema:
            type: string
        - name: provider
          in: query
          description: Filter by provider
          schema:
            type: string
            enum:
              [
                smtp,
                outlook,
                google,
                missioninbox,
                supersend-relay,
                ss-private-smtp,
                sendgrid,
                mailreef,
                infraforge,
                zoho,
                namecheap,
                hostinger,
                titan,
                ionos,
                dreamhost,
                aol,
                mailgun,
                aws,
                azure,
              ]
        - name: disabled
          in: query
          description: Filter by disabled status
          schema:
            type: string
            enum: ["true", "false"]
        - name: warm
          in: query
          description: Filter by warming status
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: List of senders
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SenderListResponse"

  /senders/{id}:
    get:
      tags: [Senders]
      summary: Get Sender
      description: Retrieves sender details including health and configuration.
      operationId: getSender
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Sender details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SenderResponse"
        "404":
          $ref: "#/components/responses/NotFound"

    patch:
      tags: [Senders]
      summary: Update Sender
      description: |
        Updates sender settings. Only provided fields are updated.
        Does not support changing provider or re-verifying credentials.
      operationId: updateSender
      parameters:
        - $ref: "#/components/parameters/IdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSenderRequest"
      responses:
        "200":
          description: Sender updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SenderResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  # ==================== EVENTS ====================
  /events:
    get:
      tags: [Events]
      summary: List Events
      description: |
        Lists activity events (sends, opens, clicks, replies, etc.)
        with filtering by team, campaign, contact, and event type.
      operationId: listEvents
      parameters:
        - $ref: "#/components/parameters/TeamIdQuery"
        - $ref: "#/components/parameters/CampaignIdQuery"
        - name: ContactId
          in: query
          description: Filter by contact ID
          schema:
            type: string
            format: uuid
        - name: SenderId
          in: query
          description: Filter by sender ID
          schema:
            type: string
            format: uuid
        - name: type
          in: query
          description: Filter by event type
          schema:
            type: string
            enum: [scheduled, sent, open, reply, click, bounce, unsubscribe]
        - name: channel
          in: query
          description: Filter by channel
          schema:
            type: string
            enum: [email, linkedin, twitter, text, call]
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
        - name: start_date
          in: query
          description: Filter events after this date (ISO 8601)
          schema:
            type: string
            format: date-time
        - name: end_date
          in: query
          description: Filter events before this date (ISO 8601)
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: List of events
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventListResponse"

  /events/{id}:
    get:
      tags: [Events]
      summary: Get Event
      description: Retrieves detailed information about a specific event.
      operationId: getEvent
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Event details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventResponse"
        "404":
          $ref: "#/components/responses/NotFound"

  # ==================== WEBHOOKS ====================
  /webhooks:
    get:
      tags: [Webhooks]
      summary: List Webhooks
      description: List all webhook integrations for a team.
      operationId: listWebhooks
      parameters:
        - name: team_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: Team ID
        - name: enabled
          in: query
          schema:
            type: boolean
          description: Filter by enabled status
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/offset"
      responses:
        "200":
          description: List of webhooks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /webhooks/{id}/deliveries:
    get:
      tags: [Webhooks]
      summary: List Webhook Deliveries
      description: |
        List webhook delivery history for a specific webhook integration.
        Returns delivery attempts with status, response details, and retry information.
      operationId: listWebhookDeliveries
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook integration ID
        - name: team_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: Team ID
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, delivered, failed, retrying]
          description: Filter by delivery status
        - name: event_type
          in: query
          schema:
            type: string
          description: Filter by event type (reply, open, click, etc.)
        - name: campaign_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by campaign ID
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/offset"
      responses:
        "200":
          description: List of webhook deliveries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /webhooks/{id}/deliveries/{deliveryId}:
    get:
      tags: [Webhooks]
      summary: Get Webhook Delivery
      description: |
        Get detailed information about a specific webhook delivery,
        including the full payload and response body.
      operationId: getWebhookDelivery
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook integration ID
        - name: deliveryId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Delivery ID
        - name: team_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: Team ID
      responses:
        "200":
          description: Webhook delivery details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /webhooks/{id}/deliveries/{deliveryId}/retry:
    post:
      tags: [Webhooks]
      summary: Retry Webhook Delivery
      description: |
        Manually retry a failed webhook delivery.
        Resets the attempt count and queues the delivery for immediate retry.
      operationId: retryWebhookDelivery
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook integration ID
        - name: deliveryId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Delivery ID to retry
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [team_id]
              properties:
                team_id:
                  type: string
                  format: uuid
                  description: Team ID
      responses:
        "200":
          description: Retry queued successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryRetryResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  # ==================== MAILBOXES ====================
  /mailboxes/purchase:
    post:
      tags: [Mailboxes]
      summary: Purchase Mailboxes
      description: Purchase mailboxes for existing domains. Domains must exist in the org.
      operationId: purchaseMailboxes
  # ==================== DOMAINS ====================
  /domains:
    get:
      tags: [Domains]
      summary: List Domains
      description: List managed domains with filtering and pagination.
      operationId: listDomains
      parameters:
        - name: team_id
          in: query
          schema:
            type: string
            format: uuid
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, purchasing, purchased, setting_up, active, purchase_failed, setup_failed, inactive, expired, cancelled]
        - name: managed
          in: query
          schema:
            type: string
            enum: [internal, external]
        - name: search
          in: query
          schema:
            type: string
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/offset"
      responses:
        "200":
          description: List of domains
        "401":
          $ref: "#/components/responses/Unauthorized"

  /domains/purchase:
    post:
      tags: [Domains]
      summary: Purchase Domains
      description: Purchase one or more domains. Domains are processed asynchronously.
      operationId: purchaseDomains
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domains, paymentMethodId, forwardingAddress, contactDetails]
              properties:
                domains:
                  type: array
                  items:
                    type: string
                  minItems: 1
                paymentMethodId:
                  type: string
                forwardingAddress:
                  type: string
                contactDetails:
                  type: object
                dmarcEmail:
                  type: string
      responses:
        "201":
          description: Purchase initiated
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /domains/purchase-with-mailboxes:
    post:
      tags: [Domains]
      summary: Purchase Domains and Mailboxes
      description: Purchase domains and/or mailboxes in one transaction.
      operationId: purchaseDomainsAndMailboxes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [paymentMethodId]
              properties:
                domains:
                  type: array
                  items:
                    type: string
                domainsWithProviders:
                  type: array
                  items:
                    type: object
                    required: [domain, provider]
                    properties:
                      domain:
                        type: string
                      provider:
                        type: string
                        enum: [google, outlook, smtp]
                mailboxes:
                  type: array
                  items:
                    type: object
                    required: [username, firstName, domain]
                    properties:
                      username:
                        type: string
                      firstName:
                        type: string
                      lastName:
                        type: string
                      domain:
                        type: string
                      signature:
                        type: string
                      provider:
                        type: string
                        enum: [google, outlook, smtp]
                  minItems: 1
                paymentMethodId:
                  type: string
                TeamId:
                  type: string
                  format: uuid
                forwardingAddress:
                  type: string
                dmarcEmail:
                  type: string
                contactDetails:
                  type: object
                saveContactAsDefault:
                  type: boolean
      responses:
        "201":
          description: Purchase initiated
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /domains/{id}:
    get:
      tags: [Domains]
      summary: Get Domain
      description: Get a single domain by ID.
      operationId: getDomain
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Domain details
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  # ==================== CONVERSATIONS ====================
  /conversations:
    get:
      tags: [Conversations]
      summary: List Conversations
      description: |
        List conversations from email inbox, LinkedIn inbox, or Twitter inbox with filtering and pagination.
        Use `channel` to filter by platform. Use `last_message_direction` to filter by who sent the last message:
        - `inbound` = replies from contacts (needs your response)
        - `outbound` = your sent messages (waiting on their reply)
      operationId: listConversations
      parameters:
        - name: team_id
          in: query
          description: Filter by team ID (UUID from list teams)
          schema:
            type: string
            format: uuid
        - name: channel
          in: query
          description: Which inbox to list (email, linkedin, or twitter)
          schema:
            type: string
            enum: [email, linkedin, twitter]
            default: linkedin
        - name: last_message_direction
          in: query
          description: Filter by who sent the last message; inbound = replies from contacts, outbound = your sent messages
          schema:
            type: string
            enum: [inbound, outbound]
        - name: identity_id
          in: query
          description: Filter by identity ID
          schema:
            type: string
            format: uuid
        - name: status
          in: query
          description: Filter by archive status
          schema:
            type: string
            enum: [archived, unarchived]
        - name: read_status
          in: query
          description: Filter by read status
          schema:
            type: string
            enum: [read, unread]
        - name: search
          in: query
          description: Search in conversation title, messages, or contact details
          schema:
            type: string
            maxLength: 100
        - name: sort_by
          in: query
          description: Sort field (default date)
          schema:
            type: string
            enum: [date]
            default: date
        - name: sort_direction
          in: query
          description: Sort order
          schema:
            type: string
            enum: [asc, desc]
            default: desc
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
      responses:
        "200":
          description: List of conversations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /conversations/{id}:
    get:
      tags: [Conversations]
      summary: Get Conversation
      description: Retrieves a conversation with recent messages (up to 50).
      operationId: getConversation
      parameters:
        - name: id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Conversation details with messages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

    patch:
      tags: [Conversations]
      summary: Update Conversation
      description: Update conversation status (archive, mark read, etc.).
      operationId: updateConversation
      parameters:
        - name: id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              minProperties: 1
              properties:
                is_archived:
                  type: boolean
                  description: Archive or unarchive conversation
                is_unread:
                  type: boolean
                  description: Mark as read or unread
                mark_all_read:
                  type: boolean
                  description: Mark all messages as read
      responses:
        "200":
          description: Conversation updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationUpdateResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /conversations/{id}/messages:
    get:
      tags: [Conversations]
      summary: Get Messages
      description: Get paginated messages in a conversation. Messages are returned oldest first.
      operationId: getConversationMessages
      parameters:
        - name: id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
      responses:
        "200":
          description: List of messages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

    post:
      tags: [Conversations]
      summary: Send Message
      description: |
        Send a message in a conversation. The endpoint automatically detects the
        conversation platform type and routes the message appropriately.

        **Platform-specific behavior:**
        - **Email**: Requires `sender_id`. Supports `subject`, `cc`, `bcc`, `attachments`, and `is_html`.
        - **LinkedIn**: Only requires `message`. Message is queued for delivery via LinkedIn.
        - **Twitter**: Not yet supported via API.

        The message is queued for async delivery. A temporary message is created
        immediately for optimistic UI updates, and the actual delivery happens
        in the background.
      operationId: sendConversationMessage
      parameters:
        - name: id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
                  minLength: 1
                  maxLength: 8000
                  description: Message content (required for all platforms)
                sender_id:
                  type: string
                  format: uuid
                  description: |
                    Sender/mailbox ID to send from (required for email conversations).
                    Must be an active sender in your organization.
                subject:
                  type: string
                  description: 'Email subject line (email only, defaults to "Re: {conversation title}")'
                is_html:
                  type: boolean
                  default: false
                  description: Whether the message is HTML formatted (email only)
                to:
                  type: string
                  format: email
                  description: Override recipient email address (email only, auto-selected if not provided)
                cc:
                  type: array
                  items:
                    type: string
                    format: email
                  description: CC recipients (email only)
                bcc:
                  type: array
                  items:
                    type: string
                    format: email
                  description: BCC recipients (email only)
                attachments:
                  type: array
                  items:
                    type: object
                  description: Email attachments (email only)
            examples:
              email_message:
                summary: Send email reply
                value:
                  message: "Thank you for your interest! I'd love to schedule a call."
                  sender_id: "123e4567-e89b-12d3-a456-426614174000"
                  subject: "Re: Partnership Opportunity"
                  is_html: false
              linkedin_message:
                summary: Send LinkedIn message
                value:
                  message: "Thanks for connecting! Would love to chat about your work."
      responses:
        "201":
          description: Message queued for delivery
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SendMessageResponse"
              examples:
                email_response:
                  summary: Email message response
                  value:
                    success: true
                    data:
                      object: message
                      id: "msg_abc123"
                      text: "Thank you for your interest!"
                      subject: "Re: Partnership Opportunity"
                      timestamp: "2026-01-28T12:00:00.000Z"
                      is_from_self: true
                      is_read: true
                      status: pending
                      job_id: "job_xyz789"
                      conversation_id: "conv_123"
                      platform_type: email
                      sender:
                        id: "123e4567-e89b-12d3-a456-426614174000"
                        email: "sales@company.com"
                    request_id: req_abc123
                linkedin_response:
                  summary: LinkedIn message response
                  value:
                    success: true
                    data:
                      object: message
                      id: "msg_abc123"
                      text: "Thanks for connecting!"
                      timestamp: "2026-01-28T12:00:00.000Z"
                      is_from_self: true
                      is_read: true
                      status: pending
                      job_id: "job_xyz789"
                      conversation_id: "conv_123"
                      platform_type: linkedin
                    request_id: req_abc123
        "400":
          description: Bad request (missing sender_id for email, Twitter not supported, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_sender:
                  summary: Missing sender_id for email
                  value:
                    error:
                      type: invalid_request_error
                      code: missing_required_parameter
                      message: "sender_id is required for email conversations"
                      param: sender_id
                      doc_url: "https://docs.supersend.io/docs/errors#missing_required_parameter"
                twitter_not_supported:
                  summary: Twitter not supported
                  value:
                    error:
                      type: invalid_request_error
                      code: feature_not_available
                      message: "Sending Twitter messages via API is not yet supported. Please use the SuperSend inbox to reply to Twitter conversations."
                      doc_url: "https://docs.supersend.io/docs/errors#feature_not_available"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          description: Message queue unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: API key from your SuperSend dashboard

  parameters:
    IdPath:
      name: id
      in: path
      required: true
      description: Resource ID (UUID)
      schema:
        type: string
        format: uuid

    TeamIdQuery:
      name: TeamId
      in: query
      description: Filter by team ID
      schema:
        type: string
        format: uuid

    CampaignIdQuery:
      name: CampaignId
      in: query
      description: Filter by campaign ID
      schema:
        type: string
        format: uuid

    LimitParam:
      name: limit
      in: query
      description: Number of items to return
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50

    OffsetParam:
      name: offset
      in: query
      description: Number of items to skip
      schema:
        type: integer
        minimum: 0
        default: 0

  responses:
    BadRequest:
      description: Bad Request - Invalid parameters
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              type: invalid_request_error
              code: missing_required_parameter
              message: CampaignId is required
              param: CampaignId
              doc_url: https://docs.supersend.io/errors#missing_required_parameter
            request_id: req_abc123def456

    Unauthorized:
      description: Unauthorized - Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              type: authentication_error
              code: invalid_api_key
              message: Invalid API key provided
              doc_url: https://docs.supersend.io/errors#invalid_api_key
            request_id: req_abc123def456

    Forbidden:
      description: Forbidden - Access denied
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              type: authorization_error
              code: team_access_denied
              message: You do not have access to this team
              doc_url: https://docs.supersend.io/errors#team_access_denied
            request_id: req_abc123def456

    NotFound:
      description: Not Found - Resource does not exist
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              type: not_found_error
              code: contact_not_found
              message: Contact not found
              doc_url: https://docs.supersend.io/errors#contact_not_found
            request_id: req_abc123def456

    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          description: Seconds to wait before retrying
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Request limit per minute
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Remaining requests this minute
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    # ===== Common =====
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        request_id:
          type: string
          example: req_abc123def456

    MessageResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            message:
              type: string

    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            type:
              type: string
              enum:
                [
                  invalid_request_error,
                  authentication_error,
                  authorization_error,
                  not_found_error,
                  rate_limit_error,
                  api_error,
                ]
            code:
              type: string
            message:
              type: string
            param:
              type: string
              nullable: true
            doc_url:
              type: string
              format: uri
        request_id:
          type: string

    Pagination:
      type: object
      properties:
        limit:
          type: integer
        offset:
          type: integer
        total:
          type: integer
        has_more:
          type: boolean

    # ===== Health =====
    HealthResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                status:
                  type: string
                  enum: [healthy]
                version:
                  type: string
                timestamp:
                  type: string
                  format: date-time
                uptime:
                  type: number

    EmailValidationVerifyRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
          description: Address to verify
        TeamId:
          type: string
          format: uuid
          description: Optional team UUID for credit transaction attribution

    EmailValidationVerifyResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          required: [email, valid, validators, credits_used]
          properties:
            email:
              type: string
              format: email
            valid:
              type: boolean
              description: Whether the SMTP probe considers the mailbox deliverable
            validators:
              type: object
              description: Raw validator output (e.g. smtp)
              additionalProperties: true
            credits_used:
              type: integer
              description: Global credits debited for this call (0 if org cost is configured as 0)
            email_hash:
              type: string
              description: SHA-256 hash of the normalised email address
            domain:
              type: string
              description: Domain portion of the email address
            is_role_based:
              type: boolean
              description: True if the address is a role-based alias (info@, support@, etc.)
            is_abuse_email:
              type: boolean
              description: True if the address is associated with abuse or spam
            is_free_provider:
              type: boolean
              description: True if the domain is a free email provider (gmail, yahoo, etc.)
            is_disallowed:
              type: boolean
              description: True if the address is on a global disallow list (disposable, etc.)
            validity_score:
              type: integer
              minimum: 0
              maximum: 100
              description: Aggregate deliverability score (0 = worst, 100 = best)
            risk_level:
              type: string
              enum: [LOW, MEDIUM, HIGH, CRITICAL]
              description: Risk categorisation derived from validity_score
            confidence:
              type: string
              enum: [NONE, LOW, MEDIUM, HIGH]
              description: Confidence level based on historical send volume
            email_provider:
              type: string
              nullable: true
              description: Detected ESP or mailbox provider (e.g. Google Workspace, Microsoft 365)
            email_security_service:
              type: string
              nullable: true
              description: Detected email security gateway (e.g. Proofpoint, Mimecast)
            bounce_type_breakdown:
              type: object
              nullable: true
              description: Historical breakdown of bounce types seen across the SuperSend network
              additionalProperties: true
            total_sends:
              type: integer
              description: Total sends observed across the SuperSend network for this address
            hard_bounce_count:
              type: integer
              description: Hard bounces observed across the network
            reply_count:
              type: integer
              description: Replies observed across the network
            has_historical_data:
              type: boolean
              description: Whether historical delivery data exists for this address
        request_id:
          type: string

    # ===== Contact =====
    Contact:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        first_name:
          type: string
        last_name:
          type: string
        company:
          type: string
        title:
          type: string
        linkedin_url:
          type: string
          format: uri
        twitter:
          type: string
        phone:
          type: string
        website:
          type: string
        interest:
          type: string
          description: Contact status - interested, not_interested, meeting_request, meeting_booked, customer, future, follow_up
        finished:
          type: boolean
        deleted:
          type: boolean
        custom:
          type: object
          additionalProperties: true
        team_id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CreateContactRequest:
      type: object
      required:
        - TeamId
        - CampaignId
      properties:
        email:
          type: string
          format: email
        linkedin_url:
          type: string
          format: uri
        first_name:
          type: string
        last_name:
          type: string
        company:
          type: string
        title:
          type: string
        phone:
          type: string
        website:
          type: string
        twitter:
          type: string
        custom:
          type: object
          additionalProperties: true
        TeamId:
          type: string
          format: uuid
        CampaignId:
          type: string
          format: uuid

    UpdateContactRequest:
      type: object
      properties:
        first_name:
          type: string
        last_name:
          type: string
        company:
          type: string
        title:
          type: string
        phone:
          type: string
        website:
          type: string
        interest:
          type: string
          description: Contact status - interested, not_interested, meeting_request, meeting_booked, customer, future, follow_up
        custom:
          type: object
          additionalProperties: true

    ContactResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/Contact"

    ContactListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Contact"
            pagination:
              $ref: "#/components/schemas/Pagination"

    # ===== Conversations =====
    ConversationMessage:
      type: object
      properties:
        object:
          type: string
          enum: [message]
        id:
          type: string
          format: uuid
        text:
          type: string
          nullable: true
        html:
          type: string
          nullable: true
        subject:
          type: string
          nullable: true
        timestamp:
          type: string
          format: date-time
        is_from_self:
          type: boolean
        is_read:
          type: boolean
        status:
          type: string
          enum: [delivered, pending, failed]
        job_id:
          type: string
          nullable: true
        conversation_id:
          type: string
          format: uuid
        platform_type:
          type: string
          enum: [email, linkedin, twitter]
        sender:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            email:
              type: string
              format: email

    SendMessageResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/ConversationMessage"

    ConversationParticipant:
      type: object
      description: A participant in a conversation (from ConversationParticipants + PlatformPersonProfile)
      properties:
        id:
          type: string
          format: uuid
          nullable: true
          description: Platform person profile ID (null if profile unavailable)
        display_name:
          type: string
          nullable: true
          description: Display name for the participant
        avatar_url:
          type: string
          format: uri
          nullable: true
          description: Avatar/profile image URL
        username:
          type: string
          nullable: true
          description: Email address (email) or platform handle (LinkedIn/Twitter)
        is_self:
          type: boolean
          description: Whether this participant is the authenticated user/team member

    Conversation:
      type: object
      description: Conversation object in list/detail responses
      properties:
        object:
          type: string
          enum: [conversation]
        id:
          type: string
          format: uuid
        platform_type:
          type: string
          enum: [email, linkedin, twitter]
        title:
          type: string
        last_activity_at:
          type: string
          format: date-time
        is_unread:
          type: boolean
        is_archived:
          type: boolean
        unread_count:
          type: integer
        identity:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            handle:
              type: string
            first_name:
              type: string
            last_name:
              type: string
            photo:
              type: string
              format: uri
            type:
              type: string
              enum: [linkedin, twitter]
        participants:
          type: array
          description: 'Participants in the conversation (populated in GET /conversations/{id}; empty in list)'
          items:
            $ref: "#/components/schemas/ConversationParticipant"
        last_message:
          type: object
          nullable: true
          properties:
            text:
              type: string
            timestamp:
              type: string
              format: date-time
        team_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ConversationListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Conversation"
            pagination:
              $ref: "#/components/schemas/Pagination"

    ConversationResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              allOf:
                - $ref: "#/components/schemas/Conversation"
                - type: object
                  properties:
                    messages:
                      type: array
                      items:
                        $ref: "#/components/schemas/ConversationMessage"

    ConversationUpdateResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                object:
                  type: string
                  enum: [conversation]
                id:
                  type: string
                  format: uuid
                is_unread:
                  type: boolean
                is_archived:
                  type: boolean
                updated_at:
                  type: string
                  format: date-time

    MessageListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/ConversationMessage"
            pagination:
              $ref: "#/components/schemas/Pagination"

    BulkContactRequest:
      type: object
      required:
        - contacts
        - TeamId
        - CampaignId
      properties:
        contacts:
          type: array
          description: Array of contacts to import (max 50,000)
          minItems: 1
          maxItems: 50000
          items:
            type: object
            properties:
              email:
                type: string
                format: email
              linkedin_url:
                type: string
                format: uri
              twitter:
                type: string
              first_name:
                type: string
              last_name:
                type: string
              company_name:
                type: string
              title:
                type: string
              phone:
                type: string
              city:
                type: string
              state:
                type: string
              country:
                type: string
              industry:
                type: string
              company_url:
                type: string
                format: uri
              note:
                type: string
              one_liner:
                type: string
              custom:
                type: object
                additionalProperties: true
        TeamId:
          type: string
          format: uuid
          description: Team ID
        CampaignId:
          type: string
          format: uuid
          description: Campaign ID
        validate_emails:
          type: boolean
          default: false
          description: When true, runs email verification on imported contacts (consumes credits). Default false to avoid surprise billing.

    BulkContactResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                upload_id:
                  type: string
                  format: uuid
                  description: Unique ID to track this upload
                status:
                  type: string
                  enum: [pending, processing, completed, failed]
                  description: Current processing status
                contact_count:
                  type: integer
                  description: Number of contacts in the upload
                message:
                  type: string
                  description: Status message

    # ===== Campaign =====
    ContactMetrics:
      type: object
      description: Contact lifecycle metrics for a campaign
      properties:
        total:
          type: integer
          description: Total non-archived contacts in campaign
        not_started:
          type: integer
          description: Contacts that haven't begun the sequence yet (in queue for auto-refill)
        in_progress:
          type: integer
          description: Contacts currently progressing through the sequence
        finished:
          type: integer
          description: Contacts that completed the sequence
        paused:
          type: integer
          description: Contacts currently paused

    Campaign:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
          enum: [active, inactive]
        disabled:
          type: boolean
        contact_count:
          type: integer
          description: Total non-archived contacts (same as contact_metrics.total)
        contact_metrics:
          $ref: "#/components/schemas/ContactMetrics"
        team_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CreateCampaignRequest:
      type: object
      required:
        - name
        - TeamId
      properties:
        name:
          type: string
          maxLength: 255
        TeamId:
          type: string
          format: uuid
        track_domain:
          type: string
          description: Tracking domain for links (team default used if omitted)
        nodes:
          type: array
          description: Initial sequence nodes (startNode, emailNode, waitNode, etc.)
        edges:
          type: array
          description: Initial sequence edges (connections between nodes)
        is_draft:
          type: boolean
          default: true
        timezone:
          type: string
          example: America/New_York
        blacklistIfBounce:
          type: boolean
          default: true
        blacklistIfUnsubscribe:
          type: boolean
          default: true

    UpdateCampaignRequest:
      type: object
      properties:
        name:
          type: string
          maxLength: 255
        track_domain:
          type: string
        nodes:
          type: array
          description: Campaign sequence nodes (cannot modify while active)
        edges:
          type: array
          description: Campaign sequence edges (cannot modify while active)
        status:
          type: number
          enum: [1, 2]
          description: 1=inactive, 2=active
        is_draft:
          type: boolean
        timezone:
          type: string
        disabled:
          type: boolean
        blacklistIfBounce:
          type: boolean
        blacklistIfUnsubscribe:
          type: boolean

    CampaignResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/Campaign"

    CampaignListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Campaign"
            pagination:
              $ref: "#/components/schemas/Pagination"

    # ===== Team =====
    Team:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        domain:
          type: string
        logo:
          type: string
          format: uri
        about:
          type: string
        is_default:
          type: boolean
        meeting_link:
          type: string
          format: uri
        meeting_link_text:
          type: string
        auto_placement_testing:
          type: boolean
        inbox_auto_tag_settings:
          type: object
          description: Auto-tag settings for inbox conversations
        inbox_super_views:
          type: array
          description: Customizable Super Views (Hot Leads, Needs Reply, etc.)
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              icon:
                type: string
              filters:
                type: object
                properties:
                  mood:
                    type: string
                    enum: [positive, negative, neutral, needs_review]
                  statuses:
                    type: array
                    items:
                      type: string
                  lastMessageDirection:
                    type: string
                    enum: [inbound, outbound]
                  labels:
                    type: array
                    items:
                      type: string
              visible:
                type: boolean
              order:
                type: integer
        member_count:
          type: integer
        organization:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    TeamMember:
      type: object
      properties:
        id:
          type: string
          format: uuid
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
        role:
          type: string
        joined_at:
          type: string
          format: date-time

    CreateTeamRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          maxLength: 255
        domain:
          type: string
        logo:
          type: string
          format: uri
        about:
          type: string
          maxLength: 2000
        meeting_link:
          type: string
          format: uri
        meeting_link_text:
          type: string
          maxLength: 100
        auto_placement_testing:
          type: boolean

    UpdateTeamRequest:
      type: object
      properties:
        name:
          type: string
          maxLength: 255
        domain:
          type: string
        logo:
          type: string
          format: uri
        about:
          type: string
          maxLength: 2000
        meeting_link:
          type: string
          format: uri
        meeting_link_text:
          type: string
          maxLength: 100
        auto_placement_testing:
          type: boolean
        auto_placement_testing_frequency:
          type: integer
          enum: [7, 14, 30]
        inbox_auto_tag_settings:
          type: object
          properties:
            auto_tag_bounced:
              type: boolean
            auto_tag_opt_out:
              type: boolean
            auto_tag_out_of_office:
              type: boolean
        inbox_super_views:
          type: array
          description: Customizable Super Views configuration
          items:
            type: object
            required: [id, name, icon, filters]
            properties:
              id:
                type: string
              name:
                type: string
              icon:
                type: string
              filters:
                type: object
                properties:
                  mood:
                    type: string
                    enum: [positive, negative, neutral, needs_review]
                  statuses:
                    type: array
                    items:
                      type: string
                  lastMessageDirection:
                    type: string
                    enum: [inbound, outbound]
                  labels:
                    type: array
                    items:
                      type: string
              visible:
                type: boolean
              order:
                type: integer
                minimum: 0

    TeamResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/Team"

    TeamDetailResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              allOf:
                - $ref: "#/components/schemas/Team"
                - type: object
                  properties:
                    members:
                      type: array
                      items:
                        $ref: "#/components/schemas/TeamMember"

    TeamListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Team"
            pagination:
              $ref: "#/components/schemas/Pagination"

    TeamUsageResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                team_id:
                  type: string
                  format: uuid
                team_name:
                  type: string
                contacts_used:
                  type: integer
                contacts_limit:
                  type: integer
                usage_percentage:
                  type: number
                is_at_limit:
                  type: boolean

    # ===== Sender =====
    Sender:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        send_as:
          type: string
        reply_to:
          type: string
          format: email
        provider:
          type: string
          enum:
            [
              smtp,
              outlook,
              google,
              missioninbox,
              supersend-relay,
              ss-private-smtp,
              sendgrid,
              mailreef,
              infraforge,
              zoho,
              namecheap,
              hostinger,
              titan,
              ionos,
              dreamhost,
              aol,
              mailgun,
              aws,
              azure,
            ]
        domain:
          type: string
        disabled:
          type: boolean
        warm:
          type: boolean
        warming_stage:
          type: string
          enum: [warming, building, accelerating, mature]
        max_per_day:
          type: integer
        global_max_per_day:
          type: integer
        campaign_ramp_enabled:
          type: boolean
          description: When true, daily limit ramps from start volume to target over duration
        campaign_ramp_first_send_date:
          type: string
          format: date
          nullable: true
          description: Date of first campaign send; set when first send occurs
        campaign_ramp_duration_days:
          type: integer
          nullable: true
          minimum: 1
          maximum: 365
          description: Days to reach target (required when ramp enabled)
        campaign_ramp_start_volume:
          type: integer
          nullable: true
          minimum: 1
          description: Limit on day 0 (required when ramp enabled)
        max_warm_per_day:
          type: integer
        health_score:
          type: integer
        status:
          type: string
        signature:
          type: string
        forward_to:
          type: string
          format: email
        sender_profile:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            timezone:
              type: string
        team_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    UpdateSenderRequest:
      type: object
      properties:
        send_as:
          type: string
        reply_to:
          type: string
          format: email
        signature:
          type: string
        forward_to:
          type: string
          format: email
        disabled:
          type: boolean
        warm:
          type: boolean
        max_per_day:
          type: integer
          minimum: 1
          maximum: 15000
        global_max_per_day:
          type: integer
          minimum: 1
          maximum: 1000
        max_warm_per_day:
          type: integer
          minimum: 1
          maximum: 1000
        warm_email_ramp:
          type: integer
          minimum: 1
          maximum: 365
        campaign_ramp_enabled:
          type: boolean
          description: Enable gradual ramp from start volume to target
        campaign_ramp_duration_days:
          type: integer
          minimum: 1
          maximum: 365
          description: Days to reach target (required when ramp enabled)
        campaign_ramp_start_volume:
          type: integer
          minimum: 1
          description: Limit on first send day (required when ramp enabled)
        SenderProfileId:
          type: string
          format: uuid
          nullable: true

    SenderResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/Sender"

    SenderListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Sender"
            pagination:
              $ref: "#/components/schemas/Pagination"

    # ===== Event =====
    Event:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            [
              scheduled,
              sent,
              open,
              reply,
              unsubscribe,
              visit,
              message,
              follow,
              task,
              like,
              connection_request,
              move_contact,
            ]
        channel:
          type: string
          enum: [email, linkedin, text, call, twitter, instagram]
        date:
          type: string
          format: date-time
        sequence_step:
          type: integer
        opened:
          type: boolean
        clicked:
          type: boolean
        replied:
          type: boolean
        bounced:
          type: boolean
        autoresponse:
          type: boolean
        opens:
          type: integer
        clicks:
          type: integer
        replies:
          type: integer
        unsubscribes:
          type: integer
        subject:
          type: string
        contact:
          type: object
          properties:
            id:
              type: string
              format: uuid
            email:
              type: string
            first_name:
              type: string
            last_name:
              type: string
        campaign:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
        sender:
          type: object
          properties:
            id:
              type: string
              format: uuid
            email:
              type: string
            send_as:
              type: string
        campaign_id:
          type: string
          format: uuid
        contact_id:
          type: string
          format: uuid
        sender_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    EventResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/Event"

    EventListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Event"
            pagination:
              $ref: "#/components/schemas/Pagination"

    # ===== Webhook Schemas =====
    Webhook:
      type: object
      properties:
        object:
          type: string
          example: webhook
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        name:
          type: string
        enabled:
          type: boolean
        secret:
          type: string
          description: Masked webhook secret (or null if not set)
        campaign_count:
          type: integer
          description: Number of campaigns using this webhook
        config:
          type: object
          properties:
            reply_event:
              type: boolean
            open_event:
              type: boolean
            click_event:
              type: boolean
            sent_event:
              type: boolean
            bounce_event:
              type: boolean
            unsubscribe_event:
              type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    WebhookListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Webhook"
            pagination:
              $ref: "#/components/schemas/Pagination"

    WebhookDelivery:
      type: object
      properties:
        object:
          type: string
          example: webhook_delivery
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
          description: Deterministic event ID for deduplication
        event_type:
          type: string
          description: Event type (reply, open, click, bounce, etc.)
        status:
          type: string
          enum: [pending, delivered, failed, retrying]
          description: Current delivery status
        attempt_count:
          type: integer
          description: Number of delivery attempts made
        max_attempts:
          type: integer
          description: Maximum retry attempts (default 7)
        response_status:
          type: integer
          nullable: true
          description: HTTP response status code
        response_time_ms:
          type: integer
          nullable: true
          description: Response latency in milliseconds
        error_message:
          type: string
          nullable: true
          description: Error message if delivery failed
        created_at:
          type: string
          format: date-time
        last_attempt_at:
          type: string
          format: date-time
          nullable: true
        delivered_at:
          type: string
          format: date-time
          nullable: true
        next_retry_at:
          type: string
          format: date-time
          nullable: true
        contact:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            email:
              type: string
              format: email
            name:
              type: string
              nullable: true
        campaign:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string

    WebhookDeliveryDetail:
      allOf:
        - $ref: "#/components/schemas/WebhookDelivery"
        - type: object
          properties:
            payload:
              type: object
              description: Full webhook payload
            response_body:
              type: string
              nullable: true
              description: Response body from webhook endpoint
            response_headers:
              type: object
              nullable: true
              description: Response headers from webhook endpoint

    WebhookDeliveryListResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/WebhookDelivery"
            pagination:
              $ref: "#/components/schemas/Pagination"

    WebhookDeliveryResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/WebhookDeliveryDetail"

    WebhookDeliveryRetryResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/WebhookDelivery"
            message:
              type: string
              example: Retry queued successfully

    # ===== Sequence Node Schemas =====
    SequenceNode:
      type: object
      description: A node in the sequence builder canvas
      required:
        - id
        - type
        - data
        - position
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the node
        type:
          type: string
          enum:
            - startNode
            - stopNode
            - emailNode
            - waitNode
            - ifNode
            - switchNode
            - httpApiRequestNode
            - linkedinProfileVisitNode
            - linkedinConnectionNode
            - linkedinConnectionWithMessageNode
            - linkedinMessageNode
            - linkedinInmailNode
            - linkedinLikeRecentPostNode
            - linkedinWaitUntilConnectedNode
            - twitterFollowNode
            - twitterUnFollowNode
            - twitterDMNode
            - moveToAnotherCampaignNode
            - taskNode
            - noteNode
          description: The type of node
        data:
          type: object
          description: Node-specific data (varies by node type)
          additionalProperties: true
        position:
          $ref: "#/components/schemas/NodePosition"
        style:
          type: object
          properties:
            width:
              type: integer
            height:
              type: integer
            minWidth:
              type: integer
            minHeight:
              type: integer

    NodePosition:
      type: object
      required:
        - x
        - y
      properties:
        x:
          type: number
        y:
          type: number

    SequenceEdge:
      type: object
      description: A connection between two nodes in the sequence
      required:
        - id
        - source
        - target
      properties:
        id:
          type: string
          description: Unique identifier for the edge
        source:
          type: string
          format: uuid
          description: ID of the source node
        sourceHandle:
          type: string
          description: Handle ID on the source node (e.g., 'true', 'false', or path ID)
        target:
          type: string
          format: uuid
          description: ID of the target node
        type:
          type: string
          enum:
            - buttonedge
          default: buttonedge

    # If Node Data Schema
    IfNodeData:
      type: object
      description: Data structure for If (Condition) nodes
      required:
        - step_number
      properties:
        step_number:
          type: integer
          description: Step number in the sequence
        label:
          type: string
          description: Display label for the node
        type:
          type: integer
          enum: [10]
          description: Message type (10 = IF)
        # Binary mode fields
        event:
          type: string
          description: Field or event to evaluate (e.g., 'opened_email', 'api_step.data.score')
        property_path:
          type: string
          description: Nested property path for API responses (e.g., 'research.score')
        comparison:
          type: string
          enum:
            - "="
            - "!="
            - ">"
            - ">="
            - "<"
            - "<="
            - contains
            - starts_with
            - ends_with
            - exists
            - is_empty
            - regex
          description: Comparison operator
        comparison_value:
          type: string
          description: Value to compare against
        # Multi-path mode fields
        multi_path_enabled:
          type: boolean
          default: false
          description: Enable multi-path routing (more than 2 outcomes)
        paths:
          type: array
          maxItems: 10
          description: Array of paths with conditions (when multi_path_enabled is true)
          items:
            $ref: "#/components/schemas/IfPath"

    IfPath:
      type: object
      description: A path in a multi-path If node
      required:
        - id
        - label
        - condition_groups
      properties:
        id:
          type: string
          description: Unique identifier for this path (used in edges)
        label:
          type: string
          description: Display label for this path
        is_default:
          type: boolean
          default: false
          description: Whether this is the default/fallback path
        condition_groups:
          type: array
          description: Condition groups (ANDed together)
          items:
            $ref: "#/components/schemas/IfConditionGroup"

    IfConditionGroup:
      type: object
      description: A group of conditions (conditions within are ORed)
      required:
        - conditions
      properties:
        conditions:
          type: array
          maxItems: 10
          items:
            $ref: "#/components/schemas/IfCondition"

    IfCondition:
      type: object
      description: A single condition in an If node
      required:
        - event
        - comparison
      properties:
        event:
          type: string
          description: Field or event to evaluate
        property_path:
          type: string
          description: Nested property path for API responses
        comparison:
          type: string
          enum:
            - "="
            - "!="
            - ">"
            - ">="
            - "<"
            - "<="
            - contains
            - starts_with
            - ends_with
            - exists
            - is_empty
            - regex
        comparison_value:
          type: string
          description: Value to compare against

    # Switch Node Data Schema
    SwitchNodeData:
      type: object
      description: Data structure for Switch nodes (multi-path routing by value)
      required:
        - step_number
        - switch_field
        - cases
        - default_path_id
      properties:
        step_number:
          type: integer
          description: Step number in the sequence
        label:
          type: string
          description: Display label for the node
        type:
          type: integer
          enum: [25]
          description: Message type (25 = SWITCH)
        switch_field:
          type: string
          description: Field to switch on (e.g., 'api_step', 'company_size')
        property_path:
          type: string
          description: Nested property path for API responses
        cases:
          type: array
          maxItems: 10
          description: Array of cases to match against
          items:
            $ref: "#/components/schemas/SwitchCase"
        default_path_id:
          type: string
          description: ID of the default case (fallback)

    SwitchCase:
      type: object
      description: A case in a Switch node
      required:
        - id
        - label
        - match_type
      properties:
        id:
          type: string
          description: Unique identifier for this case (used in edges)
        label:
          type: string
          description: Display label for this case
        is_default:
          type: boolean
          default: false
          description: Whether this is the default/fallback case
        match_type:
          type: string
          enum:
            - equals
            - not_equals
            - greater_than
            - greater_than_or_equal
            - less_than
            - less_than_or_equal
            - range
            - contains
            - starts_with
            - ends_with
            - regex
            - exists
            - is_empty
          description: Type of matching to perform
        value:
          type: string
          description: Value to match (for equals, contains, etc.)
        min:
          type: number
          description: Minimum value for range matching
        max:
          type: number
          description: Maximum value for range matching
        pattern:
          type: string
          description: Regex pattern for regex matching

    # HTTP API Request Node Data Schema
    HttpApiRequestNodeData:
      type: object
      description: Data structure for HTTP API Request nodes
      required:
        - step_number
        - request_method
        - request_url
      properties:
        step_number:
          type: integer
        label:
          type: string
        type:
          type: integer
          enum: [23]
          description: Message type (23 = HTTP_API_REQUEST)
        request_method:
          type: string
          enum:
            - GET
            - POST
            - PUT
            - PATCH
            - DELETE
        request_url:
          type: string
          description: URL to make the request to (supports variables)
        request_headers:
          type: object
          additionalProperties:
            type: string
          description: HTTP headers to include in the request
        request_body:
          type: string
          description: Request body (for POST, PUT, PATCH)
        timeout_ms:
          type: integer
          default: 10000
          description: Request timeout in milliseconds
        retry_on_failure:
          type: boolean
          default: false
        max_retries:
          type: integer
          default: 3
        store_response:
          type: boolean
          default: true
          description: Whether to store response for use in If nodes

    # ===== Sequence =====
    UpdateSequenceRequest:
      type: object
      description: Request body for updating a campaign sequence
      properties:
        nodes:
          type: array
          description: Array of sequence nodes (replaces all existing nodes)
          items:
            $ref: "#/components/schemas/SequenceNode"
        edges:
          type: array
          description: Array of edges connecting nodes (replaces all existing edges)
          items:
            $ref: "#/components/schemas/SequenceEdge"

    CampaignSequenceResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                campaign_id:
                  type: string
                  format: uuid
                nodes:
                  type: array
                  items:
                    $ref: "#/components/schemas/SequenceNode"
                edges:
                  type: array
                  items:
                    $ref: "#/components/schemas/SequenceEdge"
