openapi: 3.1.0
info:
  title: NotiQ Public API
  version: "1.0.0"
  description: |
    NotiQ Public API v1. Markdown docs are the primary source for humans and agents.
    Import this file into Postman/Insomnia for a generated collection.
    Authenticate with header X-API-Key (sk_live_…).
    Success responses are bare JSON (no data wrapper).
    Every response includes header X-Request-Id.
  contact:
    name: NotiQ
    url: https://app.notiq.io/api-mcp
servers:
  - url: https://api.notiq.io/public/v1
    description: NotiQ Public API
security:
  - ApiKeyAuth: []
tags:
  - name: Credits
  - name: Accounts
  - name: Tools
  - name: Activity
  - name: Insights
  - name: Campaigns
  - name: Leads
  - name: Inbox
paths:
  /credits:
    get:
      tags: [Credits]
      summary: Get credits
      operationId: getCredits
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Credits"
        default:
          $ref: "#/components/responses/Error"
  /accounts:
    get:
      tags: [Accounts]
      summary: List Gmail accounts
      operationId: getAccounts
      parameters:
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/cursor"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                type: object
                required: [accounts, nextCursor]
                properties:
                  accounts:
                    type: array
                    items:
                      $ref: "#/components/schemas/Account"
                  nextCursor:
                    type: string
                    nullable: true
        default:
          $ref: "#/components/responses/Error"
  /tools/validate-email:
    post:
      tags: [Tools]
      summary: Validate one email
      description: |
        Replay of Idempotency-Key is stored only for HTTP 200.
        A 403 insufficient_credits applies when mode=full needs a vendor call and does not stick to the key.
      operationId: validateEmail
      parameters:
        - $ref: "#/components/parameters/idempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateEmailRequest"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateEmailResponse"
        default:
          $ref: "#/components/responses/Error"
  /tools/validate-emails:
    post:
      tags: [Tools]
      summary: Validate up to 100 emails
      operationId: validateEmails
      parameters:
        - $ref: "#/components/parameters/idempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateEmailsRequest"
      responses:
        "200":
          description: OK (partial per-item outcomes, including insufficient_credits)
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateEmailsResponse"
        default:
          $ref: "#/components/responses/Error"
  /tools/enrich-website:
    post:
      tags: [Tools]
      summary: Enrich a public website
      description: 0 credits. Private, loopback, link-local, and similar hosts return status unreachable.
      operationId: enrichWebsite
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [website]
              properties:
                website:
                  type: string
                  maxLength: 500
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnrichWebsiteResponse"
        default:
          $ref: "#/components/responses/Error"
  /activity:
    get:
      tags: [Activity]
      summary: List recent workspace activity
      operationId: getActivity
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [email_sent, follow_up_sent, reply_received, agent_replied, lead_excluded]
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/cursor"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActivityListResponse"
        default:
          $ref: "#/components/responses/Error"
  /insights/overview:
    get:
      tags: [Insights]
      summary: Sampled insights overview
      description: Ranks up to 100 recent campaigns by updatedAt — not all-time analytics.
      operationId: getInsightsOverview
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsightsOverview"
        default:
          $ref: "#/components/responses/Error"
  /campaigns:
    get:
      tags: [Campaigns]
      summary: List campaigns
      operationId: getCampaigns
      parameters:
        - name: status
          in: query
          schema: { type: string }
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/cursor"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                type: object
                required: [campaigns, nextCursor]
                properties:
                  campaigns:
                    type: array
                    items:
                      $ref: "#/components/schemas/Campaign"
                  nextCursor:
                    type: string
                    nullable: true
        default:
          $ref: "#/components/responses/Error"
    post:
      tags: [Campaigns]
      summary: Launch Maps search campaign
      operationId: createCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [userQuery]
              additionalProperties: false
              properties:
                userQuery: { type: string, maxLength: 2000 }
                numberOfSearch: { type: integer, minimum: 1, maximum: 500, default: 25 }
                leadDuplicateProtectionAcrossCampaigns: { type: boolean }
                acceptedBroadening: { type: boolean }
                previewId: { type: string, maxLength: 128 }
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateCampaignResponse"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}:
    get:
      tags: [Campaigns]
      summary: Get campaign
      operationId: getCampaign
      parameters:
        - $ref: "#/components/parameters/campaignId"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}/start:
    post:
      tags: [Campaigns]
      summary: Start or resume campaign
      operationId: startCampaign
      parameters:
        - $ref: "#/components/parameters/campaignId"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                templateId: { type: string }
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignActionResponse"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}/pause:
    post:
      tags: [Campaigns]
      summary: Pause campaign
      operationId: pauseCampaign
      parameters:
        - $ref: "#/components/parameters/campaignId"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignActionResponse"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}/leads:
    get:
      tags: [Leads]
      summary: List leads with optional filters, sort, and excluded=true
      description: |
        At most one of tag, replied, needsReply, or excluded.
        needsReply and excluded accept true only. With needsReply=true or excluded=true, sort must be createdAt (or omitted).
      operationId: getLeads
      parameters:
        - $ref: "#/components/parameters/campaignId"
        - $ref: "#/components/parameters/limit"
        - $ref: "#/components/parameters/cursor"
        - name: tag
          in: query
          schema: { type: string }
        - name: replied
          in: query
          schema: { type: boolean }
        - name: needsReply
          in: query
          description: "true only — false returns 400 validation_error; mutually exclusive with tag / replied / excluded"
          schema: { type: boolean, enum: [true] }
        - name: excluded
          in: query
          description: "true only — false returns 400; mutually exclusive with tag / replied / needsReply; sort must be createdAt"
          schema: { type: boolean, enum: [true] }
        - name: sort
          in: query
          description: |
            Always descending. Default createdAt.
            With needsReply=true or excluded=true only createdAt is allowed.
            taggedAt requires tag=; repliedAt requires replied=; lastInboundMessageAt cannot combine with tag.
          schema:
            type: string
            enum: [createdAt, taggedAt, repliedAt, lastInboundMessageAt]
            default: createdAt
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                type: object
                required: [leads, nextCursor]
                properties:
                  leads:
                    type: array
                    items:
                      $ref: "#/components/schemas/Lead"
                  nextCursor:
                    type: string
                    nullable: true
        default:
          $ref: "#/components/responses/Error"
    post:
      tags: [Leads]
      summary: Add lead by email
      operationId: addLead
      parameters:
        - $ref: "#/components/parameters/campaignId"
        - $ref: "#/components/parameters/idempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              additionalProperties: false
              properties:
                email: { type: string, maxLength: 254 }
                fullName: { type: string, maxLength: 120 }
                companyName: { type: string, maxLength: 200 }
                website: { type: string, maxLength: 500 }
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AddLeadResponse"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}/leads/batch:
    post:
      tags: [Leads]
      summary: Add leads by email (batch)
      description: |
        Max 100 items. Usually HTTP 200 with partial per-item outcomes when the campaign exists.
        Hard failure before any durable insert (or idempotency-store failure) → 503.
        Idempotency-Key is persisted only if any item is created, duplicate, or excluded.
      operationId: addLeadsBatch
      parameters:
        - $ref: "#/components/parameters/campaignId"
        - $ref: "#/components/parameters/idempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddLeadsBatchRequest"
      responses:
        "200":
          description: OK (partial per-item outcomes allowed)
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AddLeadsBatchResponse"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}/leads/batch-update:
    post:
      tags: [Leads]
      summary: Tag and/or exclude leads (batch)
      description: |
        Max 100 items. 0 credits. Rate class update_leads_batch (default 6/min).
        Auto-exclude tags apply immediately. Optional Idempotency-Key (persist when summary.updated > 0).
      operationId: updateLeadsBatch
      parameters:
        - $ref: "#/components/parameters/campaignId"
        - $ref: "#/components/parameters/idempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateLeadsBatchRequest"
      responses:
        "200":
          description: OK (may include partial per-item failures)
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpdateLeadsBatchResponse"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}/leads/batch-delete:
    post:
      tags: [Leads]
      summary: Permanently delete leads (batch)
      description: |
        Max 100 ids. 0 credits (no refund). Rate class delete_leads_batch (default 10/min).
        Campaign ingesting → 409. Emailed/queued leads → item failed. Persist Idempotency-Key when summary.deleted > 0.
      operationId: deleteLeadsBatch
      parameters:
        - $ref: "#/components/parameters/campaignId"
        - $ref: "#/components/parameters/idempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteLeadsBatchRequest"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeleteLeadsBatchResponse"
        default:
          $ref: "#/components/responses/Error"
  /campaigns/{campaignId}/leads/{leadId}:
    get:
      tags: [Leads]
      summary: Get lead
      operationId: getLead
      parameters:
        - $ref: "#/components/parameters/campaignId"
        - $ref: "#/components/parameters/leadId"
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeadDetail"
        default:
          $ref: "#/components/responses/Error"
    patch:
      tags: [Leads]
      summary: Update lead
      operationId: patchLead
      parameters:
        - $ref: "#/components/parameters/campaignId"
        - $ref: "#/components/parameters/leadId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                tag:
                  type: string
                  nullable: true
                excluded:
                  type: boolean
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PatchLeadResponse"
        default:
          $ref: "#/components/responses/Error"
  /inbox/messages:
    post:
      tags: [Inbox]
      summary: Send or reply via Gmail
      operationId: sendMessage
      parameters:
        - $ref: "#/components/parameters/idempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [campaignId, leadId, text]
              additionalProperties: false
              properties:
                campaignId: { type: string }
                leadId: { type: string }
                text: { type: string, maxLength: 8000 }
                subject: { type: string, maxLength: 500 }
                accountId: { type: string }
      responses:
        "200":
          description: OK
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SendMessageResponse"
        default:
          $ref: "#/components/responses/Error"
