openapi: 3.0.3
info:
  title: LitInboxes API
  version: 1.0.0
  description: |
    LitInboxes public API.

    Response envelope contract:
    - Success: `{ "data": ... }` and optional `{ "meta": ... }`
    - Error: `{ "error": { "code": "...", "message": "...", "details": ... } }`
servers:
  - url: http://localhost:3258
    description: Local development

tags:
  - name: Auth
  - name: Organizations
  - name: Memberships
  - name: Billing
  - name: Verifications
  - name: Placement
  - name: Spam
  - name: Infrastructure
  - name: Sending Domains
  - name: API Keys
  - name: Admin
  - name: System
  - name: Webhooks

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT or API key token

  parameters:
    XOrgSlug:
      in: header
      name: X-Org-Slug
      required: false
      description: Organization scope slug. Omit for personal scope.
      schema:
        type: string
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: true
      schema:
        type: string
        minLength: 1
    Limit:
      in: query
      name: limit
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
    Page:
      in: query
      name: page
      required: false
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      in: query
      name: per_page
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
    ResourceId:
      in: path
      name: id
      required: true
      schema:
        type: string
        format: uuid
    StripeSignature:
      in: header
      name: Stripe-Signature
      required: true
      schema:
        type: string
    XSpamIngestToken:
      in: header
      name: X-Spam-Ingest-Token
      required: true
      schema:
        type: string
    XInfraIngestToken:
      in: header
      name: X-Infra-Ingest-Token
      required: true
      schema:
        type: string

  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            unauthorized:
              value:
                error:
                  code: auth_required
                  message: Authorization header is required
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            forbidden:
              value:
                error:
                  code: forbidden
                  message: You are not allowed to perform this action
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            not_found:
              value:
                error:
                  code: not_found
                  message: Couldn't find record
    ValidationError:
      description: Validation or request error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'

  schemas:
    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            details: {}

    PaginationMeta:
      type: object
      required: [page, per_page, total_pages]
      properties:
        page:
          type: integer
        per_page:
          type: integer
        total_pages:
          type: integer

    User:
      type: object
      required: [id, email, name, platform_admin]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
        platform_admin:
          type: boolean

    AuthSession:
      type: object
      required: [user, token]
      properties:
        user:
          $ref: '#/components/schemas/User'
        token:
          type: string

    Message:
      type: object
      required: [message]
      properties:
        message:
          type: string

    Organization:
      type: object
      required: [id, name, slug]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string

    Membership:
      type: object
      required: [id, user_id, organization_id, role, status]
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        organization_id:
          type: string
          format: uuid
        role:
          type: string
          enum: [owner, admin, member]
        status:
          type: string
          enum: [invited, active, suspended]

    CreditUsageDay:
      type: object
      required: [date, total_used, verification_used, placement_used, spam_used, infra_used]
      properties:
        date:
          type: string
          format: date
        total_used:
          type: integer
        verification_used:
          type: integer
        placement_used:
          type: integer
        spam_used:
          type: integer
        infra_used:
          type: integer

    Credits:
      type: object
      required: [verification_credits, placement_credits, spam_credits, infra_credits, usage_last_30_days, usage_summary, updated_at]
      properties:
        verification_credits:
          type: integer
        placement_credits:
          type: integer
        spam_credits:
          type: integer
        infra_credits:
          type: integer
        verification_credits_plan:
          type: integer
        verification_credits_purchased:
          type: integer
        spam_credits_plan:
          type: integer
        spam_credits_purchased:
          type: integer
        usage_summary:
          type: array
          items:
            type: object
            required: [resource, used, limit, unit]
            properties:
              resource:
                type: string
              used:
                type: integer
              limit:
                type: integer
              unit:
                type: string
        usage_last_30_days:
          type: array
          minItems: 30
          maxItems: 30
          items:
            $ref: '#/components/schemas/CreditUsageDay'
        updated_at:
          type: string
          format: date-time

    Subscription:
      type: object
      nullable: true
      properties:
        id:
          type: string
          format: uuid
        plan:
          type: string
          enum: [starter, pro, enterprise]
        status:
          type: string
          enum: [trialing, active, past_due, canceled, incomplete]
        stripe_customer_id:
          type: string
          nullable: true
        stripe_subscription_id:
          type: string
          nullable: true
        period_start:
          type: string
          format: date-time
          nullable: true
        period_end:
          type: string
          format: date-time
          nullable: true

    CheckoutSession:
      type: object
      required: [checkout_url, mode, kind]
      properties:
        checkout_url:
          type: string
          format: uri
        mode:
          type: string
          enum: [subscription, payment]
        kind:
          type: string
          enum: [subscription, top_up]

    VerificationJob:
      type: object
      required: [id, mode, status, total_count, billable_count, processed_count, created_at]
      properties:
        id:
          type: string
          format: uuid
        mode:
          type: string
          enum: [single, bulk]
        status:
          type: string
          enum: [pending, processing, done, failed]
        total_count:
          type: integer
        billable_count:
          type: integer
        processed_count:
          type: integer
        original_filename:
          type: string
          nullable: true
        preview_email:
          type: string
          format: email
          nullable: true
        created_at:
          type: string
          format: date-time

    VerificationResult:
      type: object
      required: [email, status, syntax_ok, mx_ok, smtp_ok, disposable, role_based, catch_all,
                 previously_bounced, previous_complaint, platform_risk, risk_reasons]
      properties:
        email:
          type: string
          format: email
        status:
          type: string
          enum: [valid, invalid, risky, unknown]
        syntax_ok:
          type: boolean
        mx_ok:
          type: boolean
        smtp_ok:
          type: boolean
        disposable:
          type: boolean
        role_based:
          type: boolean
        catch_all:
          type: boolean
        previously_bounced:
          type: boolean
        previous_complaint:
          type: boolean
        platform_risk:
          type: boolean
        risk_reasons:
          type: array
          items:
            type: string
        error_message:
          type: string
          nullable: true

    VerificationJobDetail:
      allOf:
        - $ref: '#/components/schemas/VerificationJob'
        - type: object
          required: [summary, results]
          properties:
            summary:
              type: object
              additionalProperties:
                type: integer
            results:
              type: array
              items:
                $ref: '#/components/schemas/VerificationResult'

    SendingDomain:
      type: object
      required: [id, domain, provider, verification_status, dkim_status, spf_status]
      properties:
        id:
          type: string
          format: uuid
        domain:
          type: string
        provider:
          type: string
          enum: [ses, postmark, mailgun]
        verification_status:
          type: string
          enum: [pending, verified, failed]
        dkim_status:
          type: string
          enum: [unknown, pass, warn, fail]
        spf_status:
          type: string
          enum: [unknown, pass, warn, fail]
        verified_at:
          type: string
          format: date-time
          nullable: true
        infra_monitor:
          allOf:
            - $ref: '#/components/schemas/SendingDomainMonitor'
          nullable: true

    SendingDomainMonitor:
      type: object
      required: [id, sending_domain_id, status, dkim_selectors, frequency_hours, next_run_at]
      properties:
        id:
          type: string
          format: uuid
        sending_domain_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [active, paused]
        dkim_selectors:
          type: array
          minItems: 1
          maxItems: 3
          items:
            type: string
        frequency_hours:
          type: integer
          enum: [6]
        next_run_at:
          type: string
          format: date-time
        last_check_id:
          type: string
          format: uuid
          nullable: true

    PlacementTestListItem:
      type: object
      required: [id, status, identifier, tracking_message_id, seed_mailbox_count, summary, created_at]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, sending, polling, done, failed]
        identifier:
          type: string
        tracking_message_id:
          type: string
        seed_mailbox_count:
          type: integer
        summary:
          type: object
          additionalProperties:
            type: integer
        polling_started_at:
          type: string
          format: date-time
          nullable: true
        first_result_at:
          type: string
          format: date-time
          nullable: true
        polling_completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    PlacementSeedTarget:
      type: object
      required: [seed_mailbox_id, provider, email_address]
      properties:
        seed_mailbox_id:
          type: string
          format: uuid
        provider:
          type: string
          enum: [gmail, outlook, yahoo, icloud, other]
        email_address:
          type: string
          format: email

    PlacementCreateAccepted:
      type: object
      required: [id, status, identifier, tracking_message_id, seed_mailbox_count, seed_targets]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, sending, polling, done, failed]
        identifier:
          type: string
        tracking_message_id:
          type: string
        seed_mailbox_count:
          type: integer
        seed_targets:
          type: array
          items:
            $ref: '#/components/schemas/PlacementSeedTarget'

    PlacementStartAccepted:
      type: object
      required: [id, status, identifier]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, sending, polling, done, failed]
        identifier:
          type: string
        polling_started_at:
          type: string
          format: date-time
          nullable: true

    PlacementResult:
      allOf:
        - $ref: '#/components/schemas/PlacementSeedTarget'
        - type: object
          required: [placement]
          properties:
            seed_email:
              type: string
              format: email
            placement:
              type: string
              enum: [inbox, spam, missing]
            received_at:
              type: string
              format: date-time
              nullable: true
            folder_name:
              type: string
              nullable: true

    PlacementDetail:
      type: object
      required: [id, status, identifier, tracking_message_id, summary, results]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, sending, polling, done, failed]
        identifier:
          type: string
        tracking_message_id:
          type: string
        summary:
          type: object
          additionalProperties:
            type: integer
        seed_mailbox_count:
          type: integer
        polling_started_at:
          type: string
          format: date-time
          nullable: true
        first_result_at:
          type: string
          format: date-time
          nullable: true
        polling_completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        results:
          type: array
          items:
            $ref: '#/components/schemas/PlacementResult'

    SpamCheck:
      type: object
      required: [id, status, check_mode, overall_status, created_at]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, processing, done, failed]
        check_mode:
          type: string
          enum: [pasted_content, identifier_email]
        subject:
          type: string
          nullable: true
        identifier_token:
          type: string
          nullable: true
        waiting_for_email:
          type: boolean
        received_email_at:
          type: string
          format: date-time
          nullable: true
        rspamd_score:
          type: number
          nullable: true
        rspamd_action:
          type: string
          nullable: true
        rspamd_symbols:
          type: object
        inhouse_flags:
          type: object
        overall_status:
          type: string
          enum: [unknown, pass, warn, fail]
        created_at:
          type: string
          format: date-time

    InfraCheck:
      type: object
      required: [id, status, check_mode, domain, waiting_for_email, spf_status, dkim_status, dmarc_status, mx_status, bimi_status, blacklist_results, created_at]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, processing, done, failed]
        check_mode:
          type: string
          enum: [dns_records, identifier_email]
        domain:
          type: string
          nullable: true
        dkim_selector:
          type: string
          nullable: true
        dmarc_selector:
          type: string
          nullable: true
        identifier_token:
          type: string
          nullable: true
        waiting_for_email:
          type: boolean
        received_email_at:
          type: string
          format: date-time
          nullable: true
        spf_status:
          type: string
          enum: [unknown, pass, warn, fail]
        dkim_status:
          type: string
          enum: [unknown, pass, warn, fail]
        dmarc_status:
          type: string
          enum: [unknown, pass, warn, fail]
        mx_status:
          type: string
          enum: [unknown, pass, warn, fail]
        bimi_status:
          type: string
          enum: [unknown, pass, warn, fail]
        blacklist_results:
          type: array
          items:
            type: object
        checked_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    InfraCreateAccepted:
      type: object
      required: [id, status, check_mode, domain]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, processing, done, failed]
        check_mode:
          type: string
          enum: [dns_records, identifier_email]
        domain:
          type: string
          nullable: true
        identifier_token:
          type: string
          nullable: true
        forward_to:
          type: string
          nullable: true
        waiting_for_email:
          type: boolean
          nullable: true

    InfraMonitorLastCheck:
      type: object
      required: [id, status, spf_status, dkim_status, dmarc_status, mx_status, bimi_status, created_at]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, processing, done, failed]
        spf_status:
          type: string
          enum: [unknown, pass, warn, fail]
        dkim_status:
          type: string
          enum: [unknown, pass, warn, fail]
        dmarc_status:
          type: string
          enum: [unknown, pass, warn, fail]
        mx_status:
          type: string
          enum: [unknown, pass, warn, fail]
        bimi_status:
          type: string
          enum: [unknown, pass, warn, fail]
        checked_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    InfraMonitor:
      type: object
      required: [id, sending_domain_id, domain, dkim_selectors, status, frequency_hours, next_run_at, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        sending_domain_id:
          type: string
          format: uuid
        domain:
          type: string
        dkim_selectors:
          type: array
          minItems: 1
          maxItems: 3
          items:
            type: string
        status:
          type: string
          enum: [active, paused]
        frequency_hours:
          type: integer
          enum: [6]
        next_run_at:
          type: string
          format: date-time
        last_check:
          allOf:
            - $ref: '#/components/schemas/InfraMonitorLastCheck'
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ApiKey:
      type: object
      required: [id, name, prefix, scopes]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        prefix:
          type: string
        scopes:
          type: array
          items:
            type: string
        last_used_at:
          type: string
          format: date-time
          nullable: true
        revoked_at:
          type: string
          format: date-time
          nullable: true
        expires_at:
          type: string
          format: date-time
          nullable: true

    ApiKeyCreateResponse:
      allOf:
        - $ref: '#/components/schemas/ApiKey'
        - type: object
          required: [token]
          properties:
            token:
              type: string
              description: Returned once at creation. Not returned by list endpoints.

    SeedMailbox:
      type: object
      required: [id, provider, email_address, imap_host, imap_port, active]
      properties:
        id:
          type: string
          format: uuid
        provider:
          type: string
          enum: [gmail, outlook, yahoo, icloud, other]
        email_address:
          type: string
          format: email
        imap_host:
          type: string
        imap_port:
          type: integer
        active:
          type: boolean

    AdminStatus:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [ok, error]
        detail:
          type: string
          nullable: true

    SidekiqStatus:
      oneOf:
        - type: object
          required: [processed, failed, enqueued, retry_size, dead_size]
          properties:
            processed:
              type: integer
            failed:
              type: integer
            enqueued:
              type: integer
            retry_size:
              type: integer
            dead_size:
              type: integer
        - $ref: '#/components/schemas/AdminStatus'

    AdminSystem:
      type: object
      required: [database, redis, sidekiq, time]
      properties:
        database:
          $ref: '#/components/schemas/AdminStatus'
        redis:
          $ref: '#/components/schemas/AdminStatus'
        sidekiq:
          $ref: '#/components/schemas/SidekiqStatus'
        time:
          type: string
          format: date-time

    Health:
      type: object
      required: [status, time]
      properties:
        status:
          type: string
          enum: [ok]
        time:
          type: string
          format: date-time

    RegisterRequest:
      type: object
      required: [email, name, password, password_confirmation]
      properties:
        email:
          type: string
          format: email
        name:
          type: string
        password:
          type: string
          minLength: 8
        password_confirmation:
          type: string
          minLength: 8

    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string

    ForgotPasswordRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email

    ResetPasswordRequest:
      type: object
      required: [token, password, password_confirmation]
      properties:
        token:
          type: string
        password:
          type: string
        password_confirmation:
          type: string

    OrganizationCreateRequest:
      type: object
      required: [name, slug]
      properties:
        name:
          type: string
        slug:
          type: string

    MembershipCreateRequest:
      type: object
      required: [email]
      properties:
        organization_id:
          type: string
          format: uuid
          nullable: true
          description: Optional when X-Org-Slug is provided.
        email:
          type: string
          format: email
        role:
          type: string
          enum: [owner, admin, member]

    MembershipUpdateRequest:
      type: object
      properties:
        role:
          type: string
          enum: [owner, admin, member]
        status:
          type: string
          enum: [invited, active, suspended]

    SubscriptionCheckoutRequest:
      type: object
      properties:
        kind:
          type: string
          enum: [subscription, top_up]
          default: subscription
        price_id:
          type: string
          description: Required for top_up. Optional for subscription when STRIPE_DEFAULT_PRICE_ID is configured.

    VerificationCreateRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email

    PlacementCreateRequest:
      type: object
      description: No fields are required. Server returns a unique identifier and seed inbox targets.
      additionalProperties: false

    SpamCreateRequest:
      type: object
      description: |
        Set check_mode to pasted_content (default) and provide eml_base64 or headers/body fields,
        or set check_mode to identifier_email to receive an identifier token and send the message later.
      properties:
        check_mode:
          type: string
          enum: [pasted_content, identifier_email]
        eml_base64:
          type: string
        headers:
          type: string
        body_text:
          type: string
        body_html:
          type: string

    SpamCreateAccepted:
      type: object
      required: [id, status, check_mode]
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, processing, done, failed]
        check_mode:
          type: string
          enum: [pasted_content, identifier_email]
        identifier_token:
          type: string
          nullable: true
        forward_to:
          type: string
          description: Inbox address to receive identifier-mode messages.
          nullable: true
        waiting_for_email:
          type: boolean
          nullable: true

    SpamIngestRequest:
      type: object
      required: [raw_email]
      properties:
        raw_email:
          type: string
          description: Full RFC822 message content including headers.

    InfraCreateRequest:
      type: object
      properties:
        check_mode:
          type: string
          enum: [dns_records, identifier_email]
        domain:
          type: string
        dkim_selector:
          type: string
        dmarc_selector:
          type: string

    InfraIngestRequest:
      type: object
      required: [raw_email]
      properties:
        raw_email:
          type: string
          description: Full RFC822 message content including headers.

    InfraMonitorUpdateRequest:
      type: object
      properties:
        status:
          type: string
          enum: [active, paused]
        dkim_selectors:
          type: array
          minItems: 1
          maxItems: 3
          items:
            type: string

    SendingDomainCreateRequest:
      type: object
      required: [domain]
      properties:
        domain:
          type: string
        provider:
          type: string
          enum: [ses, postmark, mailgun]
        provider_domain_id:
          type: string

    ApiKeyCreateRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        scopes:
          type: array
          items:
            type: string
        expires_at:
          type: string
          format: date-time

    SeedMailboxCreateRequest:
      type: object
      required: [provider, email_address, imap_host, imap_port, credentials]
      properties:
        provider:
          type: string
          enum: [gmail, outlook, yahoo, icloud, other]
        email_address:
          type: string
          format: email
        imap_host:
          type: string
        imap_port:
          type: integer
        credentials:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
        active:
          type: boolean

    SeedMailboxUpdateRequest:
      type: object
      properties:
        provider:
          type: string
          enum: [gmail, outlook, yahoo, icloud, other]
        email_address:
          type: string
          format: email
        imap_host:
          type: string
        imap_port:
          type: integer
        credentials:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
        active:
          type: boolean

