openapi: 3.2.0
info:
  title: MiniMailer API
  version: 1.0.0
  description: |
    JSON:API endpoints for MiniMailer.

    Content negotiation follows JSON:API v1.1: mutation requests must send
    `Content-Type: application/vnd.api+json` (a `profile` media type parameter is accepted and
    ignored; an `ext` parameter or any other parameter such as `charset` returns 415, as no
    JSON:API extensions are supported). Any request whose `Accept` header mentions the JSON:API
    media type only in instances modified by disallowed parameters — or only with unsupported
    `ext` extensions — returns 406.
  license:
    name: Proprietary
    url: https://minimailer.app/terms
servers:
  - url: https://api.minimailer.app
    description: Production
tags:
  - name: Audiences
    description: Audience management operations.
  - name: Contacts
    description: Contact management operations.
  - name: Campaigns
    description: Campaign management operations.
  - name: Domains
    description: Sending domain management operations.
  - name: OAuth
    description: Public OAuth 2.1 endpoints and discovery metadata for MCP clients (apex host, not the api. subdomain).
  - name: Inboxes
    description: Inbox management operations.
  - name: InboundEmails
    description: Inbound email retrieval operations.
  - name: OutboundEmails
    description: Outbound email delivery operations.
  - name: Suppressions
    description: Suppression list operations.
  - name: Webhooks
    description: Webhook management operations.
  - name: Tokens
    description: Personal access token management operations.
  - name: Recipients
    description: Recipient privacy and right-to-forget operations.
  - name: Compliance
    description: Account-level compliance and audit export operations.
security:
  - bearerAuth: []
