openapi: 3.0.3
info:
  title: SendFox API
  description: |
    # Introduction
    SendFox's REST API lets you manage contacts, campaigns, lists, automations, and forms programmatically. It uses OAuth 2.0 for authentication.

    Compatible with AI agents (Claude, ChatGPT, etc.) via the OpenAPI spec. Available at `GET /openapi.yaml`.

    # Authentication
    ### Personal Access Token
    Create a personal access token at https://sendfox.com/account/oauth. Once created, use it in the `Authorization` header:
    ```
    Authorization: Bearer {TOKEN}
    ```

    ### OAuth 2.0 Client
    For integrations that require user authentication, create an OAuth 2.0 client at https://sendfox.com/account/oauth

    * Authorization URL: https://sendfox.com/oauth/authorize
    * Access Token URL: https://sendfox.com/oauth/token

    # Rate Limits
    API requests are limited to **60 requests per minute** per authenticated user. Rate limit status is returned in response headers:
    - `X-RateLimit-Limit`: Maximum requests per minute
    - `X-RateLimit-Remaining`: Remaining requests in current window
    - `Retry-After`: Seconds until rate limit resets (only on 429 responses)

    # Error Responses
    All error responses use standard HTTP status codes and Laravel's default error format:
    - `message`: Human-readable error description
    - `errors`: Field-level validation errors (on 422 responses)

    # Plans & API Access
    API access requires a **Lifetime** or **Empire** plan. Free users cannot use the API.
  version: 1.3.0
  x-logo:
    url: /img/sendfox-logo.svg
servers:
  - url: https://api.sendfox.com

components:
  securitySchemes:
    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://sendfox.com/oauth/authorize
          tokenUrl: https://sendfox.com/oauth/token
          scopes: {}

  schemas:
    Contact:
      type: object
      properties:
        id:
          type: integer
        email:
          type: string
          format: email
        first_name:
          type: string
        last_name:
          type: string
        ip_address:
          type: string
        unsubscribed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - email

    Campaign:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        subject:
          type: string
        preview_text:
          type: string
          maxLength: 191
          nullable: true
          description: Inbox preview snippet shown beneath the subject line in most email clients.
        html:
          type: string
        from_name:
          type: string
        from_email:
          type: string
          format: email
        scheduled_at:
          type: string
          format: date-time
          nullable: true
        sent_at:
          type: string
          format: date-time
          nullable: true
        timezone:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - title
        - subject
        - html
        - from_name
        - from_email

    Error:
      type: object
      properties:
        message:
          type: string
          description: Human-readable error description
        errors:
          type: object
          description: Field-level validation errors (on 422 responses)
          additionalProperties:
            type: array
            items:
              type: string

    Form:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        landing_page_id:
          type: integer
          nullable: true
        redirect_url:
          type: string
          nullable: true
        gdpr_required:
          type: boolean
        url:
          type: string
          description: Public subscribe URL
        lists:
          type: array
          items:
            $ref: '#/components/schemas/ContactList'
          description: Attached lists (included on create/update)
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - title
        - lists

    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email
        contacts_count:
          type: integer
        contact_limit:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    UserContactField:
      type: object
      properties:
        id:
          type: integer
        label:
          type: string
          description: Human-readable field label
        name:
          type: string
          description: Machine-readable slug (auto-generated from label)
        type:
          type: string
          enum: [text, number, date]
          description: Field type
      required:
        - label

    WhitelabelDomain:
      type: object
      properties:
        id:
          type: integer
        domain:
          type: string
        sendgrid_whitelabel_domain_id:
          type: integer
          nullable: true
        validated_at:
          type: string
          format: date-time
          nullable: true
        dns:
          type: object
          description: DNS records from SendGrid (included on show)
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - domain

    ContactList:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        user_id:
          type: integer
        average_email_open_percent:
          type: number
          format: float
        average_email_click_percent:
          type: number
          format: float
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - name

    Automation:
      type: object
      properties:
        id:
          type: integer
        user_id:
          type: integer
        title:
          type: string
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        automation_triggers:
          type: array
          items:
            $ref: '#/components/schemas/AutomationTrigger'
        automation_items:
          type: array
          items:
            $ref: '#/components/schemas/AutomationItem'

    AutomationTrigger:
      type: object
      properties:
        id:
          type: integer
        automation_id:
          type: integer
        type:
          type: string
          enum: [apply_list, open_campaign, click_campaign]
        list_id:
          type: integer
          nullable: true
        campaign_id:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    AutomationItem:
      type: object
      properties:
        id:
          type: integer
        automation_id:
          type: integer
        campaign_id:
          type: integer
        delay_hours:
          type: integer
        send_order:
          type: integer
        campaign:
          $ref: '#/components/schemas/Campaign'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CampaignStats:
      type: object
      properties:
        sent_count:
          type: integer
        unique_open_count:
          type: integer
        unique_click_count:
          type: integer
        unsubscribe_count:
          type: integer
        bounce_count:
          type: integer
        spam_count:
          type: integer
        open_rate:
          type: number
          format: float
        click_rate:
          type: number
          format: float
        unsubscribe_rate:
          type: number
          format: float
        bounce_rate:
          type: number
          format: float
        spam_rate:
          type: number
          format: float

    BatchResult:
      type: object
      properties:
        created:
          type: integer
        updated:
          type: integer