security:
  - bearerAuth: []

paths:
  /api/v1/auth/register:
    post:
      tags: [Auth]
      summary: Register user
      operationId: registerUser
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
      responses:
        '201':
          description: User registered
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/AuthSession'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/auth/login:
    post:
      tags: [Auth]
      summary: Login with email/password
      operationId: loginUser
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Session token
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/AuthSession'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/auth/logout:
    delete:
      tags: [Auth]
      summary: Logout current session
      operationId: logoutUser
      responses:
        '204':
          description: Logged out
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/auth/me:
    get:
      tags: [Auth]
      summary: Get current user
      operationId: getCurrentUser
      responses:
        '200':
          description: Current user profile
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/auth/forgot_password:
    post:
      tags: [Auth]
      summary: Send password reset instructions
      operationId: forgotPassword
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ForgotPasswordRequest'
      responses:
        '200':
          description: Accepted
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Message'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/auth/reset_password:
    post:
      tags: [Auth]
      summary: Reset password with token
      operationId: resetPassword
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordRequest'
      responses:
        '200':
          description: Password updated
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Message'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/organizations:
    get:
      tags: [Organizations]
      summary: List organizations for current user
      operationId: listOrganizations
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Organizations
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Organization'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      tags: [Organizations]
      summary: Create organization
      operationId: createOrganization
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrganizationCreateRequest'
      responses:
        '201':
          description: Organization created
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Organization'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/organizations/{id}:
    get:
      tags: [Organizations]
      summary: Get organization by id
      operationId: getOrganization
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Organization
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Organization'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/memberships:
    post:
      tags: [Memberships]
      summary: Invite/add organization membership
      operationId: createMembership
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MembershipCreateRequest'
      responses:
        '201':
          description: Membership created
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Membership'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/memberships/{id}:
    patch:
      tags: [Memberships]
      summary: Update membership role/status
      operationId: updateMembership
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MembershipUpdateRequest'
      responses:
        '200':
          description: Membership updated
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Membership'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/credits:
    get:
      tags: [Billing]
      summary: Get credit balances
      operationId: getCredits
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Credit balances
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Credits'

  /api/v1/subscription:
    get:
      tags: [Billing]
      summary: Get subscription state
      operationId: getSubscription
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Subscription or null
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Subscription'

    post:
      tags: [Billing]
      summary: Create Stripe checkout session
      description: Creates a subscription or top-up checkout session.
      operationId: createCheckoutSession
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscriptionCheckoutRequest'
      responses:
        '200':
          description: Checkout session URL
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/CheckoutSession'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/verifications:
    get:
      tags: [Verifications]
      summary: List verification jobs
      operationId: listVerificationJobs
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Verification jobs
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/VerificationJob'

    post:
      tags: [Verifications]
      summary: Create single email verification job
      operationId: createVerification
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerificationCreateRequest'
      responses:
        '202':
          description: Verification accepted
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/VerificationJob'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/verifications/bulk:
    post:
      tags: [Verifications]
      summary: Create bulk verification job
      operationId: createVerificationBulk
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                has_header:
                  type: boolean
                dedupe:
                  type: boolean
      responses:
        '202':
          description: Verification accepted
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/VerificationJob'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/verifications/{id}:
    get:
      tags: [Verifications]
      summary: Get verification job details
      operationId: getVerification
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
      responses:
        '200':
          description: Verification detail
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/VerificationJobDetail'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/verifications/{id}/download:
    get:
      tags: [Verifications]
      summary: Download verification results CSV
      operationId: downloadVerificationCsv
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: CSV download
          content:
            text/csv:
              schema:
                type: string
                format: binary
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/sending_domains:
    get:
      tags: [Sending Domains]
      summary: List sending domains
      operationId: listSendingDomains
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Sending domains
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/SendingDomain'

    post:
      tags: [Sending Domains]
      summary: Create sending domain
      operationId: createSendingDomain
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendingDomainCreateRequest'
      responses:
        '201':
          description: Sending domain created
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/SendingDomain'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/sending_domains/{id}/verify:
    post:
      tags: [Sending Domains]
      summary: Verify sending domain
      operationId: verifySendingDomain
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Sending domain verification result
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/SendingDomain'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/placement_tests:
    get:
      tags: [Placement]
      summary: List placement tests
      operationId: listPlacementTests
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Placement tests
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlacementTestListItem'

    post:
      tags: [Placement]
      summary: Generate placement check setup
      operationId: createPlacementTest
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlacementCreateRequest'
      responses:
        '202':
          description: Placement test accepted
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/PlacementCreateAccepted'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/placement_tests/{id}:
    get:
      tags: [Placement]
      summary: Get placement test results
      operationId: getPlacementTest
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Placement details
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/PlacementDetail'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/placement_tests/{id}/start:
    post:
      tags: [Placement]
      summary: Start polling seed inboxes for a placement test
      operationId: startPlacementTestPolling
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '202':
          description: Polling started
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/PlacementStartAccepted'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/spam_checks:
    get:
      tags: [Spam]
      summary: List spam checks
      operationId: listSpamChecks
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Spam checks
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/SpamCheck'

    post:
      tags: [Spam]
      summary: Create spam check
      operationId: createSpamCheck
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SpamCreateRequest'
      responses:
        '202':
          description: Spam check accepted
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/SpamCreateAccepted'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/spam_checks/ingest:
    post:
      tags: [Spam]
      summary: Ingest inbound spam check email
      operationId: ingestSpamCheckEmail
      security: []
      parameters:
        - $ref: '#/components/parameters/XSpamIngestToken'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SpamIngestRequest'
      responses:
        '202':
          description: Email accepted and spam check queued
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [id, status, identifier_token, queued]
                    properties:
                      id:
                        type: string
                        format: uuid
                      status:
                        type: string
                        enum: [pending, processing, done, failed]
                      identifier_token:
                        type: string
                      queued:
                        type: boolean
        '200':
          description: Duplicate ingest accepted (already queued/processed)
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [id, status, identifier_token, queued]
                    properties:
                      id:
                        type: string
                        format: uuid
                      status:
                        type: string
                        enum: [pending, processing, done, failed]
                      identifier_token:
                        type: string
                      queued:
                        type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/spam_checks/{id}:
    get:
      tags: [Spam]
      summary: Get spam check
      operationId: getSpamCheck
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Spam check detail
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/SpamCheck'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/infra_checks:
    get:
      tags: [Infrastructure]
      summary: List infrastructure checks
      operationId: listInfraChecks
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Infrastructure checks
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/InfraCheck'

    post:
      tags: [Infrastructure]
      summary: Create infrastructure check
      operationId: createInfraCheck
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InfraCreateRequest'
      responses:
        '202':
          description: Infrastructure check accepted
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/InfraCreateAccepted'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/infra_checks/ingest:
    post:
      tags: [Infrastructure]
      summary: Ingest inbound infrastructure check email
      operationId: ingestInfraCheckEmail
      parameters:
        - $ref: '#/components/parameters/XInfraIngestToken'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InfraIngestRequest'
      responses:
        '202':
          description: Email accepted and infrastructure check queued
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [id, status, identifier_token, queued]
                    properties:
                      id:
                        type: string
                        format: uuid
                      status:
                        type: string
                        enum: [pending, processing, done, failed]
                      identifier_token:
                        type: string
                      queued:
                        type: boolean
        '200':
          description: Duplicate ingest accepted (already queued/processed)
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [id, status, identifier_token, queued]
                    properties:
                      id:
                        type: string
                        format: uuid
                      status:
                        type: string
                        enum: [pending, processing, done, failed]
                      identifier_token:
                        type: string
                      queued:
                        type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/infra_checks/{id}:
    get:
      tags: [Infrastructure]
      summary: Get infrastructure check
      operationId: getInfraCheck
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Infrastructure check detail
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/InfraCheck'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/infra_monitors:
    get:
      tags: [Infrastructure]
      summary: List infrastructure monitors
      operationId: listInfraMonitors
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: Infrastructure monitors
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/InfraMonitor'

  /api/v1/infra_monitors/{id}:
    patch:
      tags: [Infrastructure]
      summary: Update infrastructure monitor
      operationId: updateInfraMonitor
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InfraMonitorUpdateRequest'
      responses:
        '200':
          description: Infrastructure monitor updated
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/InfraMonitor'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/api_keys:
    get:
      tags: [API Keys]
      summary: List API keys
      operationId: listApiKeys
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '200':
          description: API keys
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKey'

    post:
      tags: [API Keys]
      summary: Create API key
      operationId: createApiKey
      parameters:
        - $ref: '#/components/parameters/XOrgSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiKeyCreateRequest'
      responses:
        '201':
          description: API key created
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/ApiKeyCreateResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/api_keys/{id}:
    delete:
      tags: [API Keys]
      summary: Revoke API key
      operationId: revokeApiKey
      parameters:
        - $ref: '#/components/parameters/ResourceId'
        - $ref: '#/components/parameters/XOrgSlug'
      responses:
        '204':
          description: API key revoked
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/admin/seed_mailboxes:
    get:
      tags: [Admin]
      summary: List seed mailboxes (platform admin)
      operationId: listSeedMailboxes
      responses:
        '200':
          description: Seed mailboxes
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/SeedMailbox'
        '403':
          $ref: '#/components/responses/Forbidden'

    post:
      tags: [Admin]
      summary: Create seed mailbox (platform admin)
      operationId: createSeedMailbox
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SeedMailboxCreateRequest'
      responses:
        '201':
          description: Seed mailbox created
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/SeedMailbox'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/admin/seed_mailboxes/{id}:
    patch:
      tags: [Admin]
      summary: Update seed mailbox (platform admin)
      operationId: updateSeedMailbox
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SeedMailboxUpdateRequest'
      responses:
        '200':
          description: Seed mailbox updated
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/SeedMailbox'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/admin/seed_mailboxes/{id}/test_connection:
    post:
      tags: [Admin]
      summary: Test IMAP connection for seed mailbox (platform admin)
      operationId: testSeedMailboxConnection
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      responses:
        '200':
          description: Connection succeeded
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [status]
                    properties:
                      status:
                        type: string
                        enum: [ok]
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/admin/system:
    get:
      tags: [Admin, System]
      summary: Platform system health snapshot (platform admin)
      operationId: getAdminSystemHealth
      responses:
        '200':
          description: System health payload
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/AdminSystem'
        '403':
          $ref: '#/components/responses/Forbidden'

  /api/v1/webhooks/stripe:
    post:
      tags: [Webhooks]
      summary: Stripe webhook receiver
      operationId: stripeWebhook
      security: []
      parameters:
        - $ref: '#/components/parameters/StripeSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: Event accepted or deduplicated
        '400':
          description: Invalid signature or payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: Webhook secret missing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'

  /api/v1/webhooks/dmarc/sns:
    post:
      tags: [Webhooks]
      summary: DMARC inbound SNS webhook (SES receiving)
      description: |
        Receives Amazon SNS notifications for DMARC aggregate report mail ingested via SES on
        dmarcreports.litinboxes.com. Handles SubscriptionConfirmation (auto-confirms via SubscribeURL)
        and Notification messages containing base64-encoded MIME with dmarc+{token} recipients.
      operationId: dmarcSnsWebhook
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: SNS message accepted
        '400':
          description: Invalid JSON payload
        '401':
          description: Invalid SNS signature or topic ARN
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: Subscription confirmation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: DMARC_SNS_TOPIC_ARN not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'

  /api/v1/webhooks/dmarc/forensic/sns:
    post:
      tags: [Webhooks]
      summary: DMARC forensic inbound SNS webhook (SES receiving)
      description: |
        Receives Amazon SNS notifications for DMARC forensic (RUF) failure-report mail ingested via SES on
        forensic.litinboxes.com. Handles SubscriptionConfirmation (auto-confirms via SubscribeURL)
        and Notification messages containing base64-encoded MIME with dmarc+{token} recipients.
      operationId: dmarcForensicSnsWebhook
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: SNS message accepted
        '400':
          description: Invalid JSON payload
        '401':
          description: Invalid SNS signature or topic ARN
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: Subscription confirmation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: DMARC_FORENSIC_SNS_TOPIC_ARN not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'

  /api/v1/health:
    get:
      tags: [System]
      summary: Public health endpoint
      operationId: getHealth
      security: []
      responses:
        '200':
          description: Service healthy
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Health'