paths:
  /tokens:
    post:
      tags:
        - Tokens
      summary: Create a personal access token
      description: |
        Issues a personal access token for the authenticated user and returns the plaintext bearer
        token **once**, in the response's `token` attribute. It is never retrievable again, so
        capture it on receipt.

        The token's scopes and lifetime (`1_month`, `6_months`, or `12_months`) are fixed at
        creation; it can call only the endpoints its granted scopes permit. Use it as
        `Authorization: Bearer <token>` on every subsequent request.

        Requires the `tokens:write` scope.
      operationId: createToken
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateTokenRequestDocument'
      responses:
        '201':
          description: Personal access token created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/TokenDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /tokens/{token}:
    delete:
      tags:
        - Tokens
      summary: Revoke a personal access token
      description: |
        Permanently revokes one of the authenticated user's personal access tokens. The token stops
        authenticating immediately on the next request. A token that does not exist or belongs to
        another user returns `404` rather than revealing its existence.

        Requires the `tokens:write` scope.
      operationId: deleteToken
      parameters:
        - name: token
          in: path
          required: true
          description: The token identifier.
          schema:
            type: string
      responses:
        '204':
          description: Personal access token revoked.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /domains:
    post:
      tags:
        - Domains
      summary: Create a sending domain
      description: |
        Registers a new sending domain and returns it in the `pending` state. MiniMailer generates
        the DKIM, SPF, and MAIL FROM (MX) records you must publish in DNS before it will send from
        the domain.

        **Verification flow**

        1. Fetch the generated records from `GET /domains/{domain}/records`.
        2. Publish them at your DNS provider.
        3. MiniMailer polls DNS and advances the domain `pending` → `verifying` → `active`. It lands
           on `failed` if the records do not resolve within the verification window.

        A domain must be `active` before any transactional or campaign email can be sent from it.
        Domain names are unique per account. Requires the `domains:write` scope.
      operationId: createDomain
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateDomainRequestDocument'
      responses:
        '201':
          description: Sending domain created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/DomainDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    get:
      tags:
        - Domains
      summary: List sending domains
      description: |
        Returns the authenticated user's sending domains, newest first. Supports the JSON:API
        `filter`, `sort`, `include`, and sparse `fields` query parameters. Requires the
        `domains:read` scope.
      operationId: listDomains
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Sending domains retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/DomainCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /domains/{domain}:
    get:
      tags:
        - Domains
      summary: Show a sending domain
      description: |
        Returns a single sending domain owned by the authenticated user, including its current
        verification `status` (`pending`, `verifying`, `active`, `suspended`, or `failed`). Requires
        the `domains:read` scope.
      operationId: showDomain
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: domain
          in: path
          required: true
          description: The domain identifier.
          schema:
            $ref: '#/components/schemas/DomainId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Sending domain retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/DomainDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    patch:
      tags:
        - Domains
      summary: Update a sending domain
      description: |
        Updates the mutable settings of a sending domain owned by the authenticated user. Four
        operations are supported, individually or together:

        - **TLS policy** — set `Require` or `Optional` transport encryption.
        - **MAIL FROM prefix** — changing it re-issues the MX/SPF records and reverts an `active`
          domain to `verifying` until the new records resolve.
        - **DKIM key rotation** — set `rotate_dkim_key`; a new selector is generated and the domain
          returns to `verifying` until its record is published.
        - **Open/click tracking** — toggle tracking and choose the redirect mode. `branded`
          (per-customer redirect domain) requires a Growth plan or above, otherwise `422`.

        The domain name itself is immutable. Requires the `domains:write` scope.
      operationId: updateDomain
      parameters:
        - name: domain
          in: path
          required: true
          description: The domain identifier.
          schema:
            $ref: '#/components/schemas/DomainId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/UpdateDomainRequestDocument'
      responses:
        '200':
          description: Sending domain updated.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/DomainDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    delete:
      tags:
        - Domains
      summary: Delete a sending domain
      description: |
        Removes a sending domain owned by the authenticated user and tears down its sending
        configuration. The DKIM secret is retained for a 7-day recovery window before permanent
        deletion.

        The domain row is soft-deleted so existing `sends`, `campaigns`, and `suppressions` keep their
        reference for audit and GDPR retention; their statistics remain queryable. Sending from the
        domain stops immediately. If the teardown fails the domain is left `suspended` and the
        request returns `502`. Requires the `domains:write` scope.
      operationId: deleteDomain
      parameters:
        - name: domain
          in: path
          required: true
          description: The domain identifier.
          schema:
            $ref: '#/components/schemas/DomainId'
      responses:
        '204':
          description: Sending domain deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /domains/{domain}/records:
    get:
      tags:
        - Domains
      summary: List domain DNS records
      description: |
        Returns the DNS records (DKIM, SPF, MAIL FROM MX, and DMARC where applicable) that must be
        published at your DNS provider to verify and use a sending domain. Each record carries its
        `record_type`, `name`, `value`, `ttl`, and a `verified` flag so you can track which entries
        MiniMailer has already confirmed.

        Poll this endpoint after publishing records to watch verification progress. Requires the
        `domains:read` scope.
      operationId: listDomainRecords
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
        - name: domain
          in: path
          required: true
          description: The domain identifier.
          schema:
            $ref: '#/components/schemas/DomainId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Domain DNS records retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/RecordCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /outbound-emails:
    post:
      tags:
        - OutboundEmails
      summary: Create an outbound email
      description: |
        Queues a single transactional email for delivery through the sending domain that owns the
        `from` address, and returns it in the `queued` state. Delivery is asynchronous: track
        progress by polling the resource or, preferably, by subscribing to webhooks — the send
        advances `queued` → `sent` → `delivered`, or to a terminal `bounced`, `complained`,
        `rejected`, `suppressed`, or `failed` state.

        The send is rejected with `422` when any recipient — `to`, `cc`, or `bcc` — carries an
        active `all`-scope suppression on the domain (bounce, complaint, or manual entry; a
        marketing-only unsubscribe does not block transactional sends), and the sending domain
        must be `active`. This endpoint
        additionally requires an active subscription; without one it returns `403`.

        Each recipient (`to` plus every `cc` and `bcc` address) counts as one email against the
        plan's included volume. The send's `status` tracks the primary (`to`) recipient's delivery
        journey; bounces or complaints affecting only `cc`/`bcc` recipients are recorded as events
        and suppressions without changing the send's status.

        Requires the `emails:send` scope.
      operationId: createOutboundEmail
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateOutboundEmailRequestDocument'
      responses:
        '201':
          description: Outbound email created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/OutboundEmailDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
          description: The referenced sending domain does not exist.
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '409':
          $ref: '#/components/responses/ConflictError'
          description: The `idempotency_key` was already used with a different request payload.
        '422':
          $ref: '#/components/responses/ValidationError'
          description: The request document failed validation, including when a recipient carries an active `all`-scope suppression on the domain.
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    get:
      tags:
        - OutboundEmails
      summary: List outbound emails
      description: |
        Returns the authenticated user's outbound emails, newest first. Use the JSON:API `filter`
        parameter to narrow by delivery `status` and the `sort`, `include`, and sparse `fields`
        parameters to shape the response. Requires the `emails:read` scope.
      operationId: listOutboundEmails
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Outbound emails retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/OutboundEmailCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /outbound-emails/{outboundEmail}:
    get:
      tags:
        - OutboundEmails
      summary: Show an outbound email
      description: |
        Returns a single outbound email owned by the authenticated user, including its current
        delivery `status` and lifecycle timestamps. Poll this to follow a send after it is queued.
        Requires the `emails:read` scope.
      operationId: showOutboundEmail
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: outboundEmail
          in: path
          required: true
          description: The outbound email identifier.
          schema:
            $ref: '#/components/schemas/OutboundEmailId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Outbound email retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/OutboundEmailDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    delete:
      tags:
        - OutboundEmails
      summary: Delete an outbound email
      description: |
        Deletes the record of an outbound email owned by the authenticated user. This removes the
        stored message and its metadata; it does **not** recall a message already handed to the
        recipient's mail server. Requires the `emails:write` scope.
      operationId: deleteOutboundEmail
      parameters:
        - name: outboundEmail
          in: path
          required: true
          description: The outbound email identifier.
          schema:
            $ref: '#/components/schemas/OutboundEmailId'
      responses:
        '204':
          description: Outbound email deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /outbound-emails/{outboundEmail}/stats:
    get:
      tags:
        - OutboundEmails
      summary: Show outbound email stats
      description: |
        Returns aggregate delivery and engagement statistics for a single outbound email — counts
        such as deliveries, opens, clicks, bounces, and complaints derived from its tracked events.
        Open and click figures are only populated when tracking was enabled for the send. Requires
        the `emails:read` scope.
      operationId: showOutboundEmailStats
      parameters:
        - name: outboundEmail
          in: path
          required: true
          description: The outbound email identifier.
          schema:
            $ref: '#/components/schemas/OutboundEmailId'
      responses:
        '200':
          description: Outbound email statistics retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/OutboundEmailStatsDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /suppressions:
    get:
      tags:
        - Suppressions
      summary: List suppressions
      description: |
        Returns the suppression entries across the authenticated user's sending domains, newest
        first. Each entry records the suppressed address, the `reason` (`unsubscribe`, `hard_bounce`,
        `complaint`, `manual`, `list_import`, or `soft_bounce`), its source, its `scope`, and an
        optional `expires_at` (null means permanent). The `scope` states what the entry blocks:
        `marketing` entries (unsubscribes) block campaign sends only, while `all` entries (bounces,
        complaints, manual additions) block transactional sends too. Supports the JSON:API `filter`
        (including `filter[scope]`), `sort`, `include`, and sparse `fields` parameters. Requires the
        `suppressions:read` scope.
      operationId: listSuppressions
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Suppressions retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/SuppressionCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /suppressions/{suppression}:
    delete:
      tags:
        - Suppressions
      summary: Delete a suppression
      description: |
        Removes a suppression entry, re-enabling sending to that address on the domain. Removal is
        only permitted for reversible reasons; `hard_bounce` and `complaint` suppressions are
        permanent (CAN-SPAM and sender-reputation protection) and cannot be deleted — attempting to do
        so returns `422`. Requires the `suppressions:write` scope.
      operationId: deleteSuppression
      parameters:
        - name: suppression
          in: path
          required: true
          description: The suppression identifier.
          schema:
            $ref: '#/components/schemas/SuppressionId'
      responses:
        '204':
          description: Suppression deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '422':
          $ref: '#/components/responses/ValidationError'
          description: The request document failed validation, including when the suppression reason is permanent and cannot be removed.
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /webhooks:
    get:
      tags:
        - Webhooks
      summary: List webhooks
      description: |
        Returns the webhook endpoints registered across the authenticated user's sending domains,
        newest first. Each webhook lists its target `url`, subscribed `events`, and `active` state.
        The signing secret is never returned here — it is shown only once, at creation. Requires the
        `webhooks:read` scope.
      operationId: listWebhooks
      parameters:
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Webhooks retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/WebhookCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    post:
      tags:
        - Webhooks
      summary: Create a webhook
      description: |
        Registers a webhook endpoint on one of your sending domains and subscribes it to a set of
        event types (for example `email.delivered`, `email.bounced`, `email.opened`,
        `inbound_email.received`, `campaign.sent`, `domain.verified`). MiniMailer POSTs each matching
        event to your `url`.

        The response includes a `whsec_`-prefixed signing secret **once** — store it now. Every
        delivery is signed with it following the [Standard Webhooks](https://www.standardwebhooks.com)
        spec, so your endpoint can verify authenticity. Deliveries are retried with backoff, and an
        endpoint that keeps failing is deactivated.

        Requires the `webhooks:write` scope.
      operationId: createWebhook
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequestDocument'
      responses:
        '201':
          description: Webhook created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/WebhookDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
          description: The referenced sending domain does not exist.
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /webhooks/{webhook}:
    get:
      tags:
        - Webhooks
      summary: Show a webhook
      description: |
        Returns a single webhook owned by the authenticated user, including its target `url`,
        subscribed `events`, and `active` state. The signing secret is not included. Requires the
        `webhooks:read` scope.
      operationId: showWebhook
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: webhook
          in: path
          required: true
          description: The webhook identifier.
          schema:
            $ref: '#/components/schemas/WebhookId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Webhook retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/WebhookDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    patch:
      tags:
        - Webhooks
      summary: Update a webhook
      description: |
        Updates a webhook owned by the authenticated user — its `name`, target `url`, the set of
        subscribed `events`, or its `active` flag (set `active: false` to pause deliveries without
        deleting the endpoint). Set `rotate_secret: true` to rotate the signing secret: a new
        secret is generated server-side and returned once in the response, while the previous
        secret keeps verifying deliveries for a 24-hour grace window. The secret value itself
        cannot be chosen by the client. Requires the `webhooks:write` scope.
      operationId: updateWebhook
      parameters:
        - name: webhook
          in: path
          required: true
          description: The webhook identifier.
          schema:
            $ref: '#/components/schemas/WebhookId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookRequestDocument'
      responses:
        '200':
          description: Webhook updated.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/WebhookDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    delete:
      tags:
        - Webhooks
      summary: Delete a webhook
      description: |
        Permanently deletes a webhook owned by the authenticated user. No further events are
        delivered to its endpoint and its signing secret is invalidated. Past delivery records are
        retained. To pause deliveries temporarily, set `active: false` instead. Requires the
        `webhooks:write` scope.
      operationId: deleteWebhook
      parameters:
        - name: webhook
          in: path
          required: true
          description: The webhook identifier.
          schema:
            $ref: '#/components/schemas/WebhookId'
      responses:
        '204':
          description: Webhook deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /webhooks/{webhook}/deliveries:
    get:
      tags:
        - Webhooks
      summary: List webhook deliveries
      description: |
        Returns the delivery attempts made for a webhook owned by the authenticated user, newest
        first — each with the event delivered, the response status, and the outcome. Use it to audit
        which events reached your endpoint and to find failed deliveries to replay. Requires the
        `webhooks:read` scope.
      operationId: listWebhookDeliveries
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
        - name: webhook
          in: path
          required: true
          description: The webhook identifier.
          schema:
            $ref: '#/components/schemas/WebhookId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Webhook deliveries retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/OutboundWebhookCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /deliveries/{outboundWebhook}:
    get:
      tags:
        - Webhooks
      summary: Show a webhook delivery
      description: |
        Returns a single webhook delivery attempt owned by the authenticated user, including the
        signed payload that was sent and the endpoint's response. Requires the `webhooks:read` scope.
      operationId: showWebhookDelivery
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: outboundWebhook
          in: path
          required: true
          description: The webhook delivery identifier.
          schema:
            $ref: '#/components/schemas/WebhookDeliveryId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Webhook delivery retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/OutboundWebhookDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /deliveries/{outboundWebhook}/replay:
    post:
      tags:
        - Webhooks
      summary: Replay a webhook delivery
      description: |
        Re-sends a past webhook delivery to its endpoint, reusing the original event payload with a
        fresh signature and timestamp. Use it to recover events your endpoint missed during downtime.
        This creates a new delivery attempt rather than mutating the original. Requires the
        `webhooks:write` scope.
      operationId: replayWebhookDelivery
      parameters:
        - name: outboundWebhook
          in: path
          required: true
          description: The webhook delivery identifier.
          schema:
            $ref: '#/components/schemas/WebhookDeliveryId'
      responses:
        '202':
          description: Webhook delivery replay accepted.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/OutboundWebhookDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /inboxes:
    post:
      tags:
        - Inboxes
      summary: Create an inbox
      description: |
        Creates a managed inbound mailbox at an address on one of your sending domains, so mail sent
        to it is received, parsed, and exposed through the API (and as `inbound_email.received`
        webhooks).

        The first inbox on a domain provisions the inbound MX record and routing
        automatically; subsequent inboxes reuse it. If the address is ambiguous — the same domain
        name exists in more than one region under your account — the request is rejected with `422`
        and you should register a region-distinct subdomain.

        Inboxes are governed by the domain scopes: requires the `domains:write` scope.
      operationId: createInbox
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateInboxRequestDocument'
      responses:
        '201':
          description: Inbox created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/InboxDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    get:
      tags:
        - Inboxes
      summary: List inboxes
      description: |
        Returns the authenticated user's inbound mailboxes, newest first, with the JSON:API
        `sort`, `include`, and sparse `fields` parameters. Requires the `domains:read`
        scope.
      operationId: listInboxes
      parameters:
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Inboxes retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/InboxCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /inboxes/{inbox}:
    get:
      tags:
        - Inboxes
      summary: Show an inbox
      description: |
        Returns a single inbound mailbox owned by the authenticated user, including its address and
        configuration. Requires the `domains:read` scope.
      operationId: showInbox
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: inbox
          in: path
          required: true
          description: The inbox identifier.
          schema:
            $ref: '#/components/schemas/InboxId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Inbox retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/InboxDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    patch:
      tags:
        - Inboxes
      summary: Update an inbox
      description: |
        Updates the mutable settings of an inbound mailbox owned by the authenticated user. The
        inbox address is immutable — create a new inbox to receive at a different address. Requires
        the `domains:write` scope.
      operationId: updateInbox
      parameters:
        - name: inbox
          in: path
          required: true
          description: The inbox identifier.
          schema:
            $ref: '#/components/schemas/InboxId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/UpdateInboxRequestDocument'
      responses:
        '200':
          description: Inbox updated.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/InboxDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    delete:
      tags:
        - Inboxes
      summary: Delete an inbox
      description: |
        Deletes an inbound mailbox owned by the authenticated user and stops receiving mail at its
        address. Inbound emails already received and stored remain retrievable. Requires the
        `domains:write` scope.
      operationId: deleteInbox
      parameters:
        - name: inbox
          in: path
          required: true
          description: The inbox identifier.
          schema:
            $ref: '#/components/schemas/InboxId'
      responses:
        '204':
          description: Inbox deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /inbound-emails:
    get:
      tags:
        - InboundEmails
      summary: List inbound emails
      description: |
        Returns inbound emails received across all inboxes owned by the authenticated user, newest
        first, with their parsed status (`received`, `virus_detected`, `oversized`, or
        `parse_rejected`). Pass the `inbox` query parameter to scope the list to a single mailbox, or
        use the JSON:API `filter`, `sort`, `include`, and sparse `fields` parameters. Requires the
        `inbound:read` scope.
      operationId: listAllInboundEmails
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
        - name: inbox
          in: query
          required: false
          description: Filter inbound emails to a single inbox by its identifier.
          schema:
            $ref: '#/components/schemas/InboxId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Inbound emails retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/InboundEmailCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /inboxes/{inbox}/inbound-emails:
    get:
      tags:
        - InboundEmails
      summary: List inbound emails
      description: |
        Returns the inbound emails received by one specific inbox owned by the authenticated user,
        newest first — the relationship-scoped equivalent of `GET /inbound-emails?inbox=...`.
        Requires the `inbound:read` scope.
      operationId: listInboundEmails
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
        - name: inbox
          in: path
          required: true
          description: The inbox identifier.
          schema:
            $ref: '#/components/schemas/InboxId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Inbound emails retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/InboundEmailCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /inbound-emails/{inboundEmail}:
    get:
      tags:
        - InboundEmails
      summary: Show an inbound email
      description: |
        Returns a single inbound email owned by the authenticated user, including its parsed
        headers, sender and recipients, text and HTML bodies, and attachment metadata. Requires the
        `inbound:read` scope.
      operationId: showInboundEmail
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: inboundEmail
          in: path
          required: true
          description: The inbound email identifier.
          schema:
            $ref: '#/components/schemas/InboundEmailId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Inbound email retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/InboundEmailDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /recipients/{recipient}/forget:
    delete:
      tags:
        - Recipients
      summary: Submit a right-to-forget request
      description: |
        Submits a GDPR right-to-erasure ("right to be forgotten") request for a recipient email
        address across the authenticated user's sending domains. MiniMailer records the request in a
        durable ledger and dispatches an asynchronous scrub that removes the recipient's personal
        data from every store while preserving a non-reversible hash for audit and re-erasure on
        backup restore.

        The operation is idempotent: repeating it for an already-scrubbed recipient is a harmless
        no-op. Because erasure runs asynchronously, a `202`-style acceptance means the request is
        recorded, not that scrubbing has finished. Requires the `compliance:write` scope.
      operationId: forgetRecipient
      parameters:
        - name: recipient
          in: path
          required: true
          description: The recipient email address.
          schema:
            type: string
      responses:
        '202':
          description: Right-to-forget request accepted and processing.
          content:
            application/vnd.api+json:
              schema:
                type: object
                required:
                  - meta
                  - jsonapi
                  - links
                properties:
                  links:
                    $ref: '#/components/schemas/DocumentLinks'
                  meta:
                    type: object
                    required:
                      - status
                      - job_reference
                    properties:
                      status:
                        type: string
                        const: accepted
                      job_reference:
                        type: string
                        description: |
                          The non-reversible recipient hash correlating this request with the
                          completed erasure.
                  jsonapi:
                    $ref: '#/components/schemas/JsonApiObject'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /recipients/{recipient}/data:
    get:
      tags:
        - Recipients
      summary: Request a recipient data export
      description: |
        Submits a GDPR Article 15 subject-access export for a recipient email address across the
        authenticated user's sending domains. The export is assembled asynchronously and delivered
        out of band; the acceptance response echoes the non-reversible recipient hash used to
        correlate the request with the completed export.

        Requires the `compliance:write` scope.
      operationId: exportRecipientData
      parameters:
        - name: recipient
          in: path
          required: true
          description: The recipient email address.
          schema:
            type: string
      responses:
        '202':
          description: Recipient data export request accepted and processing.
          content:
            application/vnd.api+json:
              schema:
                type: object
                required:
                  - meta
                  - jsonapi
                  - links
                properties:
                  links:
                    $ref: '#/components/schemas/DocumentLinks'
                  meta:
                    type: object
                    required:
                      - status
                      - recipient_email_hash
                    properties:
                      status:
                        type: string
                        const: accepted
                      recipient_email_hash:
                        type: string
                        description: |
                          The non-reversible recipient hash correlating this request with the
                          completed export.
                  jsonapi:
                    $ref: '#/components/schemas/JsonApiObject'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /recipient-consents:
    post:
      tags:
        - Recipients
      summary: Record a recipient consent
      description: |
        Appends a consent record for a recipient on one of your sending domains, giving you the
        demonstrable proof of consent GDPR Article 7(1) requires. Capture the `consent_type`
        (`opt_in`, `double_opt_in`, `list_import`, or `manual`), the `consent_source`, the exact
        `consent_text` shown, and optionally the recipient's IP and user agent.

        The ledger is append-only: nothing is ever deleted, so the consent history stays intact —
        a later consent for the same recipient is a new record, and a withdrawal stamps the
        withdrawal timestamp onto the latest matching record (see the withdraw operation). The
        stored hash survives a right-to-forget erasure to preserve the audit trail. Requires the
        `compliance:write` scope.
      operationId: createRecipientConsent
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateRecipientConsentRequestDocument'
      responses:
        '201':
          description: Recipient consent recorded.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/RecipientConsentDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /recipient-consents/withdraw:
    post:
      tags:
        - Recipients
      summary: Withdraw a recipient consent
      description: |
        Records a GDPR Article 7(3) consent withdrawal: stamps the withdrawal timestamp and
        channel onto the latest consent record matching the domain, recipient email, and
        `consent_type`. The ledger stays append-only — the consent record is never deleted, and
        earlier records for the same recipient are left untouched.

        The recipient is matched by the stored email hash, so a withdrawal can still be recorded
        after a right-to-forget erasure has scrubbed the cleartext address. Responds 404 when no
        matching consent exists or when the latest one is already withdrawn. `withdrawn_via`
        defaults to `api` when omitted.

        A recipient's own unsubscribe records a withdrawal automatically (with the unsubscribe as
        the channel), so this endpoint is for withdrawals that reach you out-of-band — a reply, a
        phone call, a regulator request. Requires the `compliance:write` scope.
      operationId: withdrawRecipientConsent
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/WithdrawRecipientConsentRequestDocument'
      responses:
        '200':
          description: Consent withdrawal recorded on the latest matching record.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/RecipientConsentDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /controller-audit:
    get:
      tags:
        - Compliance
      summary: Request a controller audit export
      description: |
        Submits a GDPR Article 28(3)(h) audit export of the processing footprint MiniMailer holds
        for the authenticated user's account: sending domains, message metadata, delivery feedback,
        and webhook delivery trails. The export is assembled asynchronously and delivered by email;
        the acceptance response confirms the request is recorded.

        Requires the `compliance:write` scope.
      operationId: exportControllerAudit
      responses:
        '202':
          description: Controller audit export request accepted and processing.
          content:
            application/vnd.api+json:
              schema:
                type: object
                required:
                  - meta
                  - jsonapi
                  - links
                properties:
                  links:
                    $ref: '#/components/schemas/DocumentLinks'
                  meta:
                    type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: accepted
                  jsonapi:
                    $ref: '#/components/schemas/JsonApiObject'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /audiences:
    post:
      tags:
        - Audiences
      summary: Create an audience
      description: |
        Creates an audience — a named list that groups contacts for marketing campaigns — for the
        authenticated user. Add contacts to it individually via
        `POST /audiences/{audience}/contacts` or in bulk via the import endpoint, then
        target it from a campaign. Requires the `contacts:write` scope.
      operationId: createAudience
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateAudienceRequestDocument'
      responses:
        '201':
          description: Audience created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/AudienceDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
          description: The referenced domain does not exist.
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    get:
      tags:
        - Audiences
      summary: List audiences
      description: |
        Returns the authenticated user's audiences, newest first, with the JSON:API
        `sort`, `include`, and sparse `fields` parameters. Requires the `contacts:read` scope.
      operationId: listAudiences
      parameters:
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Audiences retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/AudienceCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /audiences/{audience}:
    get:
      tags:
        - Audiences
      summary: Show an audience
      description: |
        Returns a single audience owned by the authenticated user. Use the `include` parameter to
        embed related contacts in the response. Requires the `contacts:read` scope.
      operationId: showAudience
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: audience
          in: path
          required: true
          description: The audience identifier.
          schema:
            $ref: '#/components/schemas/AudienceId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Audience retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/AudienceDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    patch:
      tags:
        - Audiences
      summary: Update an audience
      description: |
        Updates an audience owned by the authenticated user, such as its name. Its contacts are
        managed through the audience's contacts relationship, not this endpoint. Requires the
        `contacts:write` scope.
      operationId: updateAudience
      parameters:
        - name: audience
          in: path
          required: true
          description: The audience identifier.
          schema:
            $ref: '#/components/schemas/AudienceId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/UpdateAudienceRequestDocument'
      responses:
        '200':
          description: Audience updated.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/AudienceDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    delete:
      tags:
        - Audiences
      summary: Delete an audience
      description: |
        Deletes an audience owned by the authenticated user along with its membership. Campaigns
        that targeted the audience are retained, including their historical statistics. Their
        audience relationship is returned with `data: null` when requested via `include=audience`.
        Deletion is refused with `409` while a campaign targeting the audience is scheduled,
        sending, or paused — cancel that campaign first. Requires the `contacts:write` scope.
      operationId: deleteAudience
      parameters:
        - name: audience
          in: path
          required: true
          description: The audience identifier.
          schema:
            $ref: '#/components/schemas/AudienceId'
      responses:
        '204':
          description: Audience deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '409':
          $ref: '#/components/responses/ConflictError'
          description: A campaign targeting this audience is scheduled, sending, or paused; cancel it before deleting the audience.
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /audiences/{audience}/contacts:
    get:
      tags:
        - Contacts
      summary: List contacts
      description: |
        Returns the contacts belonging to an audience owned by the authenticated user, newest first.
        Supports the JSON:API `filter`, `sort`, `include`, and sparse `fields` parameters — for
        example filter by subscription status. Requires the `contacts:read` scope.
      operationId: listContacts
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
        - name: audience
          in: path
          required: true
          description: The audience identifier.
          schema:
            $ref: '#/components/schemas/AudienceId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Contacts retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ContactCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    post:
      tags:
        - Contacts
      summary: Create a contact
      description: |
        Adds a single contact to an audience owned by the authenticated user, with an email plus
        optional name, preferences, and custom fields. Email is normalised (lower-cased and trimmed)
        and unique within the audience; adding an address already present returns `422`. For adding
        many contacts at once use the import endpoint. Requires the `contacts:write` scope.
      operationId: createContact
      parameters:
        - name: audience
          in: path
          required: true
          description: The audience identifier.
          schema:
            $ref: '#/components/schemas/AudienceId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateContactRequestDocument'
      responses:
        '201':
          description: Contact created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ContactDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /audiences/{audience}/contacts/import:
    post:
      tags:
        - Contacts
      summary: Import contacts
      description: |
        Bulk-adds contacts to an audience owned by the authenticated user in a single request.
        Emails are normalised and de-duplicated both against contacts already in the audience and
        within the submitted batch; duplicates are **skipped, not rejected**, so a partially
        overlapping import still succeeds. The response reports how many contacts were `created`
        versus `skipped`. Requires the `contacts:write` scope.
      operationId: importContacts
      parameters:
        - name: audience
          in: path
          required: true
          description: The audience identifier.
          schema:
            $ref: '#/components/schemas/AudienceId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/ImportContactsRequestDocument'
      responses:
        '200':
          description: Contacts import processed.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ContactImportDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /contacts/{contact}:
    get:
      tags:
        - Contacts
      summary: Show a contact
      description: |
        Returns a single contact belonging to an audience owned by the authenticated user, including
        its email, name, subscription status, preferences, and custom fields. Requires the
        `contacts:read` scope.
      operationId: showContact
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: contact
          in: path
          required: true
          description: The contact identifier.
          schema:
            $ref: '#/components/schemas/ContactId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Contact retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ContactDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    patch:
      tags:
        - Contacts
      summary: Update a contact
      description: |
        Updates a contact belonging to an audience owned by the authenticated user — its name,
        subscription status, preferences, or custom fields. Requires the `contacts:write` scope.
      operationId: updateContact
      parameters:
        - name: contact
          in: path
          required: true
          description: The contact identifier.
          schema:
            $ref: '#/components/schemas/ContactId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/UpdateContactRequestDocument'
      responses:
        '200':
          description: Contact updated.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ContactDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    delete:
      tags:
        - Contacts
      summary: Delete a contact
      description: |
        Removes a contact from an audience owned by the authenticated user. This deletes the
        contact's membership of the audience; it does not add the address to any suppression list.
        Requires the `contacts:write` scope.
      operationId: deleteContact
      parameters:
        - name: contact
          in: path
          required: true
          description: The contact identifier.
          schema:
            $ref: '#/components/schemas/ContactId'
      responses:
        '204':
          description: Contact deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /campaigns:
    post:
      tags:
        - Campaigns
      summary: Create a campaign
      description: |
        Creates a marketing campaign in the `draft` state for the authenticated user. Set its
        content — subject, HTML/text body, from address, target audience, schedule, and tracking
        options — now or via subsequent draft updates. The campaign is not sent on creation; drive it
        through its lifecycle with `PATCH /campaigns/{campaign}` (`draft` → `scheduled`). Requires the
        `campaigns:write` scope.
      operationId: createCampaign
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/CreateCampaignRequestDocument'
      responses:
        '201':
          description: Campaign created.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/CampaignDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
          description: The referenced audience does not exist.
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    get:
      tags:
        - Campaigns
      summary: List campaigns
      description: |
        Returns the authenticated user's campaigns, newest first, each with its current lifecycle
        `status` (`draft`, `scheduled`, `sending`, `paused`, `sent`, `cancelled`, or `failed`).
        Supports the JSON:API `filter`, `sort`, `include`, and sparse `fields` parameters. Requires
        the `campaigns:read` scope.
      operationId: listCampaigns
      parameters:
        - $ref: '#/components/parameters/FilterParam'
        - $ref: '#/components/parameters/SortParam'
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - $ref: '#/components/parameters/PageParam'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Campaigns retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/CampaignCollectionDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /campaigns/{campaign}:
    get:
      tags:
        - Campaigns
      summary: Show a campaign
      description: |
        Returns a single campaign owned by the authenticated user, including its content, target
        audience, schedule, and current lifecycle `status`. Requires the `campaigns:read` scope.
      operationId: showCampaign
      parameters:
        - $ref: '#/components/parameters/IncludeParam'
        - $ref: '#/components/parameters/SparseFieldsetsParam'
        - name: campaign
          in: path
          required: true
          description: The campaign identifier.
          schema:
            $ref: '#/components/schemas/CampaignId'
      responses:
        '400':
          $ref: '#/components/responses/BadRequestError'
        '200':
          description: Campaign retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/CampaignDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    patch:
      tags:
        - Campaigns
      summary: Update a campaign
      description: |
        Updates a campaign owned by the authenticated user. This one endpoint does two jobs: editing
        content and driving the lifecycle state machine.

        **Content edits** (subject, body, from address, schedule, tracking) are allowed **only while
        the campaign is a `draft`**; editing content on any other status returns `422`.

        **Status transitions** follow the allowed edges; any other transition returns `422`:

        - `draft` → `scheduled` (queue it to send at its `scheduled_at`) or `cancelled`
        - `scheduled` → `cancelled`
        - `sending` → `paused`
        - `paused` → `sending` (resume — re-queues the not-yet-sent recipients) or `cancelled`

        Internal edges (`scheduled` → `sending` when the schedule fires, `sending` → `sent`/`failed`)
        happen automatically and are never triggered through this endpoint. Requires the
        `campaigns:write` scope.
      operationId: updateCampaign
      parameters:
        - name: campaign
          in: path
          required: true
          description: The campaign identifier.
          schema:
            $ref: '#/components/schemas/CampaignId'
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/UpdateCampaignRequestDocument'
      responses:
        '200':
          description: Campaign updated.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/CampaignDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '415':
          $ref: '#/components/responses/UnsupportedMediaTypeError'
        '422':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
    delete:
      tags:
        - Campaigns
      summary: Delete a campaign
      description: |
        Deletes a campaign owned by the authenticated user. To stop an in-flight send, transition it
        to `cancelled` via `PATCH` rather than deleting it. Statistics for a campaign that has already
        sent are retained on its send records. Requires the `campaigns:write` scope.
      operationId: deleteCampaign
      parameters:
        - name: campaign
          in: path
          required: true
          description: The campaign identifier.
          schema:
            $ref: '#/components/schemas/CampaignId'
      responses:
        '204':
          description: Campaign deleted.
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /campaigns/{campaign}/stats:
    get:
      tags:
        - Campaigns
      summary: Show campaign stats
      description: |
        Returns aggregate delivery and engagement statistics for a campaign — totals such as
        recipients, deliveries, opens, clicks, bounces, complaints, and unsubscribes derived from its
        send events. Open and click figures reflect only the tracking that was enabled for the
        campaign. Figures are most meaningful once the campaign has reached `sending` or `sent`.
        Requires the `campaigns:read` scope.
      operationId: showCampaignStats
      parameters:
        - name: campaign
          in: path
          required: true
          description: The campaign identifier.
          schema:
            $ref: '#/components/schemas/CampaignId'
      responses:
        '200':
          description: Campaign statistics retrieved.
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/CampaignStatsDocument'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '406':
          $ref: '#/components/responses/NotAcceptableError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
  /oauth/register:
    servers:
      - url: https://minimailer.app
        description: Production apex host — the OAuth endpoints are not on the api. subdomain.
    post:
      tags:
        - OAuth
      summary: Register an OAuth client (Dynamic Client Registration)
      description: |
        RFC 7591 Dynamic Client Registration for MCP OAuth clients. Public and unauthenticated, but
        rate-limited: a redirect URI must be on an allowed first-party origin, and the registered
        client is public (no secret, `token_endpoint_auth_method: none`) and constrained to the
        `mcp:use` scope. It is redeemed with PKCE at the token endpoint.
      operationId: registerOAuthClient
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - redirect_uris
              properties:
                client_name:
                  type: string
                  maxLength: 255
                  description: Human-readable client name shown on the consent screen.
                redirect_uris:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    format: uri
                  description: Redirect URIs; each must be on an allowed first-party origin.
      responses:
        '201':
          description: Client registered.
          content:
            application/json:
              schema:
                type: object
                required:
                  - client_id
                  - grant_types
                  - response_types
                  - redirect_uris
                  - scope
                  - token_endpoint_auth_method
                properties:
                  client_id:
                    type: string
                  grant_types:
                    type: array
                    items:
                      type: string
                  response_types:
                    type: array
                    items:
                      type: string
                  redirect_uris:
                    type: array
                    items:
                      type: string
                  scope:
                    type: string
                  token_endpoint_auth_method:
                    type: string
                    enum:
                      - none
        '400':
          description: Invalid client metadata or a redirect URI on a disallowed origin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
  /oauth/token:
    servers:
      - url: https://minimailer.app
        description: Production apex host — the OAuth endpoints are not on the api. subdomain.
    post:
      tags:
        - OAuth
      summary: Exchange an authorization grant for an access token
      description: |
        OAuth 2.1 token endpoint. Exchanges an authorization code (with PKCE) or a refresh token for
        a bearer access token. Public — a dynamically registered client is authenticated by its PKCE
        `code_verifier`, not a secret.
      operationId: issueOAuthToken
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/OAuthTokenRequest'
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthTokenRequest'
      responses:
        '200':
          description: Access token issued.
          content:
            application/json:
              schema:
                type: object
                required:
                  - token_type
                  - expires_in
                  - access_token
                properties:
                  token_type:
                    type: string
                  expires_in:
                    type: integer
                  access_token:
                    type: string
                  refresh_token:
                    type: string
        '400':
          description: Invalid or unsupported grant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: Client authentication failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
  /.well-known/oauth-authorization-server:
    servers:
      - url: https://minimailer.app
        description: Production apex host — the OAuth endpoints are not on the api. subdomain.
    get:
      tags:
        - OAuth
      summary: OAuth 2.0 Authorization Server Metadata (RFC 8414)
      description: Public discovery document describing the OAuth authorization server.
      operationId: oauthAuthorizationServerMetadata
      security: []
      responses:
        '200':
          description: Authorization server metadata.
          content:
            application/json:
              schema:
                type: object
                required:
                  - issuer
                  - authorization_endpoint
                  - token_endpoint
                properties:
                  issuer:
                    type: string
                    format: uri
                  authorization_endpoint:
                    type: string
                    format: uri
                  token_endpoint:
                    type: string
                    format: uri
                  registration_endpoint:
                    type: string
                    format: uri
                  response_types_supported:
                    type: array
                    items:
                      type: string
                  code_challenge_methods_supported:
                    type: array
                    items:
                      type: string
                  scopes_supported:
                    type: array
                    items:
                      type: string
                  grant_types_supported:
                    type: array
                    items:
                      type: string
  /.well-known/oauth-protected-resource:
    servers:
      - url: https://minimailer.app
        description: Production apex host — the OAuth endpoints are not on the api. subdomain.
    get:
      tags:
        - OAuth
      summary: OAuth 2.0 Protected Resource Metadata (RFC 9728)
      description: Public discovery document describing this protected resource and its authorization servers.
      operationId: oauthProtectedResourceMetadata
      security: []
      responses:
        '200':
          description: Protected resource metadata.
          content:
            application/json:
              schema:
                type: object
                required:
                  - resource
                  - authorization_servers
                properties:
                  resource:
                    type: string
                    format: uri
                  authorization_servers:
                    type: array
                    items:
                      type: string
                      format: uri
                  scopes_supported:
                    type: array
                    items:
                      type: string
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    oauth2:
      type: oauth2
      description: |
        OAuth 2.1 authorization-code flow with PKCE for MCP clients. Issued tokens carry the
        `mcp:use` scope and are presented as bearer tokens. See the /oauth endpoints and the
        RFC 8414 / RFC 9728 discovery documents.
      flows:
        authorizationCode:
          authorizationUrl: https://minimailer.app/oauth/authorize
          tokenUrl: https://minimailer.app/oauth/token
          scopes:
            mcp:use: MCP server access for AI agents
  responses:
    BadRequestError:
      description: "The include or fields query parameter is not supported (unknown relationship, over-deep include, or unknown sparse-fieldset type or field)."
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
    UnauthorizedError:
      description: Authentication is required.
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
    ForbiddenError:
      description: The authenticated token does not have permission to perform this action.
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
    NotFoundError:
      description: The requested resource could not be found.
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
    ConflictError:
      description: "The request conflicts with the endpoint or current state: the document's resource type does not match the endpoint, its id does not match the resource being updated, or an idempotency key was reused with a different payload. (Creating with a client-generated id and updating relationships on an endpoint that does not support them return 403 instead.)"
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
    NotAcceptableError:
      description: "The Accept header mentions the JSON:API media type only in instances modified by disallowed media type parameters (or only with unsupported ext extensions), so no acceptable representation remains."
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
    UnsupportedMediaTypeError:
      description: "The request Content-Type must be application/vnd.api+json, with no media type parameters other than profile (ext is rejected — no JSON:API extensions are supported — as is any other parameter such as charset)."
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
    ValidationError:
      description: The request document failed validation.
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ValidationErrorDocument'
    TooManyRequestsError:
      description: The caller exceeded the API rate limit.
      content:
        application/vnd.api+json:
          schema:
            $ref: '#/components/schemas/ErrorDocument'
  parameters:
    IncludeParam:
      name: include
      in: query
      required: false
      description: "Comma-separated related resources to embed in the response included member (JSON:API compound documents). Example: include=records,webhooks. Nested paths use dot notation, max depth 2. Relationships not declared by the resource, or over-deep paths, return 400."
      schema:
        type: string
    SparseFieldsetsParam:
      name: fields
      in: query
      required: false
      style: deepObject
      explode: true
      description: "Per-type sparse fieldsets. Example: fields[domains]=name,status restricts the returned attributes for that type. The type must be present in the document and each field must be a known attribute, otherwise 400 is returned."
      schema:
        type: object
        additionalProperties:
          type: string
    FilterParam:
      name: filter
      in: query
      required: false
      style: deepObject
      explode: true
      description: "Exact-match filters, e.g. filter[status]=delivered. Only the fields this endpoint allow-lists are accepted; an unsupported filter returns 400."
      schema:
        type: object
        additionalProperties:
          type: string
    SortParam:
      name: sort
      in: query
      required: false
      description: "Comma-separated sort fields; prefix a field with - for descending, e.g. sort=-created_at,name. Defaults to newest-first. Only allow-listed fields are accepted; an unsupported sort returns 400."
      schema:
        type: string
    PageParam:
      name: page
      in: query
      required: false
      style: deepObject
      explode: true
      description: "Cursor-based pagination, e.g. page[cursor]=eyJpZCI6...&page[size]=50. The cursor is an opaque token issued by the API — take it from the response's links.next / links.prev URLs or meta.next_cursor / meta.prev_cursor; never construct one. size defaults to 25 and is silently capped at 100 (out-of-range or non-numeric values fall back to the default). An invalid or expired cursor falls back to the first page; a cursor replayed with a different sort than it was issued under returns 400; unknown page members such as page[number] return 400. Follow links.next until it is null (or meta.has_more is false) to traverse a collection; links.first restarts traversal. Link URLs carry these parameters URL-encoded (page%5Bcursor%5D=...)."
      schema:
        type: object
        additionalProperties: false
        properties:
          cursor:
            type: string
          size:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
  schemas:
    OAuthError:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: RFC 6749 error code (e.g. invalid_redirect_uri, unsupported_grant_type).
        error_description:
          type: string
        hint:
          type: string
    OAuthTokenRequest:
      type: object
      required:
        - grant_type
      properties:
        grant_type:
          type: string
          enum:
            - authorization_code
            - refresh_token
        code:
          type: string
        redirect_uri:
          type: string
          format: uri
        code_verifier:
          type: string
        client_id:
          type: string
        refresh_token:
          type: string
    CreateTokenRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
          properties:
            type:
              type: string
              const: tokens
            attributes:
              type: object
              required:
                - name
                - scopes
                - duration
              properties:
                name:
                  type: string
                  maxLength: 255
                scopes:
                  type: array
                  items:
                    type: string
                    enum:
                      - emails:send
                      - emails:read
                      - emails:write
                      - campaigns:write
                      - campaigns:read
                      - contacts:write
                      - contacts:read
                      - inbound:read
                      - domains:write
                      - domains:read
                      - webhooks:write
                      - webhooks:read
                      - suppressions:write
                      - suppressions:read
                      - compliance:write
                      - tokens:write
                      - mcp:use
                duration:
                  type: string
                  description: >-
                    The token's lifetime. The token stops authenticating once
                    this period elapses — the chosen duration is enforced, not
                    advisory.
                  enum:
                    - 1_month
                    - 6_months
                    - 12_months
    TokenDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/TokenResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    TokenResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: tokens
        id:
          type: string
        attributes:
          type: object
          required:
            - name
            - scopes
            - expires_at
            - created_at
            - token
          properties:
            name:
              type: string
            scopes:
              type: array
              items:
                type: string
            expires_at:
              type:
                - string
                - 'null'
              format: date-time
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            token:
              type: string
    DomainId:
      type: string
      pattern: '^dom_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for a domain.
      example: dom_01JZX8K7Q2M3N4P5R6S7T8V9W0
    DomainRelationship:
      type: object
      description: JSON:API to-one relationship linkage identifying a sending domain.
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
          properties:
            type:
              type: string
              const: domains
            id:
              $ref: '#/components/schemas/DomainId'
    AudienceRelationship:
      type: object
      description: JSON:API to-one relationship linkage identifying an audience.
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
          properties:
            type:
              type: string
              const: audiences
            id:
              $ref: '#/components/schemas/AudienceId'
    NullableAudienceRelationship:
      type: object
      description: JSON:API to-one audience linkage, or null when the audience has been deleted.
      required:
        - data
      properties:
        data:
          type:
            - object
            - 'null'
          required:
            - type
            - id
          properties:
            type:
              type: string
              const: audiences
            id:
              $ref: '#/components/schemas/AudienceId'
    CreateRecipientConsentRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
            - relationships
          properties:
            type:
              type: string
              const: recipient-consents
            attributes:
              type: object
              required:
                - recipient_email
                - consent_type
                - consent_source
                - consent_text
              properties:
                recipient_email:
                  type: string
                  format: email
                consent_type:
                  type: string
                  enum:
                    - opt_in
                    - double_opt_in
                    - list_import
                    - manual
                consent_source:
                  type: string
                  enum:
                    - signup_form
                    - csv_import
                    - api
                    - dashboard
                    - mcp_tool
                consent_text:
                  type: string
                  maxLength: 2000
                ip_address:
                  type:
                    - string
                    - 'null'
                  format: ip
                user_agent:
                  type:
                    - string
                    - 'null'
                  maxLength: 512
            relationships:
              type: object
              required:
                - domain
              properties:
                domain:
                  $ref: '#/components/schemas/DomainRelationship'
    WithdrawRecipientConsentRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
            - relationships
          properties:
            type:
              type: string
              const: recipient-consents
            attributes:
              type: object
              required:
                - recipient_email
                - consent_type
              properties:
                recipient_email:
                  type: string
                  format: email
                consent_type:
                  type: string
                  enum:
                    - opt_in
                    - double_opt_in
                    - list_import
                    - manual
                withdrawn_via:
                  type: string
                  description: The channel the withdrawal arrived through. Defaults to `api`.
                  enum:
                    - unsubscribe_link
                    - dashboard
                    - api
                    - regulator_request
            relationships:
              type: object
              required:
                - domain
              properties:
                domain:
                  $ref: '#/components/schemas/DomainRelationship'
    RecipientConsentDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
        data:
          type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              const: recipient-consents
            id:
              type: string
              description: Unique identifier for the recorded consent.
              example: cnst_01JZX8K7Q2M3N4P5R6S7T8V9W1
            attributes:
              type: object
              properties:
                recipient_email:
                  type: string
                  format: email
                consent_type:
                  type: string
                  enum:
                    - opt_in
                    - double_opt_in
                    - list_import
                    - manual
                consent_source:
                  type: string
                  enum:
                    - signup_form
                    - csv_import
                    - api
                    - dashboard
                    - mcp_tool
                consented_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                withdrawn_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                  description: When consent was withdrawn (GDPR Article 7(3)); null while active.
                withdrawn_via:
                  type:
                    - string
                    - 'null'
                  description: The channel the withdrawal arrived through; null while active.
                  enum:
                    - unsubscribe_link
                    - dashboard
                    - api
                    - regulator_request
                    - null
    CreateDomainRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
          properties:
            type:
              type: string
              const: domains
            attributes:
              type: object
              required:
                - name
                - purpose
              properties:
                name:
                  type: string
                  maxLength: 253
                  pattern: '^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$'
                  description: Fully qualified domain name.
                purpose:
                  type: string
                  enum:
                    - marketing
                    - transactional
                region:
                  type: string
                  maxLength: 20
                mail_from_prefix:
                  type: string
                  maxLength: 100
                tls_policy:
                  type: string
                  enum:
                    - require
                    - optional
    UpdateDomainRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              const: domains
            id:
              $ref: '#/components/schemas/DomainId'
            attributes:
              type: object
              properties:
                tls_policy:
                  type: string
                  enum:
                    - require
                    - optional
                mail_from_prefix:
                  type: string
                  maxLength: 100
                rotate_dkim_key:
                  type: boolean
                open_tracking_enabled:
                  type: boolean
                click_tracking_enabled:
                  type: boolean
                tracking_redirect_mode:
                  type: string
                  enum:
                    - shared
                    - branded
    DomainDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/DomainResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    PaginationLinks:
      type: object
      description: "Top-level links for paginated collections: cursor traversal (first restarts without a cursor; last is always null under cursor pagination; prev/next are null when there is no page in that direction) plus the describedby link to this OpenAPI description."
      required:
        - first
        - last
        - prev
        - next
        - describedby
      properties:
        describedby:
          type: string
          format: uri-reference
        first:
          type:
            - string
            - 'null'
          format: uri-reference
        last:
          type:
            - string
            - 'null'
          format: uri-reference
        prev:
          type:
            - string
            - 'null'
          format: uri-reference
        next:
          type:
            - string
            - 'null'
          format: uri-reference
    PaginationMeta:
      type: object
      required:
        - per_page
        - has_more
        - next_cursor
        - prev_cursor
      additionalProperties: false
      properties:
        per_page:
          type: integer
          minimum: 1
          maximum: 100
        has_more:
          type: boolean
        next_cursor:
          type:
            - string
            - 'null'
        prev_cursor:
          type:
            - string
            - 'null'
    DomainCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/DomainResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    DomainResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: domains
        id:
          $ref: '#/components/schemas/DomainId'
        attributes:
          type: object
          required:
            - name
            - purpose
            - status
            - region
            - mail_from_prefix
            - dkim_selector
            - tls_policy
            - activated_at
            - created_at
            - updated_at
          properties:
            name:
              type: string
            purpose:
              type: string
              enum:
                - marketing
                - transactional
            status:
              type: string
              enum:
                - pending
                - verifying
                - active
                - suspended
                - failed
            region:
              type: string
            mail_from_prefix:
              type: string
            dkim_selector:
              type: string
            tls_policy:
              type: string
              enum:
                - require
                - optional
            activated_at:
              type:
                - string
                - 'null'
              format: date-time
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
    RecordId:
      type: string
      pattern: '^dns_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for a DNS record.
      example: dns_01JZX8K7Q2M3N4P5R6S7T8V9W1
    RecordCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RecordResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    RecordResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: records
        id:
          $ref: '#/components/schemas/RecordId'
        attributes:
          type: object
          required:
            - record_type
            - name
            - value
            - verified
            - priority
            - ttl
            - verified_at
          properties:
            record_type:
              type: string
              enum:
                - mx
                - spf
                - dkim
                - dmarc
                - cname
                - txt
            name:
              type: string
            value:
              type: string
            verified:
              type: boolean
            priority:
              type:
                - integer
                - 'null'
            ttl:
              type:
                - integer
                - 'null'
            verified_at:
              type:
                - string
                - 'null'
              format: date-time
    OutboundEmailId:
      type: string
      pattern: '^email_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for an outbound email.
      example: email_01JZX8K7Q2M3N4P5R6S7T8V9W2
    CreateOutboundEmailRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
            - relationships
          properties:
            type:
              type: string
              const: outbound-emails
            attributes:
              type: object
              required:
                - from
                - to
                - subject
                - html
              properties:
                from:
                  type: string
                  format: email
                to:
                  type: string
                  format: email
                cc:
                  description: |
                    Optional carbon-copy recipients, visible to everyone on the message. A single
                    address or an array of up to 10 addresses. Duplicate addresses — within the
                    list or repeating `to` — are dropped. Every cc recipient is checked against
                    the domain's `all`-scope suppressions and counts as one email against the plan's
                    included volume.
                  oneOf:
                    - type: string
                      format: email
                    - type: array
                      maxItems: 10
                      items:
                        type: string
                        format: email
                    - type: 'null'
                bcc:
                  description: |
                    Optional blind-carbon-copy recipients: they receive the message but never
                    appear in its headers, so other recipients cannot see them. A single address
                    or an array of up to 10 addresses. Duplicate addresses — within the list or
                    repeating `to`/`cc` — are dropped. Every bcc recipient is checked against
                    the domain's `all`-scope suppressions and counts as one email against the plan's
                    included volume.
                  oneOf:
                    - type: string
                      format: email
                    - type: array
                      maxItems: 10
                      items:
                        type: string
                        format: email
                    - type: 'null'
                reply_to:
                  description: |
                    Optional reply-to addresses — where replies land instead of the `from`
                    address. A single address or an array of up to 10 addresses. Every address
                    must belong to the sending domain's family: the sending domain itself, a
                    subdomain of it, or a parent of it (for example, sending from
                    `mail.acme.com` allows `support@acme.com`). Addresses outside that family
                    are rejected with a `422`.
                  oneOf:
                    - type: string
                      format: email
                    - type: array
                      maxItems: 10
                      items:
                        type: string
                        format: email
                    - type: 'null'
                subject:
                  type: string
                  maxLength: 998
                html:
                  type: string
                text:
                  type:
                    - string
                    - 'null'
                idempotency_key:
                  type:
                    - string
                    - 'null'
                  maxLength: 255
                agent_id:
                  type:
                    - string
                    - 'null'
                  maxLength: 128
                  pattern: '^[A-Za-z0-9_\-.]+$'
                track_opens:
                  type:
                    - boolean
                    - 'null'
                  description: "Per-send open-tracking override. Absent or null follows the sending domain's default; false forces tracking off; true requires open tracking enabled on the domain, otherwise 422."
                track_clicks:
                  type:
                    - boolean
                    - 'null'
                  description: "Per-send click-tracking override. Absent or null follows the sending domain's default; false forces tracking off; true requires click tracking enabled on the domain, otherwise 422."
                ai_disclosure:
                  type:
                    - boolean
                    - 'null'
                  description: "Optional AI-provenance marker. Absent or null follows the channel default (off for REST); true attaches the AI-disclosure header to the outgoing message; false forces it off."
                attachments:
                  type: array
                  maxItems: 20
                  description: |
                    Files to attach to the email. Provide the file either inline as base64 with
                    `content`, or as a remote URL with `path` — exactly one per attachment. The
                    total message size, including all attachments after encoding, must stay under
                    40 MB. Executable and script file types are not permitted.

                    To embed an image inline instead of appending it as a download, set a
                    `content_id` and reference it from the HTML body as `cid:<content_id>`
                    (for example `<img src="cid:logo">`).
                  items:
                    type: object
                    required:
                      - filename
                    properties:
                      filename:
                        type: string
                        maxLength: 255
                        description: |
                          File name including its extension. The extension determines the content
                          type when `content_type` is omitted, and is validated against the list of
                          disallowed executable and script types.
                      content:
                        type: string
                        description: |
                          Base64-encoded file content — use this to attach a local file. Mutually
                          exclusive with `path`.
                      path:
                        type: string
                        format: uri
                        description: |
                          HTTPS URL the file is fetched from — use this to attach a remote file.
                          Mutually exclusive with `content`. Only public HTTPS hosts are allowed.
                      content_type:
                        type: string
                        maxLength: 78
                        description: |
                          IANA media type of the file (for example `application/pdf`). Inferred from
                          the filename extension when omitted.
                      content_id:
                        type: string
                        maxLength: 78
                        description: |
                          When set, the file is embedded inline in the HTML body rather than appended
                          as a download; reference it from the HTML as `cid:<content_id>`.
                    oneOf:
                      - required:
                          - content
                      - required:
                          - path
                headers:
                  type: object
                  maxProperties: 10
                  propertyNames:
                    pattern: '^[!-9;-@A-~]{1,126}$'
                  additionalProperties:
                    type: string
                    minLength: 1
                    maxLength: 995
                    pattern: '^[ -~]{1,995}$'
                  description: |
                    Optional custom email headers as a name → value map, for example
                    `{"X-Entity-Ref-ID": "ref-123"}` to prevent Gmail from threading related
                    messages. Maximum 10 headers per email. Names are printable ASCII without
                    colons or spaces (max 126 characters); values are printable ASCII without
                    line breaks (max 995 characters); a name and its value combined must not
                    exceed 996 characters.

                    Reserved headers are rejected with a 422 rather than silently dropped:
                    addressing and envelope headers the provider controls (`To`, `From`,
                    `Subject`, `Cc`, `Bcc`, `Reply-To`, `Date`, `Message-ID`, `MIME-Version`,
                    `Content-Type`, `Content-Disposition`, `Return-Path`), headers the platform
                    injects (`Feedback-To`, `X-Complaint-To`, `List-Unsubscribe`,
                    `List-Unsubscribe-Post`, the AI-disclosure header), and any name in the
                    platform's reserved internal-header prefix range (a rejected request names the
                    offending header in its 422 response).

                    To reply to a received email, set `In-Reply-To` to that email's `message_id`,
                    and continue threads by appending prior message ids to `References`
                    (space-separated); both headers are accepted here.

                    A received email's own `In-Reply-To` and `References` are available in its
                    `headers` attribute, so a reply-to-a-reply can build its `References` chain as
                    the parent's `References` plus the parent's `message_id`. Header values must
                    fit the length limit documented above (995 characters) — for very deep threads,
                    trim the OLDEST ids from `References` first, but always keep the first (root)
                    id. No silent truncation happens server-side: an over-length `References` value
                    is rejected by validation.
            relationships:
              type: object
              required:
                - domain
              properties:
                domain:
                  $ref: '#/components/schemas/DomainRelationship'
    OutboundEmailDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/OutboundEmailResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    OutboundEmailCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/OutboundEmailResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    OutboundEmailResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: outbound-emails
        id:
          $ref: '#/components/schemas/OutboundEmailId'
        attributes:
          type: object
          required:
            - name
            - subject
            - recipient
            - cc
            - bcc
            - reply_to
            - agent_id
            - attachments
            - headers
            - status
            - message_id
            - created_at
            - updated_at
          properties:
            name:
              type: string
            subject:
              type: string
            recipient:
              type:
                - string
                - 'null'
            cc:
              type:
                - array
                - 'null'
              items:
                type: string
              description: Carbon-copy recipient addresses as sent. Null when the send carried none.
            bcc:
              type:
                - array
                - 'null'
              items:
                type: string
              description: Blind-carbon-copy recipient addresses as sent. Null when the send carried none.
            reply_to:
              type:
                - array
                - 'null'
              items:
                type: string
              description: Reply-to addresses as sent. Null when the send carried none.
            agent_id:
              type:
                - string
                - 'null'
            attachments:
              type: array
              description: |
                Metadata for the files attached to this email; an empty array when there are none.
                Raw file content is never returned.
              items:
                type: object
                required:
                  - filename
                  - content_type
                  - size
                  - inline
                  - sha256
                properties:
                  filename:
                    type: string
                  content_type:
                    type: string
                  size:
                    type: integer
                    description: Size of the file in bytes.
                  inline:
                    type: boolean
                    description: True when the file is embedded inline in the HTML body via its content id.
                  sha256:
                    type: string
                    description: SHA-256 hex digest of the file's bytes as sent, for later verification.
            headers:
              type:
                - object
                - 'null'
              additionalProperties:
                type: string
              description: |
                The custom headers supplied when the email was sent, as a name → value map.
                Null when the send carried none. Platform-injected headers are never included.
            status:
              type:
                - string
                - 'null'
              description: |
                Possible non-null values are queued, sent, delivered, bounced, complained,
                rejected, suppressed, and failed. Status tracks the primary (`to`) recipient's
                delivery journey; bounces or complaints affecting only `cc`/`bcc` recipients are
                recorded as events and suppressions without changing the send's status.
            message_id:
              type:
                - string
                - 'null'
              description: |
                Provider message identifier assigned to this send. This is NOT the RFC 5322
                `Message-ID` header value and must not be used as the `In-Reply-To` value when
                threading a reply — for that, use the received email's `message_id` attribute
                (see `InboundEmailResource`). Null when the email has not been sent yet.
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
    OutboundEmailStatsDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/OutboundEmailStatsResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    OutboundEmailStatsResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: outbound-email-stats
        id:
          $ref: '#/components/schemas/OutboundEmailId'
        attributes:
          type: object
          required:
            - sends
            - delivered
            - bounced
            - complained
            - opened
            - clicked
            - rejected
          properties:
            sends:
              type: integer
            delivered:
              type: integer
            bounced:
              type: integer
            complained:
              type: integer
            opened:
              type: integer
            clicked:
              type: integer
            rejected:
              type: integer
    SuppressionId:
      type: string
      pattern: '^supp_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for a suppression.
      example: supp_01JZX8K7Q2M3N4P5R6S7T8V9W3
    SuppressionCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SuppressionResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    SuppressionResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: suppressions
        id:
          $ref: '#/components/schemas/SuppressionId'
        attributes:
          type: object
          required:
            - email
            - reason
            - source
            - scope
            - suppressed_at
            - expires_at
            - created_at
          properties:
            email:
              type: string
            reason:
              type: string
              enum:
                - unsubscribe
                - hard_bounce
                - complaint
                - manual
                - list_import
                - soft_bounce
            source:
              type: string
              enum:
                - email_link
                - dashboard
                - api
                - aws_ses_sync
                - bulk_import
                - fbl_report
            scope:
              type: string
              enum:
                - marketing
                - all
              description: |
                What the entry blocks. `marketing` (unsubscribes) blocks campaign sends only;
                `all` (bounces, complaints, manual and imported entries) also blocks
                transactional sends.
            suppressed_at:
              type:
                - string
                - 'null'
              format: date-time
            expires_at:
              type:
                - string
                - 'null'
              format: date-time
            created_at:
              type:
                - string
                - 'null'
              format: date-time
    WebhookId:
      type: string
      pattern: '^whk_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for a webhook.
      example: whk_01JZX8K7Q2M3N4P5R6S7T8V9W4
    CreateWebhookRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
            - relationships
          properties:
            type:
              type: string
              const: webhooks
            attributes:
              type: object
              required:
                - name
                - url
                - events
              properties:
                name:
                  type: string
                  maxLength: 255
                url:
                  type: string
                  format: uri
                  description: HTTPS endpoint URL that will receive webhook deliveries.
                events:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    description: Webhook event type, including email.sent, email.delivered, email.delayed, email.bounced, email.complained, email.opened, email.clicked, email.rejected, email.unsubscribed, inbound_email.received, campaign.sending, campaign.sent, campaign.paused, campaign.failed, domain.verified, domain.verification_failed, domain.suspended, or the wildcard *.
            relationships:
              type: object
              required:
                - domain
              properties:
                domain:
                  $ref: '#/components/schemas/DomainRelationship'
    UpdateWebhookRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              const: webhooks
            id:
              $ref: '#/components/schemas/WebhookId'
            attributes:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 255
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    description: Webhook event type, including email.sent, email.delivered, email.delayed, email.bounced, email.complained, email.opened, email.clicked, email.rejected, email.unsubscribed, inbound_email.received, campaign.sending, campaign.sent, campaign.paused, campaign.failed, domain.verified, domain.verification_failed, domain.suspended, or the wildcard *.
                active:
                  type: boolean
                rotate_secret:
                  type: boolean
    WebhookDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/WebhookResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    WebhookCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WebhookResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    WebhookResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: webhooks
        id:
          $ref: '#/components/schemas/WebhookId'
        attributes:
          type: object
          required:
            - name
            - url
            - events
            - active
            - consecutive_failures
            - created_at
            - updated_at
            - secret_hint
          properties:
            name:
              type: string
            url:
              type: string
              format: uri
            events:
              type: array
              items:
                type: string
            active:
              type: boolean
            consecutive_failures:
              type: integer
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
            secret_hint:
              type: string
            secret:
              type: string
    WebhookDeliveryId:
      type: string
      pattern: '^whcall_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for a webhook delivery.
      example: whcall_01JZX8K7Q2M3N4P5R6S7T8V9W5
    OutboundWebhookDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/OutboundWebhookResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    OutboundWebhookCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/OutboundWebhookResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    OutboundWebhookResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: webhook-deliveries
        id:
          $ref: '#/components/schemas/WebhookDeliveryId'
        attributes:
          type: object
          required:
            - msg_id
            - event_type
            - status
            - response_code
            - attempts
            - last_attempt_at
            - next_retry_at
            - created_at
            - updated_at
          properties:
            msg_id:
              type: string
            event_type:
              type: string
            status:
              type: string
            response_code:
              type:
                - integer
                - 'null'
            attempts:
              type:
                - integer
                - 'null'
              description: Null until the first delivery attempt has been made.
            last_attempt_at:
              type:
                - string
                - 'null'
              format: date-time
            next_retry_at:
              type:
                - string
                - 'null'
              format: date-time
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
            payload:
              type: object
              additionalProperties: true
    InboundEmailId:
      type: string
      pattern: '^inmail_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for an inbound email.
      example: inmail_01JZX8K7Q2M3N4P5R6S7T8V9W7
    InboundEmailDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/InboundEmailResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    InboundEmailCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/InboundEmailResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    InboundEmailResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: inbound-emails
        id:
          $ref: '#/components/schemas/InboundEmailId'
        attributes:
          type: object
          required:
            - from
            - to
            - subject
            - body_html
            - body_text
            - headers
            - attachments
            - status
            - ses_message_id
            - message_id
            - received_at
            - created_at
          properties:
            from:
              type: string
            to:
              type: string
            subject:
              type:
                - string
                - 'null'
            body_html:
              type:
                - string
                - 'null'
            body_text:
              type:
                - string
                - 'null'
            headers:
              type:
                - object
                - 'null'
              additionalProperties: true
              description: |
                Selected headers of the received message — `Message-ID`, `Date`, `In-Reply-To`,
                and `References`. Values may be empty when the received message did not carry
                the header; `In-Reply-To` and `References` are populated when the received
                email is part of a thread.
            attachments:
              type:
                - array
                - 'null'
              items:
                type: object
                additionalProperties: true
            status:
              type: string
              enum:
                - received
                - virus_detected
                - oversized
                - parse_rejected
            ses_message_id:
              type:
                - string
                - 'null'
            message_id:
              type:
                - string
                - 'null'
              description: |
                RFC 5322 Message-ID header of the received message. Use it as the `In-Reply-To`
                header value when sending a reply. Distinct from `ses_message_id`, the provider
                message identifier.
            received_at:
              type:
                - string
                - 'null'
              format: date-time
            created_at:
              type:
                - string
                - 'null'
              format: date-time
    InboxId:
      type: string
      pattern: '^inbox_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for an inbox.
      example: inbox_01JZX8K7Q2M3N4P5R6S7T8V9W6
    InboxResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: inboxes
        id:
          $ref: '#/components/schemas/InboxId'
        attributes:
          type: object
          required:
            - name
            - address
            - created_at
            - updated_at
          properties:
            name:
              type: string
            address:
              type: string
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
    InboxDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/InboxResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    InboxCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/InboxResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    CreateInboxRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
          properties:
            type:
              type: string
              const: inboxes
            attributes:
              type: object
              required:
                - name
                - address
              properties:
                name:
                  type: string
                  maxLength: 255
                address:
                  type: string
                  format: email
                  maxLength: 255
    UpdateInboxRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              const: inboxes
            id:
              $ref: '#/components/schemas/InboxId'
            attributes:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 255
                address:
                  type: string
                  format: email
                  maxLength: 255
    AudienceId:
      type: string
      pattern: '^aud_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for an audience.
      example: aud_01JZX8K7Q2M3N4P5R6S7T8V9W8
    CreateAudienceRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
            - relationships
          properties:
            type:
              type: string
              const: audiences
            attributes:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  maxLength: 255
                description:
                  type:
                    - string
                    - 'null'
                  maxLength: 1000
            relationships:
              type: object
              required:
                - domain
              properties:
                domain:
                  $ref: '#/components/schemas/DomainRelationship'
    UpdateAudienceRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              const: audiences
            id:
              $ref: '#/components/schemas/AudienceId'
            attributes:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 255
                description:
                  type:
                    - string
                    - 'null'
                  maxLength: 1000
    AudienceDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/AudienceResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    AudienceCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/AudienceResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    AudienceResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: audiences
        id:
          $ref: '#/components/schemas/AudienceId'
        attributes:
          type: object
          required:
            - name
            - description
            - created_at
            - updated_at
          properties:
            name:
              type: string
            description:
              type:
                - string
                - 'null'
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
    ContactId:
      type: string
      pattern: '^cnct_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for a contact.
      example: cnct_01JZX8K7Q2M3N4P5R6S7T8V9W9
    CreateContactRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
          properties:
            type:
              type: string
              const: contacts
            attributes:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 255
                name:
                  type:
                    - string
                    - 'null'
                  maxLength: 255
                preferences:
                  type:
                    - object
                    - 'null'
                  additionalProperties: true
                custom_fields:
                  type:
                    - object
                    - 'null'
                  additionalProperties: true
    UpdateContactRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              const: contacts
            id:
              $ref: '#/components/schemas/ContactId'
            attributes:
              type: object
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 255
                name:
                  type:
                    - string
                    - 'null'
                  maxLength: 255
                preferences:
                  type:
                    - object
                    - 'null'
                  additionalProperties: true
                custom_fields:
                  type:
                    - object
                    - 'null'
                  additionalProperties: true
    ContactDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/ContactResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    ContactCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ContactResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    ContactResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: contacts
        id:
          $ref: '#/components/schemas/ContactId'
        attributes:
          type: object
          required:
            - email
            - name
            - preferences
            - custom_fields
            - created_at
            - updated_at
          properties:
            email:
              type: string
            name:
              type:
                - string
                - 'null'
            preferences:
              type:
                - object
                - 'null'
              additionalProperties: true
            custom_fields:
              type:
                - object
                - 'null'
              additionalProperties: true
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
    ImportContactsRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
          properties:
            type:
              type: string
              const: contacts
            attributes:
              type: object
              required:
                - contacts
              properties:
                contacts:
                  type: array
                  minItems: 1
                  maxItems: 1000
                  items:
                    type: object
                    required:
                      - email
                    properties:
                      email:
                        type: string
                        format: email
                        maxLength: 255
                      name:
                        type:
                          - string
                          - 'null'
                        maxLength: 255
                      preferences:
                        type:
                          - object
                          - 'null'
                        additionalProperties: true
                      custom_fields:
                        type:
                          - object
                          - 'null'
                        additionalProperties: true
    ContactImportDocument:
      type: object
      description: Import result as a meta document — a bulk import creates contacts, not an addressable import resource.
      required:
        - meta
        - jsonapi
        - links
      properties:
        links:
          $ref: '#/components/schemas/DocumentLinks'
        meta:
          type: object
          required:
            - created
            - skipped
          properties:
            created:
              type: integer
            skipped:
              type: integer
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
    CampaignId:
      type: string
      pattern: '^camp_[0-9A-HJKMNP-TV-Z]{26}$'
      description: Unique identifier for a campaign.
      example: camp_01JZX8K7Q2M3N4P5R6S7T8V9WA
    CreateCampaignRequestDocument:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - attributes
            - relationships
          properties:
            type:
              type: string
              const: campaigns
            attributes:
              type: object
              required:
                - name
                - subject
                - body_html
              properties:
                name:
                  type: string
                  maxLength: 255
                subject:
                  type: string
                  maxLength: 255
                  description: |
                    Supports merge tags — see `body_html`. Values are inserted literally here,
                    with any carriage return or line feed removed, because the subject becomes a
                    single header line.

                    Must not itself contain a carriage return or line feed; a subject that does is
                    rejected with `422`.
                body_html:
                  type: string
                  description: |
                    Supports merge tags: `{{name}}`, `{{email}}`, `{{unsubscribe_url}}`,
                    `{{unsubscribe_mailto}}`, and any key from a contact's `custom_fields`.
                    Every recipient's message is rendered individually. A tag with no matching
                    value renders as empty. Values are HTML-escaped here and inserted literally
                    in `subject` and `body_text`. The unsubscribe tags are populated only for
                    real audience contacts, never for a `test_mode` send.
                body_text:
                  type:
                    - string
                    - 'null'
                  description: |
                    Supports merge tags — see `body_html`. Values are inserted literally here.
                from_address:
                  type:
                    - string
                    - 'null'
                  format: email
                  maxLength: 255
                  description: |
                    Optional sender address for the campaign. When set, it must belong to the
                    sending domain's family: the audience's sending domain itself, a subdomain of
                    it, or a parent of it. Addresses outside that family are rejected with a
                    `422`. When null, the campaign sends from `noreply@` on the sending domain.
                reply_to:
                  description: |
                    Optional reply-to addresses for the whole campaign — where replies land instead
                    of the `from` address. A single address or an array of up to 10 addresses. Every
                    address must belong to the sending domain's family: the sending domain itself, a
                    subdomain of it, or a parent of it (for example, a campaign on `mail.acme.com`
                    allows `support@acme.com`). Addresses outside that family are rejected with a `422`.
                  oneOf:
                    - type: string
                      format: email
                    - type: array
                      maxItems: 10
                      items:
                        type: string
                        format: email
                    - type: 'null'
                test_mode:
                  type: boolean
                test_recipient_email:
                  type:
                    - string
                    - 'null'
                  format: email
                  maxLength: 255
                track_opens:
                  type:
                    - boolean
                    - 'null'
                  description: "Per-campaign open-tracking override. Absent or null follows the sending domain's default; false forces tracking off; true requires open tracking enabled on the domain, otherwise 422."
                track_clicks:
                  type:
                    - boolean
                    - 'null'
                  description: "Per-campaign click-tracking override. Absent or null follows the sending domain's default; false forces tracking off; true requires click tracking enabled on the domain, otherwise 422."
            relationships:
              type: object
              required:
                - audience
              properties:
                audience:
                  $ref: '#/components/schemas/AudienceRelationship'
    UpdateCampaignRequestDocument:
      type: object
      description: PATCH drives the campaign lifecycle state machine.
      required:
        - data
      properties:
        data:
          type: object
          required:
            - type
            - id
            - attributes
          properties:
            type:
              type: string
              const: campaigns
            id:
              $ref: '#/components/schemas/CampaignId'
            attributes:
              type: object
              properties:
                status:
                  type: string
                  enum:
                    - draft
                    - scheduled
                    - sending
                    - paused
                    - sent
                    - cancelled
                    - failed
                name:
                  type: string
                  maxLength: 255
                subject:
                  type: string
                  maxLength: 255
                  description: |
                    Must not contain a carriage return or line feed; a subject that does is
                    rejected with `422`. Merge-tag values are inserted with those characters
                    removed, because the subject becomes a single header line.
                body_html:
                  type: string
                body_text:
                  type:
                    - string
                    - 'null'
                from_address:
                  type:
                    - string
                    - 'null'
                  format: email
                  maxLength: 255
                  description: |
                    Optional sender address for the campaign. When set, it must belong to the
                    sending domain's family: the audience's sending domain itself, a subdomain of
                    it, or a parent of it. Addresses outside that family are rejected with a
                    `422`. When null, the campaign sends from `noreply@` on the sending domain.
                reply_to:
                  description: |
                    Optional reply-to addresses for the whole campaign — where replies land instead
                    of the `from` address. A single address or an array of up to 10 addresses. Every
                    address must belong to the sending domain's family: the sending domain itself, a
                    subdomain of it, or a parent of it. Addresses outside that family are rejected
                    with a `422`. Editable only while the campaign is a draft.
                  oneOf:
                    - type: string
                      format: email
                    - type: array
                      maxItems: 10
                      items:
                        type: string
                        format: email
                    - type: 'null'
                test_mode:
                  type: boolean
                test_recipient_email:
                  type:
                    - string
                    - 'null'
                  format: email
                  maxLength: 255
                scheduled_at:
                  type:
                    - string
                    - 'null'
                  format: date-time
                track_opens:
                  type:
                    - boolean
                    - 'null'
                  description: "Per-campaign open-tracking override. Absent or null follows the sending domain's default; false forces tracking off; true requires open tracking enabled on the domain, otherwise 422."
                track_clicks:
                  type:
                    - boolean
                    - 'null'
                  description: "Per-campaign click-tracking override. Absent or null follows the sending domain's default; false forces tracking off; true requires click tracking enabled on the domain, otherwise 422."
    CampaignDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/CampaignResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    CampaignCollectionDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/CampaignResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/PaginationLinks'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
    CampaignResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: campaigns
        id:
          $ref: '#/components/schemas/CampaignId'
        attributes:
          type: object
          required:
            - name
            - subject
            - body_html
            - body_text
            - status
            - from_address
            - reply_to
            - test_mode
            - test_recipient_email
            - scheduled_at
            - dispatched_at
            - completed_at
            - total_recipients
            - total_batches
            - batches_completed
            - failed_reason
            - created_at
            - updated_at
          properties:
            name:
              type: string
            subject:
              type: string
            body_html:
              type: string
            body_text:
              type:
                - string
                - 'null'
            status:
              type: string
              enum:
                - draft
                - scheduled
                - sending
                - paused
                - sent
                - cancelled
                - failed
            from_address:
              type:
                - string
                - 'null'
            reply_to:
              type:
                - array
                - 'null'
              items:
                type: string
              description: Reply-to addresses as sent. Null when the campaign carried none.
            test_mode:
              type: boolean
            test_recipient_email:
              type:
                - string
                - 'null'
            scheduled_at:
              type:
                - string
                - 'null'
              format: date-time
            dispatched_at:
              type:
                - string
                - 'null'
              format: date-time
            completed_at:
              type:
                - string
                - 'null'
              format: date-time
            total_recipients:
              type:
                - integer
                - 'null'
            total_batches:
              type:
                - integer
                - 'null'
            batches_completed:
              type:
                - integer
                - 'null'
            failed_reason:
              type:
                - string
                - 'null'
              description: |
                Why the campaign failed, when `status` is `failed`: `domain_not_found`,
                `domain_unverified`, `domain_suspended`, `audience_deleted`, `no_recipients`,
                `test_recipient_suppressed`, `free_tier_limit`, or `free_tier_capacity`.
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            updated_at:
              type:
                - string
                - 'null'
              format: date-time
        relationships:
          type: object
          description: Present when the audience relationship is requested via `include=audience`.
          required:
            - audience
          properties:
            audience:
              $ref: '#/components/schemas/NullableAudienceRelationship'
    CampaignStatsDocument:
      type: object
      required:
        - data
        - jsonapi
        - links
      properties:
        data:
          $ref: '#/components/schemas/CampaignStatsResource'
        jsonapi:
          $ref: '#/components/schemas/JsonApiObject'
        links:
          $ref: '#/components/schemas/DocumentLinks'
    CampaignStatsResource:
      type: object
      required:
        - type
        - id
        - attributes
      properties:
        type:
          type: string
          const: campaign-stats
        id:
          $ref: '#/components/schemas/CampaignId'
        attributes:
          type: object
          required:
            - total_recipients
            - sent
            - delivered
            - bounced
            - complained
            - suppressed
            - opened
            - clicked
            - unsubscribed
            - open_rate
            - click_rate
            - bounce_rate
            - complaint_rate
          properties:
            total_recipients:
              type: integer
            sent:
              type: integer
            delivered:
              type: integer
            bounced:
              type: integer
            complained:
              type: integer
            suppressed:
              type: integer
            opened:
              type:
                - integer
                - 'null'
              description: Null when open tracking is disabled for the sending domain.
            clicked:
              type:
                - integer
                - 'null'
              description: Null when click tracking is disabled for the sending domain.
            unsubscribed:
              type: integer
            open_rate:
              type:
                - number
                - 'null'
              description: Null when open tracking is disabled for the sending domain.
            click_rate:
              type:
                - number
                - 'null'
              description: Null when click tracking is disabled for the sending domain.
            bounce_rate:
              type: number
            complaint_rate:
              type: number
    JsonApiObject:
      type: object
      required:
        - version
      properties:
        version:
          type: string
          const: '1.1'
    DocumentLinks:
      type: object
      description: "Top-level links for non-paginated documents. describedby points at this OpenAPI description (JSON:API v1.1 defines describedby as a top-level links member; the jsonapi object itself carries only the version)."
      required:
        - describedby
      properties:
        describedby:
          type: string
          format: uri-reference
    ErrorDocument:
      type: object
      required:
        - errors
        - links
      properties:
        links:
          $ref: '#/components/schemas/DocumentLinks'
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ErrorObject'
    ValidationErrorDocument:
      type: object
      required:
        - errors
      properties:
        errors:
          type: array
          items:
            allOf:
              - $ref: '#/components/schemas/ErrorObject'
              - type: object
                properties:
                  source:
                    type: object
                    properties:
                      pointer:
                        type: string
    ErrorObject:
      type: object
      required:
        - status
        - code
        - detail
      properties:
        status:
          type: string
        code:
          type: string
          description: "Stable machine-readable identifier (e.g. validation_failed, not_found, rate_limited, missing_scope). Branch on status + code; detail wording is not part of the contract."
        detail:
          type: string