components:
  headers:
    XRequestId:
      description: Unique request correlation id (always returned)
      schema: { type: string }
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  parameters:
    campaignId:
      name: campaignId
      in: path
      required: true
      schema: { type: string }
    leadId:
      name: leadId
      in: path
      required: true
      schema: { type: string }
    limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
    cursor:
      name: cursor
      in: query
      schema: { type: string, nullable: true }
    idempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema: { type: string, maxLength: 128 }
  responses:
    Error:
      description: Error
      headers:
        X-Request-Id:
          $ref: "#/components/headers/XRequestId"
        Retry-After:
          schema: { type: integer }
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorBody"
  schemas:
    ErrorBody:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }
            details: { type: object, additionalProperties: true }
    Credits:
      type: object
      required: [credits, topUpCredits, planId]
      properties:
        credits: { type: number }
        topUpCredits: { type: number }
        planId:
          type: string
          description: Workspace plan id, or "free" when none.
    Account:
      type: object
      required: [id, name, status, active, today]
      properties:
        id: { type: string }
        name: { type: string }
        status:
          type: string
          enum: [connected, connecting, failed, disconnected]
        active: { type: boolean }
        today:
          type: object
          required: [emailsSent, emailLimit]
          properties:
            emailsSent: { type: number }
            emailLimit: { type: number }
    Campaign:
      type: object
      required: [id, name, status, totalLeads, assignedTemplateId, stats]
      properties:
        id: { type: string }
        name: { type: string }
        status: { type: string }
        totalLeads: { type: number }
        assignedTemplateId: { type: string, nullable: true }
        stats:
          type: object
          properties:
            emailsSent: { type: number }
            repliesReceived: { type: number }
            replyRate: { type: number, nullable: true }
            emailsOpened: { type: number }
            openRate: { type: number, nullable: true }
    CreateCampaignResponse:
      type: object
      required: [campaignId, status, requestedLeadTarget, creditsReservedEstimate]
      properties:
        campaignId: { type: string }
        status: { type: string }
        requestedLeadTarget: { type: number }
        creditsReservedEstimate: { type: number }
    CampaignActionResponse:
      type: object
      required: [campaignId, status, action]
      properties:
        campaignId: { type: string }
        status: { type: string }
        action: { type: string }
    Lead:
      type: object
      properties:
        id: { type: string }
        fullName: { type: string, nullable: true }
        firstName: { type: string, nullable: true }
        lastName: { type: string, nullable: true }
        email: { type: string, nullable: true }
        companyName: { type: string, nullable: true }
        website: { type: string, nullable: true }
        jobTitle: { type: string, nullable: true }
        location: { type: string, nullable: true }
        tag: { type: string, nullable: true }
        replied: { type: boolean }
        needsReply: { type: boolean }
        excluded: { type: boolean }
        stage: { type: string }
        repliedAt: { type: string, nullable: true, description: ISO-8601 }
        lastInboundMessageAt: { type: string, nullable: true, description: ISO-8601 }
        taggedAt: { type: string, nullable: true, description: ISO-8601 }
    LeadDetail:
      allOf:
        - $ref: "#/components/schemas/Lead"
        - type: object
          properties:
            phone: { type: string, nullable: true }
            messageSent: { type: boolean }
    AddLeadResponse:
      type: object
      required: [leadId, status, creditsCharged]
      properties:
        leadId: { type: string }
        status:
          type: string
          enum: [created, duplicate, excluded]
        creditsCharged: { type: number }
    PatchLeadResponse:
      type: object
      required: [id, tag, excluded]
      properties:
        id: { type: string }
        tag: { type: string, nullable: true }
        excluded: { type: boolean }
    SendMessageResponse:
      type: object
      required: [ok, messageId, conversationId]
      properties:
        ok: { type: boolean }
        messageId: { type: string }
        conversationId: { type: string }
        warning: { type: string }
    ActivityEvent:
      type: object
      required: [id, type, timestamp]
      properties:
        id: { type: string }
        type:
          type: string
          enum: [email_sent, follow_up_sent, reply_received, agent_replied, lead_excluded]
        timestamp: { type: string, description: ISO-8601 }
        leadId: { type: string, nullable: true }
        leadName: { type: string, nullable: true }
        companyName: { type: string, nullable: true }
        campaignId: { type: string, nullable: true }
        messagePreview: { type: string, nullable: true, description: "≤80 chars" }
        handOffReason: { type: string, nullable: true }
        excludedSource:
          type: string
          nullable: true
          description: Set on lead_excluded; otherwise null
    ActivityListResponse:
      type: object
      required: [events, nextCursor]
      properties:
        events:
          type: array
          items:
            $ref: "#/components/schemas/ActivityEvent"
        nextCursor: { type: string, nullable: true }
    InsightsOverview:
      type: object
      required:
        - highlights
        - workspace
        - topCampaignsByReplyRate
        - topCampaignsByOpenRate
        - campaignsSampled
        - campaignsSampleCapped
      properties:
        highlights:
          type: object
          properties:
            lastActivityAt: { type: string, nullable: true }
            lastReplyAt: { type: string, nullable: true }
            lastReplyLeadName: { type: string, nullable: true }
            lastReplyCampaignId: { type: string, nullable: true }
        workspace:
          type: object
          properties:
            repliesReceived: { type: number }
            emailsSent: { type: number }
            emailOpens: { type: number }
            followUpsSent: { type: number }
        topCampaignsByReplyRate:
          type: array
          maxItems: 5
          items:
            $ref: "#/components/schemas/Campaign"
        topCampaignsByOpenRate:
          type: array
          maxItems: 5
          items:
            $ref: "#/components/schemas/Campaign"
        campaignsSampled: { type: number }
        campaignsSampleCapped: { type: boolean }
    AddLeadsBatchRequest:
      type: object
      required: [leads]
      additionalProperties: false
      properties:
        leads:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
            required: [email]
            additionalProperties: false
            properties:
              email: { type: string, maxLength: 254 }
              fullName: { type: string, maxLength: 120 }
              companyName: { type: string, maxLength: 200 }
              website: { type: string, maxLength: 500 }
    AddLeadsBatchResult:
      type: object
      required: [index, email, status, creditsCharged]
      properties:
        index: { type: integer }
        email: { type: string, nullable: true }
        leadId: { type: string, nullable: true }
        status:
          type: string
          enum: [created, duplicate, excluded, validation_error, insufficient_credits, failed]
        creditsCharged: { type: number }
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
    AddLeadsBatchResponse:
      type: object
      required: [results, summary, creditsCharged]
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/AddLeadsBatchResult"
        summary:
          type: object
          required:
            - total
            - created
            - duplicate
            - excluded
            - validation_error
            - insufficient_credits
            - failed
          properties:
            total: { type: integer }
            created: { type: integer }
            duplicate: { type: integer }
            excluded: { type: integer }
            validation_error: { type: integer }
            insufficient_credits: { type: integer }
            failed: { type: integer }
        creditsCharged: { type: number }
    UpdateLeadsBatchRequest:
      type: object
      required: [updates]
      additionalProperties: false
      properties:
        updates:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
            required: [leadId]
            additionalProperties: false
            properties:
              leadId: { type: string }
              tag: { type: string, nullable: true }
              excluded: { type: boolean }
    UpdateLeadsBatchResult:
      type: object
      required: [index, leadId, status]
      properties:
        index: { type: integer }
        leadId: { type: string, nullable: true }
        status:
          type: string
          enum: [updated, not_found, validation_error, failed]
        tag: { type: string, nullable: true }
        excluded: { type: boolean }
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
    UpdateLeadsBatchResponse:
      type: object
      required: [results, summary, creditsCharged]
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/UpdateLeadsBatchResult"
        summary:
          type: object
          required: [total, updated, not_found, validation_error, failed]
          properties:
            total: { type: integer }
            updated: { type: integer }
            not_found: { type: integer }
            validation_error: { type: integer }
            failed: { type: integer }
        creditsCharged: { type: number }
    DeleteLeadsBatchRequest:
      type: object
      required: [leadIds]
      additionalProperties: false
      properties:
        leadIds:
          type: array
          minItems: 1
          maxItems: 100
          items: { type: string }
    DeleteLeadsBatchResult:
      type: object
      required: [index, leadId, status]
      properties:
        index: { type: integer }
        leadId: { type: string, nullable: true }
        status:
          type: string
          enum: [deleted, not_found, validation_error, failed]
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
    DeleteLeadsBatchResponse:
      type: object
      required: [results, summary, creditsCharged, reconciliationFailed]
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/DeleteLeadsBatchResult"
        summary:
          type: object
          required: [total, deleted, not_found, validation_error, failed]
          properties:
            total: { type: integer }
            deleted: { type: integer }
            not_found: { type: integer }
            validation_error: { type: integer }
            failed: { type: integer }
        creditsCharged: { type: number }
        reconciliationFailed: { type: boolean }
    ValidateEmailRequest:
      type: object
      additionalProperties: false
      required: [email]
      properties:
        email:
          type: string
          maxLength: 254
        mode:
          type: string
          enum: [full, mx]
          default: full
    ValidateEmailResponse:
      type: object
      required: [email, mode, verdict, creditsCharged]
      properties:
        email: { type: string }
        mode: { type: string, enum: [full, mx] }
        verdict: { type: string, enum: [allow, block, unknown] }
        format:
          type: object
          properties:
            ok: { type: boolean }
        disposable:
          type: object
          properties:
            ok: { type: boolean }
        mx:
          type: object
          properties:
            status: { type: string, enum: [valid, invalid, transient, skipped] }
            domain: { type: string }
        zeroBounce:
          type: object
          properties:
            status: { type: string }
            subStatus: { type: string }
            source: { type: string, enum: [api, cache, disabled, error, skipped] }
        creditsCharged: { type: integer, enum: [0, 1] }
    ValidateEmailsRequest:
      type: object
      additionalProperties: false
      required: [emails]
      properties:
        emails:
          type: array
          minItems: 1
          maxItems: 100
          items: { type: string }
        mode:
          type: string
          enum: [full, mx]
          default: full
    ValidateEmailsResponse:
      type: object
      required: [results, summary, creditsCharged]
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              index: { type: integer }
              email: { type: string }
              status: { type: string, enum: [ok, insufficient_credits, failed] }
              creditsCharged: { type: integer }
              verdict: { type: string, enum: [allow, block, unknown] }
              format:
                type: object
                properties:
                  ok: { type: boolean }
              disposable:
                type: object
                properties:
                  ok: { type: boolean }
              mx:
                type: object
                properties:
                  status: { type: string, enum: [valid, invalid, transient, skipped] }
                  domain: { type: string }
              zeroBounce:
                type: object
                properties:
                  status: { type: string }
                  subStatus: { type: string }
                  source: { type: string, enum: [api, cache, disabled, error, skipped] }
        summary:
          type: object
          properties:
            total: { type: integer }
            ok: { type: integer }
            insufficient_credits: { type: integer }
            failed: { type: integer }
        creditsCharged: { type: integer }
    EnrichWebsiteResponse:
      type: object
      properties:
        website: { type: string }
        status: { type: string, enum: [ok, unreachable, no-website] }
        title: { type: string }
        description: { type: string }
        headings:
          type: array
          items: { type: string }
        textSnippet: { type: string }
        hasLlmsTxt: { type: boolean }
        llmsTxt: { type: string }
        sitemapFound: { type: boolean }
        recommendedPages:
          type: array
          items:
            type: object
            properties:
              url: { type: string }
              path: { type: string }
        pages:
          type: array
          items:
            type: object
            properties:
              url: { type: string }
              title: { type: string }
              snippet: { type: string }
        creditsCharged: { type: integer, enum: [0] }