security:
  - oauth2: []

paths:
  /contacts:
    get:
      operationId: listContacts
      tags:
        - Contacts
      summary: List contacts
      description: Returns a paginated list of contacts (100 per page)
      parameters:
        - name: query
          in: query
          schema:
            type: string
          description: Search query for filtering contacts
        - name: unsubscribed
          in: query
          schema:
            type: boolean
          description: Filter unsubscribed contacts
        - name: email
          in: query
          schema:
            type: string
          description: Filter by specific email
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
        '401':
          description: Unauthorized
    post:
      operationId: createContact
      tags:
        - Contacts
      summary: Create a new contact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                first_name:
                  type: string
                last_name:
                  type: string
                ip_address:
                  type: string
                lists:
                  type: array
                  items:
                    type: integer
                  description: Array of list IDs to add the contact to
                contact_fields:
                  type: array
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                      value:
                        type: string
      responses:
        '200':
          description: Contact created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          description: Contact limit exceeded

  /contacts/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getContact
      tags:
        - Contacts
      summary: Get a specific contact
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '403':
          description: Forbidden
        '404':
          description: Contact not found
    patch:
      operationId: updateContact
      tags:
        - Contacts
      summary: Update a contact
      description: Update contact details including name, list memberships, and custom fields
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                first_name:
                  type: string
                last_name:
                  type: string
                lists:
                  type: array
                  items:
                    type: integer
                  description: Array of list IDs (replaces all current list memberships)
                contact_fields:
                  type: array
                  items:
                    type: object
                    properties:
                      name:
                        type: string
                      value:
                        type: string
                        nullable: true
      responses:
        '200':
          description: Contact updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '403':
          description: Forbidden
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      operationId: deleteContact
      tags:
        - Contacts
      summary: Delete a contact
      description: Soft-deletes a contact and cancels any scheduled deliverables
      responses:
        '200':
          description: Contact deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden
        '404':
          description: Contact not found

  /contacts/{id}/activity:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getContactActivity
      tags:
        - Contacts
      summary: Get email activity for a contact
      description: Returns paginated email deliverables and contact-level engagement summary
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  contact:
                    type: object
                    properties:
                      last_sent_at:
                        type: string
                        format: date-time
                        nullable: true
                      last_opened_at:
                        type: string
                        format: date-time
                        nullable: true
                      last_clicked_at:
                        type: string
                        format: date-time
                        nullable: true
                      unsubscribed_at:
                        type: string
                        format: date-time
                        nullable: true
                      bounced_at:
                        type: string
                        format: date-time
                        nullable: true
                  deliverables:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            campaign_title:
                              type: string
                            campaign_id:
                              type: integer
                            sent_at:
                              type: string
                              format: date-time
                              nullable: true
                            opened_at:
                              type: string
                              format: date-time
                              nullable: true
                            clicked_at:
                              type: string
                              format: date-time
                              nullable: true
                            bounced_at:
                              type: string
                              format: date-time
                              nullable: true
                            unsubscribed_at:
                              type: string
                              format: date-time
                              nullable: true
                            spam_at:
                              type: string
                              format: date-time
                              nullable: true
        '403':
          description: Forbidden
        '404':
          description: Contact not found

  /contacts/batch:
    post:
      operationId: batchImportContacts
      tags:
        - Contacts
      summary: Batch import contacts
      description: Import up to 1,000 contacts in a single request. Creates new contacts or updates existing ones.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - contacts
              properties:
                contacts:
                  type: array
                  maxItems: 1000
                  items:
                    type: object
                    required:
                      - email
                    properties:
                      email:
                        type: string
                        format: email
                      first_name:
                        type: string
                      last_name:
                        type: string
                      lists:
                        type: array
                        items:
                          type: integer
      responses:
        '200':
          description: Batch import results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResult'
        '402':
          description: Contact limit would be exceeded
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /contacts/unsubscribed:
    get:
      operationId: listUnsubscribedContacts
      tags:
        - Contacts
      summary: List unsubscribed contacts
      parameters:
        - name: query
          in: query
          schema:
            type: string
          description: Search query for filtering contacts
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer

  /unsubscribe:
    patch:
      operationId: unsubscribeContact
      tags:
        - Contacts
      summary: Unsubscribe a contact by email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
              required:
                - email
      responses:
        '200':
          description: Contact unsubscribed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /campaigns:
    get:
      operationId: listCampaigns
      tags:
        - Campaigns
      summary: List campaigns
      description: Returns a paginated list of campaigns (100 per page)
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Campaign'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
    post:
      operationId: createCampaign
      tags:
        - Campaigns
      summary: Create a new campaign
      description: |
        Creates a campaign as a draft. To send it, use the send endpoint or provide scheduled_at.
        Subject lines cannot start with "RE:" or "FWD:".
        At least one list is required if scheduled_at is provided.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  maxLength: 191
                subject:
                  type: string
                  maxLength: 191
                preview_text:
                  type: string
                  maxLength: 191
                  nullable: true
                  description: Inbox preview snippet shown beneath the subject line. Optional.
                html:
                  type: string
                  maxLength: 1000000
                  description: Email body HTML content
                from_name:
                  type: string
                  maxLength: 191
                from_email:
                  type: string
                  format: email
                  maxLength: 191
                scheduled_at:
                  type: string
                  format: date-time
                  description: Schedule send time (omit for draft). Must include at least one list.
                lists:
                  type: array
                  items:
                    type: integer
                  description: Array of list IDs to send to
              required:
                - title
                - subject
                - html
                - from_name
                - from_email
      responses:
        '201':
          description: Campaign created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '403':
          description: Forbidden (not subscribed or cannot schedule)
        '422':
          description: Validation error or configuration required (e.g., timezone not set)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /campaigns/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getCampaign
      tags:
        - Campaigns
      summary: Get a specific campaign
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '403':
          description: Forbidden
        '404':
          description: Campaign not found
    patch:
      operationId: updateCampaign
      tags:
        - Campaigns
      summary: Update a draft campaign
      description: Only draft campaigns (not yet sent) can be updated. All fields are optional.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  maxLength: 191
                subject:
                  type: string
                  maxLength: 191
                preview_text:
                  type: string
                  maxLength: 191
                  nullable: true
                  description: Inbox preview snippet. Pass null to clear.
                html:
                  type: string
                  maxLength: 1000000
                from_name:
                  type: string
                  maxLength: 191
                from_email:
                  type: string
                  format: email
                  maxLength: 191
                scheduled_at:
                  type: string
                  format: date-time
                  nullable: true
                  description: Set to null to unschedule, or a datetime to schedule
                lists:
                  type: array
                  items:
                    type: integer
                  description: Replaces all list assignments
      responses:
        '200':
          description: Campaign updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '403':
          description: Forbidden
        '409':
          description: Campaign already sent
        '422':
          description: Validation error
    delete:
      operationId: deleteCampaign
      tags:
        - Campaigns
      summary: Delete a draft campaign
      description: Only draft campaigns (not yet sent) can be deleted. Uses soft delete.
      responses:
        '200':
          description: Campaign deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden
        '409':
          description: Campaign already sent

  /campaigns/{id}/send:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    post:
      operationId: sendCampaign
      tags:
        - Campaigns
      summary: Send a campaign immediately
      description: |
        Schedules a draft campaign for immediate sending. The campaign must:
        - Not already be sent or scheduled
        - Have at least one list assigned
        - The user must not be in a warmup/throttle period

        All existing abuse prevention applies automatically: content approval workflow,
        sending throttles, spam detection, and bounce rate monitoring.
      responses:
        '200':
          description: Campaign scheduled for sending
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '400':
          description: Campaign has no lists assigned
        '403':
          description: Forbidden (cannot send or account warming up)
        '409':
          description: Campaign already sent or scheduled

  /campaigns/{id}/stats:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getCampaignStats
      tags:
        - Campaigns
      summary: Get campaign performance statistics
      description: Returns sent count, open/click/bounce/unsubscribe/spam counts and rates
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignStats'
        '403':
          description: Forbidden
        '404':
          description: Campaign not found

  /forms:
    get:
      operationId: listForms
      tags:
        - Forms
      summary: List forms
      parameters:
        - name: query
          in: query
          schema:
            type: string
          description: Search query for filtering forms
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Form'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
        '401':
          description: Unauthorized
    post:
      operationId: createForm
      tags:
        - Forms
      summary: Create a new form
      description: |
        Creates a subscription form linked to one or more lists.
        Free users are limited to 1 form.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  maxLength: 191
                lists:
                  type: array
                  items:
                    type: integer
                  description: Array of list IDs to attach
                redirect_url:
                  type: string
                  format: uri
                  nullable: true
                  description: URL to redirect to after subscription
                gdpr_required:
                  type: boolean
                  description: Whether GDPR consent checkbox is required
              required:
                - title
                - lists
      responses:
        '201':
          description: Form created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Form'
        '403':
          description: Forbidden (free user form limit reached)
        '422':
          description: Validation error

  /forms/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getForm
      tags:
        - Forms
      summary: Get a specific form
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Form'
        '403':
          description: Forbidden
        '404':
          description: Form not found
    patch:
      operationId: updateForm
      tags:
        - Forms
      summary: Update a form
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  maxLength: 191
                lists:
                  type: array
                  items:
                    type: integer
                  description: Replaces all list assignments
                redirect_url:
                  type: string
                  format: uri
                  nullable: true
                gdpr_required:
                  type: boolean
      responses:
        '200':
          description: Form updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Form'
        '403':
          description: Forbidden
        '422':
          description: Validation error
    delete:
      operationId: deleteForm
      tags:
        - Forms
      summary: Delete a form
      description: Soft-deletes the form
      responses:
        '200':
          description: Form deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden

  /me:
    get:
      operationId: getCurrentUser
      tags:
        - Users
      summary: Get current user information
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          description: Unauthorized

  /contact-fields:
    get:
      operationId: listContactFields
      tags:
        - Contact Fields
      summary: List user contact fields
      description: Returns custom contact fields defined by the user (20 per page)
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/UserContactField'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
        '401':
          description: Unauthorized
    post:
      operationId: createContactField
      tags:
        - Contact Fields
      summary: Create a custom contact field
      description: |
        Creates a custom field for contacts. The `name` is auto-generated from the `label` as a slug.
        Valid field type IDs: text, number, date (use the UUID constants from the contact_field_types table).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
                  maxLength: 191
                  description: Human-readable field label
                contact_field_type_id:
                  type: string
                  description: UUID of the field type (text, number, or date)
              required:
                - label
                - contact_field_type_id
      responses:
        '201':
          description: Contact field created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserContactField'
        '422':
          description: Validation error

  /contact-fields/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getContactField
      tags:
        - Contact Fields
      summary: Get a specific contact field
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserContactField'
        '403':
          description: Forbidden
        '404':
          description: Contact field not found
    patch:
      operationId: updateContactField
      tags:
        - Contact Fields
      summary: Update a contact field label
      description: |
        Updates the label and auto-regenerates the name slug.
        Note: `contact_field_type_id` is immutable and cannot be changed after creation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
                  maxLength: 191
              required:
                - label
      responses:
        '200':
          description: Contact field updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserContactField'
        '403':
          description: Forbidden
        '422':
          description: Validation error
    delete:
      operationId: deleteContactField
      tags:
        - Contact Fields
      summary: Delete a contact field
      description: Permanently deletes the contact field
      responses:
        '200':
          description: Contact field deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden

  /lists:
    get:
      operationId: listContactLists
      tags:
        - Lists
      summary: List contact lists
      parameters:
        - name: query
          in: query
          schema:
            type: string
          description: Search query for filtering lists
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ContactList'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
        '401':
          description: Unauthorized
    post:
      operationId: createContactList
      tags:
        - Lists
      summary: Create a new contact list
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
              required:
                - name
      responses:
        '200':
          description: List created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactList'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized

  /lists/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getContactList
      tags:
        - Lists
      summary: Get a specific contact list
      description: Returns list details including average open and click rates
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactList'
        '403':
          description: Forbidden
        '404':
          description: List not found
    patch:
      operationId: updateContactList
      tags:
        - Lists
      summary: Update a contact list
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 191
              required:
                - name
      responses:
        '200':
          description: List updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactList'
        '403':
          description: Forbidden
        '422':
          description: Validation error
    delete:
      operationId: deleteContactList
      tags:
        - Lists
      summary: Delete a contact list
      description: Soft-deletes a list. Returns 409 if the list is used by forms, landing pages, or automations.
      responses:
        '200':
          description: List deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden
        '409':
          description: List is in use
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  usages:
                    type: array
                    items:
                      type: object
                      properties:
                        type:
                          type: string
                        id:
                          type: integer
                        title:
                          type: string
        '404':
          description: List not found

  /lists/{list_id}/contacts:
    parameters:
      - name: list_id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: listContactsInList
      tags:
        - Lists
      summary: Get contacts in a list
      parameters:
        - name: query
          in: query
          schema:
            type: string
          description: Search query for filtering contacts
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
        '403':
          description: Forbidden
    post:
      operationId: addContactToList
      tags:
        - Lists
      summary: Add a contact to a list
      description: Adds an existing contact to a list. If the contact is already in the list, no duplicate is created.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                contact_id:
                  type: integer
                  description: ID of the contact to add
              required:
                - contact_id
      responses:
        '200':
          description: Contact added to list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '403':
          description: Forbidden
        '404':
          description: Contact not found
        '422':
          description: Validation error

  /lists/{list_id}/contacts/{contact_id}:
    parameters:
      - name: list_id
        in: path
        required: true
        schema:
          type: integer
      - name: contact_id
        in: path
        required: true
        schema:
          type: integer
    delete:
      operationId: removeContactFromList
      tags:
        - Lists
      summary: Remove a contact from a list
      responses:
        '200':
          description: Contact removed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '403':
          description: Forbidden
        '404':
          description: Contact or list not found

  /domains:
    get:
      operationId: listDomains
      tags:
        - Domains
      summary: List sender domains
      description: Returns a paginated list of the user's whitelabel/sender domains
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WhitelabelDomain'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
        '401':
          description: Unauthorized
    post:
      operationId: createDomain
      tags:
        - Domains
      summary: Add a sender domain
      description: |
        Adds a new sender domain and creates the corresponding SendGrid whitelabel domain.
        Requires an active subscription and SendGrid subuser.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                domain:
                  type: string
                  maxLength: 191
                  description: Domain name (e.g., example.com). Do not include @ or protocol.
              required:
                - domain
      responses:
        '201':
          description: Domain created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WhitelabelDomain'
        '403':
          description: Forbidden (not subscribed or no SendGrid subuser)
        '422':
          description: Validation error (invalid domain, already taken, or already exists)

  /domains/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getDomain
      tags:
        - Domains
      summary: Get domain with DNS records
      description: Returns domain details including DNS records needed for verification
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WhitelabelDomain'
        '403':
          description: Forbidden
        '404':
          description: Domain not found
    delete:
      operationId: deleteDomain
      tags:
        - Domains
      summary: Delete a sender domain
      description: Removes the domain from SendGrid and soft-deletes locally
      responses:
        '200':
          description: Domain deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden

  /domains/{id}/validate:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    post:
      operationId: validateDomain
      tags:
        - Domains
      summary: Validate domain DNS records
      description: |
        Triggers DNS validation for the domain via SendGrid.
        Returns whether validation passed and any errors for specific DNS records.
      responses:
        '200':
          description: Domain validated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  valid:
                    type: boolean
                  domain:
                    $ref: '#/components/schemas/WhitelabelDomain'
        '400':
          description: Validation failed (DNS records not configured correctly)
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  valid:
                    type: boolean
                  validation_errors:
                    type: object
        '403':
          description: Forbidden

  /automations:
    get:
      operationId: listAutomations
      tags:
        - Automations
      summary: List automations
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Automation'
                  current_page:
                    type: integer
                  total:
                    type: integer
                  per_page:
                    type: integer
        '401':
          description: Unauthorized
    post:
      operationId: createAutomation
      tags:
        - Automations
      summary: Create an automation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - title
              properties:
                title:
                  type: string
                trigger_type:
                  type: string
                  enum: [apply_list, open_campaign, click_campaign]
                trigger_list_id:
                  type: integer
                  description: Required when trigger_type is apply_list
                trigger_campaign_id:
                  type: integer
                  description: Required when trigger_type is open_campaign or click_campaign
                active:
                  type: boolean
      responses:
        '201':
          description: Automation created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Automation'
        '403':
          description: Forbidden (free users)
        '422':
          description: Validation error

  /automations/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    get:
      operationId: getAutomation
      tags:
        - Automations
      summary: Get a specific automation
      description: Returns automation with triggers, items, and campaign stats
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Automation'
        '401':
          description: Unauthorized
        '404':
          description: Automation not found
    patch:
      operationId: updateAutomation
      tags:
        - Automations
      summary: Update an automation
      description: Update title, trigger, or active status. Activating reschedules stale deliverables.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                active:
                  type: boolean
                trigger_type:
                  type: string
                  enum: [apply_list, open_campaign, click_campaign]
                trigger_list_id:
                  type: integer
                trigger_campaign_id:
                  type: integer
      responses:
        '200':
          description: Automation updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Automation'
        '403':
          description: Forbidden
        '422':
          description: Validation error
    delete:
      operationId: deleteAutomation
      tags:
        - Automations
      summary: Delete an automation
      description: Soft-deletes the automation and cancels all scheduled deliverables
      responses:
        '200':
          description: Automation deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden
        '404':
          description: Automation not found

  /automations/{id}/emails:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    post:
      operationId: createAutomationEmail
      tags:
        - Automations
      summary: Add an email to an automation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - subject
                - html
                - from_name
                - from_email
              properties:
                subject:
                  type: string
                html:
                  type: string
                from_name:
                  type: string
                from_email:
                  type: string
                  format: email
                delay_hours:
                  type: integer
                  minimum: 0
                  maximum: 5000
                  description: Hours to wait before sending (default 24, first email defaults to 0)
      responses:
        '201':
          description: Automation email created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationItem'
        '403':
          description: Forbidden
        '422':
          description: Validation error

  /automation-emails/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    patch:
      operationId: updateAutomationEmail
      tags:
        - Automations
      summary: Update an automation email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                subject:
                  type: string
                html:
                  type: string
                from_name:
                  type: string
                from_email:
                  type: string
                  format: email
                delay_hours:
                  type: integer
                  minimum: 0
                  maximum: 5000
                send_order:
                  type: integer
                  minimum: 1
      responses:
        '200':
          description: Automation email updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationItem'
        '403':
          description: Forbidden
        '422':
          description: Validation error
    delete:
      operationId: deleteAutomationEmail
      tags:
        - Automations
      summary: Remove an email from an automation
      responses:
        '200':
          description: Automation email deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '403':
          description: Forbidden
        '404':
          description: Automation email not found

x-tagGroups:
  -
    name: Resources
    tags:
      - Contacts
      - Campaigns
      - Lists
      - Forms
      - Automations
      - Domains
      - Users
      - Contact Fields
