openapi: 3.0.3
info:
  title: Mail.td API
  version: "2.0.0"
  description: |
    Email sandbox and testing platform for developers. Create test inboxes, receive emails via REST API, and automate email testing in CI/CD pipelines.

    ## Quick Start

    1. `GET /api/challenge` — get a proof-of-work challenge
    2. `POST /api/accounts` — create a mailbox (returns a JWT token)
    3. `GET /api/accounts/{account_id}/messages` — fetch emails using the token

    ## Authentication

    All authenticated endpoints require: `Authorization: Bearer <token>`

    - **Mailbox tokens**: JWT, valid for 7 days. Obtained from `POST /api/accounts` or `POST /api/token`.
    - **Pro API tokens**: Long-lived, prefixed with `tm_pro_`. Created in the Pro dashboard or via `POST /api/user/tokens`.

    Expired tokens return `401` with `{"error": "invalid_or_expired_token"}`. Re-authenticate via `POST /api/token`.

    ## Unified API

    The unified API at `/api/accounts/{account_id}/...` accepts both free mailbox JWTs and Pro tokens.
    Pro users can manage all their mailboxes through a single set of endpoints.

    ## Pagination

    List endpoints return up to 30 items per page. If fewer than 30 items are returned, you've reached the last page.

    ## Rate Limits

    - Legacy routes (`/api/*`, `/pro/api/*`): 8 requests/second per IP.
    - Unified routes: Free plan 20 QPS, Pro plan 50 QPS per IP.

    ## Full Documentation

    [docs.mail.td](https://docs.mail.td)

servers:
  - url: https://api.mail.td

tags:
  - name: Auth
    description: Create and sign in to email addresses
  - name: Accounts
    description: Account-scoped mailbox operations (unified API)
  - name: Messages
    description: Email message operations
  - name: Pro Auth
    description: Pro user registration and authentication
  - name: Pro User
    description: Pro user profile and account management
  - name: Pro Accounts
    description: Pro mailbox management
  - name: Pro Domains
    description: Pro custom domain management
  - name: Pro Webhooks
    description: Pro webhook management
  - name: Pro Tokens
    description: Pro API token management
  - name: Pro Sandbox
    description: SMTP sandbox for email testing
  - name: Subscription
    description: Pro subscription and billing management

paths:
  # ──────────────────────────────────────────────
  # Public API
  # ──────────────────────────────────────────────

  /api/domains:
    get:
      tags: [Auth]
      summary: List available domains
      operationId: listDomains
      responses:
        "200":
          description: List of available domains
          content:
            application/json:
              schema:
                type: object
                properties:
                  domains:
                    type: array
                    items:
                      $ref: "#/components/schemas/Domain"

  /api/challenge:
    get:
      tags: [Auth]
      summary: Get proof-of-work challenge
      operationId: getChallenge
      description: |
        Returns a proof-of-work challenge that must be solved before creating a free account.
        The client must find a nonce such that SHA-256(salt + nonce) has the required number of leading zero bits.
      responses:
        "200":
          description: PoW challenge
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PoWChallenge"

  /api/accounts:
    post:
      tags: [Auth]
      summary: Create a new email address
      operationId: createAccount
      description: |
        Create a new mailbox on any available system domain.

        **Free users:** Must include a solved proof-of-work challenge (`pow` field).

        **Pro users:** Include a Pro Bearer token (`Authorization: Bearer <pro_token>`)
        to create mailboxes on both system domains and your verified custom domains.
        PoW is not required for Pro users.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [address]
              properties:
                address:
                  type: string
                  format: email
                  example: user@mail.td
                password:
                  type: string
                  description: Mailbox password (plain text, hashed server-side)
                  example: mypassword123
                auth_key:
                  type: string
                  description: Pre-hashed auth key (alternative to password)
                pow:
                  $ref: "#/components/schemas/PoWSolution"
      responses:
        "201":
          description: Email address created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  address:
                    type: string
                    format: email
                  token:
                    type: string
                    description: JWT token for accessing this mailbox
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: PoW verification failed or mailbox limit reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Address already taken
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Rate limit exceeded (free users only)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/token:
    post:
      tags: [Auth]
      summary: Sign in to an existing mailbox
      operationId: login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [address]
              properties:
                address:
                  type: string
                  format: email
                password:
                  type: string
                  description: Mailbox password (plain text)
                auth_key:
                  type: string
                  description: Pre-hashed auth key (alternative to password)
      responses:
        "200":
          description: Sign-in successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  address:
                    type: string
                    format: email
                  token:
                    type: string
        "400":
          description: Missing required fields
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Legacy Mailbox API (free)
  # ──────────────────────────────────────────────

  /api/me:
    get:
      tags: [Accounts]
      summary: Get current mailbox info (legacy)
      operationId: getMe
      description: "Legacy endpoint. Prefer `GET /api/accounts/{account_id}` for new integrations."
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Mailbox details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountInfo"

  /api/account:
    delete:
      tags: [Accounts]
      summary: Delete current email address (legacy)
      operationId: deleteAccountLegacy
      description: "Legacy endpoint. Prefer `DELETE /api/accounts/{account_id}` for new integrations."
      security:
        - BearerAuth: []
      responses:
        "204":
          description: Email address and all messages deleted

  /api/messages:
    get:
      tags: [Messages]
      summary: List messages (legacy)
      operationId: listMessagesLegacy
      description: "Legacy endpoint. Prefer `/api/accounts/{account_id}/messages` for new integrations."
      security:
        - BearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
      responses:
        "200":
          description: Paginated message list
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      $ref: "#/components/schemas/EmailSummary"
                  page:
                    type: integer

  /api/messages/{id}:
    get:
      tags: [Messages]
      summary: Get a single message (legacy)
      operationId: getMessageLegacy
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Full message detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailDetail"
        "404":
          description: Message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      tags: [Messages]
      summary: Delete a message (legacy)
      operationId: deleteMessageLegacy
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Message deleted
        "404":
          description: Message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/messages/{id}/source:
    get:
      tags: [Messages]
      summary: Download raw EML source (legacy)
      operationId: getMessageSourceLegacy
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Raw email source
          content:
            message/rfc822:
              schema:
                type: string
                format: binary
        "404":
          description: Message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/messages/{id}/read:
    put:
      tags: [Messages]
      summary: Mark message as read (legacy)
      operationId: markAsReadLegacy
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Message marked as read (idempotent)

  /api/messages/read:
    put:
      tags: [Messages]
      summary: Batch mark messages as read (legacy)
      operationId: batchMarkAsReadLegacy
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Message IDs to mark as read (max 200)
                all:
                  type: boolean
                  description: Mark all unread messages as read
      responses:
        "200":
          description: Messages marked as read
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: integer
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/messages/{id}/attachments/{index}:
    get:
      tags: [Messages]
      summary: Download an attachment (legacy)
      operationId: getAttachmentLegacy
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: index
          in: path
          required: true
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Attachment binary
          headers:
            Content-Disposition:
              schema:
                type: string
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/ws:
    get:
      tags: [Messages]
      summary: WebSocket for real-time email notifications
      operationId: websocket
      description: |
        Upgrade to WebSocket. After connecting, send `{"type": "auth", "token": "jwt"}`
        to authenticate. Server responds with `{"type": "auth_ok"}` or `{"error": "..."}`.
        On new mail the server pushes `{"id", "from", "subject", "account_id", "address"}`.
      responses:
        "101":
          description: Switching protocols (WebSocket upgrade)

  # ──────────────────────────────────────────────
  # Unified Account-Scoped API
  # ──────────────────────────────────────────────

  /api/accounts/{account_id}:
    get:
      tags: [Accounts]
      summary: Get mailbox info
      operationId: getAccount
      description: Accepts free mailbox JWT or Pro token. Pro users can access any mailbox they own.
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
      responses:
        "200":
          description: Mailbox details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountInfo"
        "404":
          description: Account not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      tags: [Accounts]
      summary: Delete email address
      operationId: deleteAccount
      description: Permanently deletes the mailbox and all emails. Pro users must have active subscription.
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
      responses:
        "204":
          description: Email address and all messages deleted
        "402":
          description: Subscription expired (Pro users)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/accounts/{account_id}/reset-password:
    put:
      tags: [Accounts]
      summary: Reset mailbox password
      operationId: resetAccountPassword
      description: Resets the mailbox password and invalidates all existing JWTs.
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                password:
                  type: string
                  description: New mailbox password (plain text)
                auth_key:
                  type: string
                  description: Pre-hashed auth key (alternative to password)
      responses:
        "200":
          description: Password reset
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: password_reset
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/accounts/{account_id}/messages:
    get:
      tags: [Accounts]
      summary: List messages
      operationId: listMessages
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
      responses:
        "200":
          description: Paginated message list
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      $ref: "#/components/schemas/EmailSummary"
                  page:
                    type: integer

  /api/accounts/{account_id}/messages/read:
    put:
      tags: [Accounts]
      summary: Batch mark messages as read
      operationId: batchMarkAsRead
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Message IDs to mark as read (max 200)
                all:
                  type: boolean
                  description: Mark all unread messages as read
      responses:
        "200":
          description: Messages marked as read
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: integer
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/accounts/{account_id}/messages/{id}:
    get:
      tags: [Accounts]
      summary: Get a single message
      operationId: getMessage
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Full message detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailDetail"
        "404":
          description: Message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      tags: [Accounts]
      summary: Delete a message
      operationId: deleteMessage
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Message deleted
        "404":
          description: Message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/accounts/{account_id}/messages/{id}/source:
    get:
      tags: [Accounts]
      summary: Download raw EML source
      operationId: getMessageSource
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Raw email source
          content:
            message/rfc822:
              schema:
                type: string
                format: binary
        "404":
          description: Message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/accounts/{account_id}/messages/{id}/read:
    put:
      tags: [Accounts]
      summary: Mark message as read
      operationId: markAsRead
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Message marked as read (idempotent)

  /api/accounts/{account_id}/messages/{id}/attachments/{index}:
    get:
      tags: [Accounts]
      summary: Download an attachment
      operationId: getAttachment
      security:
        - BearerAuth: []
        - ProBearerAuth: []
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: index
          in: path
          required: true
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Attachment binary
          headers:
            Content-Disposition:
              schema:
                type: string
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Pro Auth
  # ──────────────────────────────────────────────

  /pro/api/register:
    post:
      tags: [Pro Auth]
      summary: Register a Pro account
      operationId: proRegister
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  description: "8-72 characters, at least 1 letter and 1 digit"
      responses:
        "201":
          description: Account created. Session token set via cookie.
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_id:
                    type: string
                    format: uuid
                  email:
                    type: string
                    format: email
                  plan:
                    type: string
                    example: pro
                  status:
                    type: string
                    example: pending
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Email already registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/login:
    post:
      tags: [Pro Auth]
      summary: Sign in to Pro account
      operationId: proLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
      responses:
        "200":
          description: Sign-in successful. Session token set via cookie.
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_id:
                    type: string
                    format: uuid
                  email:
                    type: string
                    format: email
                  plan:
                    type: string
                  status:
                    type: string
                    enum: [pending, active, suspended]
                  downgraded:
                    type: boolean
        "400":
          description: Missing required fields
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/password-reset-request:
    post:
      tags: [Pro Auth]
      summary: Request password reset
      operationId: proPasswordResetRequest
      description: Always returns 200 to prevent email enumeration.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
      responses:
        "200":
          description: Request accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "if the email exists, a reset link has been sent"

  /pro/api/password-reset:
    post:
      tags: [Pro Auth]
      summary: Reset Pro account password
      operationId: proPasswordReset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token, password]
              properties:
                token:
                  type: string
                  description: Reset token from email
                password:
                  type: string
                  description: "New password (8-72 characters, at least 1 letter and 1 digit)"
      responses:
        "200":
          description: Password updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: password_updated
        "400":
          description: Invalid or expired token, or validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Pro User Management
  # ──────────────────────────────────────────────

  /pro/api/me:
    get:
      tags: [Pro User]
      summary: Get Pro user profile
      operationId: proGetMe
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Pro user details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProUser"

  /pro/api/accounts:
    get:
      tags: [Pro Accounts]
      summary: List mailboxes under Pro account
      operationId: proListAccounts
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: List of mailboxes
          content:
            application/json:
              schema:
                type: object
                properties:
                  accounts:
                    type: array
                    items:
                      $ref: "#/components/schemas/Account"

  /pro/api/accounts/{id}:
    delete:
      tags: [Pro Accounts]
      summary: Delete a mailbox
      operationId: proDeleteAccount
      description: Requires active subscription status.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Mailbox and all its emails deleted

  /pro/api/accounts/{id}/reset-password:
    put:
      tags: [Pro Accounts]
      summary: Reset a mailbox password
      operationId: proResetAccountPassword
      description: Requires active subscription status. Invalidates all existing JWTs for the mailbox.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                password:
                  type: string
                  description: New mailbox password (plain text)
                auth_key:
                  type: string
                  description: Pre-hashed auth key (alternative to password)
      responses:
        "200":
          description: Password reset
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: password_reset
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Mailbox not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/accounts/{id}/messages:
    get:
      tags: [Pro Accounts]
      summary: List messages for a mailbox
      operationId: proListAccountMessages
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
      responses:
        "200":
          description: Paginated message list
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      $ref: "#/components/schemas/EmailSummary"
                  page:
                    type: integer
        "404":
          description: Account not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Pro Domains
  # ──────────────────────────────────────────────

  /pro/api/domains:
    get:
      tags: [Pro Domains]
      summary: List custom domains
      operationId: proListDomains
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: List of custom domains
          content:
            application/json:
              schema:
                type: object
                properties:
                  domains:
                    type: array
                    items:
                      $ref: "#/components/schemas/ProDomain"
    post:
      tags: [Pro Domains]
      summary: Add a custom domain
      operationId: proCreateDomain
      description: Requires active subscription status.
      security:
        - ProBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain:
                  type: string
                  example: custom.com
      responses:
        "201":
          description: Domain added with DNS instructions
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  domain:
                    type: string
                  verify_token:
                    type: string
                  dns_records:
                    type: array
                    items:
                      $ref: "#/components/schemas/DNSRecord"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Domain limit reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Domain already registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/domains/{id}/verify:
    post:
      tags: [Pro Domains]
      summary: Verify domain DNS configuration
      operationId: proVerifyDomain
      description: Requires active subscription status. Verification expires after 72 hours.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  verify_status:
                    type: string
                    enum: [verified, pending]
                  txt_record:
                    type: boolean
                  mx_record:
                    type: boolean
                  dns_records:
                    type: array
                    description: Present when status is pending
                    items:
                      allOf:
                        - $ref: "#/components/schemas/DNSRecord"
                        - type: object
                          properties:
                            ok:
                              type: boolean
        "404":
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Verification expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/domains/{id}:
    delete:
      tags: [Pro Domains]
      summary: Delete a custom domain
      operationId: proDeleteDomain
      description: Requires active subscription status.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Domain deleted
        "404":
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Pro Webhooks
  # ──────────────────────────────────────────────

  /pro/api/webhooks:
    get:
      tags: [Pro Webhooks]
      summary: List webhooks
      operationId: proListWebhooks
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: List of webhooks
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhooks:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        url:
                          type: string
                          format: uri
                        events:
                          type: array
                          items:
                            type: string
                        status:
                          type: string
                          enum: [active, failed]
                        failure_count:
                          type: integer
                        last_triggered_at:
                          type: string
                          format: date-time
                          nullable: true
                        created_at:
                          type: string
                          format: date-time
    post:
      tags: [Pro Webhooks]
      summary: Create a webhook
      operationId: proCreateWebhook
      description: Requires active subscription status. Maximum 1 webhook per user.
      security:
        - ProBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
                  description: Must be HTTPS
                events:
                  type: array
                  items:
                    type: string
                  default: ["email.received"]
                  example: ["email.received"]
      responses:
        "201":
          description: Webhook created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Webhook limit reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/webhooks/{id}:
    delete:
      tags: [Pro Webhooks]
      summary: Delete a webhook
      operationId: proDeleteWebhook
      description: Requires active subscription status.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Webhook deleted
        "404":
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/webhooks/{id}/rotate:
    post:
      tags: [Pro Webhooks]
      summary: Rotate webhook secret
      operationId: proRotateWebhookSecret
      description: Requires active subscription status. Also resets failure count and sets status to active.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: New secret generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  secret:
                    type: string
                    example: whsec_new...
        "404":
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/webhooks/{id}/deliveries:
    get:
      tags: [Pro Webhooks]
      summary: List webhook deliveries
      operationId: proListWebhookDeliveries
      description: Returns the last 20 delivery attempts.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: List of delivery attempts
          content:
            application/json:
              schema:
                type: object
                properties:
                  deliveries:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookDelivery"
        "404":
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Pro Tokens
  # ──────────────────────────────────────────────

  /pro/api/tokens:
    get:
      tags: [Pro Tokens]
      summary: List API tokens
      operationId: proListTokens
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: List of API tokens
          content:
            application/json:
              schema:
                type: object
                properties:
                  tokens:
                    type: array
                    items:
                      $ref: "#/components/schemas/Token"
    post:
      tags: [Pro Tokens]
      summary: Create an API token
      operationId: proCreateToken
      description: Requires active subscription status.
      security:
        - ProBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  default: API Token
                  example: My Token
      responses:
        "201":
          description: Token created (secret shown only once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  name:
                    type: string
                  token:
                    type: string
                    description: "Prefixed with tm_pro_. Only returned on creation."
                    example: tm_pro_...

  /pro/api/tokens/{id}:
    delete:
      tags: [Pro Tokens]
      summary: Revoke an API token
      operationId: proDeleteToken
      description: Requires active subscription status.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Token revoked
        "404":
          description: Token not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Pro Sandbox
  # ──────────────────────────────────────────────

  /pro/api/sandbox:
    get:
      tags: [Pro Sandbox]
      summary: Get sandbox status and SMTP credentials
      operationId: proGetSandbox
      description: |
        Returns sandbox configuration including SMTP credentials.
        Point your application's SMTP to the returned host/port, use any username,
        and use your API token as the SMTP password.
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Sandbox info
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"

  /pro/api/sandbox/messages:
    get:
      tags: [Pro Sandbox]
      summary: List sandbox messages
      operationId: proListSandboxMessages
      security:
        - ProBearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
      responses:
        "200":
          description: Paginated message list
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      $ref: "#/components/schemas/EmailSummary"
                  page:
                    type: integer
    delete:
      tags: [Pro Sandbox]
      summary: Purge all sandbox messages
      operationId: proPurgeSandboxMessages
      description: Requires active subscription status. Deletes all messages and resets usage counter.
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Messages purged
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
        "404":
          description: Sandbox not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/sandbox/messages/{id}:
    get:
      tags: [Pro Sandbox]
      summary: Get a sandbox message
      operationId: proGetSandboxMessage
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Full message detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailDetail"
        "404":
          description: Sandbox or message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      tags: [Pro Sandbox]
      summary: Delete a sandbox message
      operationId: proDeleteSandboxMessage
      description: Requires active subscription status.
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Message deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        "404":
          description: Sandbox or message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/sandbox/messages/{id}/source:
    get:
      tags: [Pro Sandbox]
      summary: Download sandbox message EML source
      operationId: proGetSandboxMessageSource
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Raw email source
          content:
            message/rfc822:
              schema:
                type: string
                format: binary
        "404":
          description: Sandbox or message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/sandbox/messages/{id}/attachments/{index}:
    get:
      tags: [Pro Sandbox]
      summary: Download sandbox message attachment
      operationId: proGetSandboxAttachment
      security:
        - ProBearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: index
          in: path
          required: true
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Attachment binary
          headers:
            Content-Disposition:
              schema:
                type: string
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: Message expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ──────────────────────────────────────────────
  # Subscription & Billing
  # ──────────────────────────────────────────────

  /pro/api/subscription/status:
    get:
      tags: [Subscription]
      summary: Get subscription status
      operationId: proGetSubscriptionStatus
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Subscription status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: ["", active, canceled, past_due]
                    description: Empty string if no subscription
                  cancel_mode:
                    type: string
                    enum: ["", refund_now, cancel_end_of_period, scheduled_cancel, canceled, past_due]
                  scheduled_cancel_at:
                    type: object
                    nullable: true
                    properties:
                      action:
                        type: string
                      effective_at:
                        type: string
                        format: date-time

  /pro/api/subscription/cancel:
    post:
      tags: [Subscription]
      summary: Cancel subscription
      operationId: proSubscriptionCancel
      description: |
        Within 14 days of first payment: immediate cancel with full refund.
        After 14 days: cancel at end of billing period.
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Cancellation processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  cancel_mode:
                    type: string
                    enum: [refund_now, canceled_refund_pending, cancel_end_of_period]
        "400":
          description: No subscription or not active
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/subscription/resume:
    post:
      tags: [Subscription]
      summary: Resume a scheduled cancellation
      operationId: proSubscriptionResume
      description: Revokes a scheduled cancellation. Only works if subscription is active with a pending scheduled change.
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Subscription resumed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: resumed
        "400":
          description: No subscription or cannot resume
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/billing/portal:
    post:
      tags: [Subscription]
      summary: Get billing portal URL
      operationId: proGetBillingPortal
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Paddle customer portal URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    format: uri
        "400":
          description: No subscription
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /pro/api/checkout-token:
    post:
      tags: [Subscription]
      summary: Create checkout session
      operationId: proCreateCheckoutToken
      description: Generates a one-time checkout session (valid 30 minutes) for Paddle checkout.
      security:
        - ProBearerAuth: []
      responses:
        "200":
          description: Checkout session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  user_id:
                    type: string
                    format: uuid
                  price_id:
                    type: string
                  sig:
                    type: string
                    description: HMAC-SHA256 signature to prevent price tampering
        "403":
          description: User suspended
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Already subscribed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  parameters:
    AccountId:
      name: account_id
      in: path
      required: true
      schema:
        type: string
        format: uuid

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token obtained from POST /api/accounts (create) or POST /api/token (sign in). Valid for 7 days.
    ProBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Pro JWT token or API token (tm_pro_...) for Pro endpoints.

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: Machine-readable error code

    PoWChallenge:
      type: object
      properties:
        id:
          type: string
        salt:
          type: string
        difficulty:
          type: integer
        expires_at:
          type: integer
          format: int64
          description: Unix timestamp

    PoWSolution:
      type: object
      properties:
        id:
          type: string
        nonce:
          type: string

    AccountInfo:
      type: object
      properties:
        id:
          type: string
          format: uuid
        address:
          type: string
          format: email
        role:
          type: string
          example: user
        quota:
          type: integer
          example: 41943040
        used:
          type: integer
          example: 12345
        created_at:
          type: string
          format: date-time

    EmailSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        sender:
          type: string
          format: email
        from:
          type: string
          nullable: true
          description: "Display name and address, e.g. Name <a@b.com>"
        subject:
          type: string
          nullable: true
        preview_text:
          type: string
          nullable: true
        size:
          type: integer
        is_read:
          type: boolean
        created_at:
          type: string
          format: date-time

    EmailDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        sender:
          type: string
        from:
          type: string
          nullable: true
        subject:
          type: string
          nullable: true
        address:
          type: string
          format: email
        size:
          type: integer
        created_at:
          type: string
          format: date-time
        text_body:
          type: string
          nullable: true
        html_body:
          type: string
          nullable: true
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/Attachment"

    Attachment:
      type: object
      properties:
        index:
          type: integer
        filename:
          type: string
        content_type:
          type: string
        size:
          type: integer

    Account:
      type: object
      properties:
        id:
          type: string
          format: uuid
        address:
          type: string
          format: email
        quota:
          type: integer
          example: 524288000
        used:
          type: integer
        created_at:
          type: string
          format: date-time

    ProUser:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        plan:
          type: string
        role:
          type: string
        status:
          type: string
          enum: [pending, active, suspended]
        max_accounts:
          type: integer
        max_domains:
          type: integer
        account_count:
          type: integer
        domain_count:
          type: integer
        created_at:
          type: string
          format: date-time
        downgraded:
          type: boolean

    Domain:
      type: object
      properties:
        id:
          type: string
          format: uuid
        domain:
          type: string
        default:
          type: boolean
        sort_order:
          type: integer

    ProDomain:
      type: object
      properties:
        id:
          type: string
          format: uuid
        domain:
          type: string
        verify_status:
          type: string
          enum: [pending, verified, failed]
        verify_token:
          type: string
        verified_at:
          type: string
          format: date-time
          nullable: true
        mx_configured:
          type: boolean
        created_at:
          type: string
          format: date-time

    DNSRecord:
      type: object
      properties:
        type:
          type: string
          example: TXT
        host:
          type: string
        value:
          type: string
        priority:
          type: integer
          nullable: true

    Webhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        secret:
          type: string
          description: "Prefixed with whsec_. Only returned on creation."
        status:
          type: string
          enum: [active, failed]
        created_at:
          type: string
          format: date-time

    WebhookDelivery:
      type: object
      properties:
        id:
          type: string
          format: uuid
        event_type:
          type: string
          example: email.received
        event_id:
          type: string
        status_code:
          type: integer
          nullable: true
        error:
          type: string
          nullable: true
        attempt:
          type: integer
        duration_ms:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time

    Token:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        last_used_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        revoked_at:
          type: string
          format: date-time
          nullable: true

    SandboxInfo:
      type: object
      properties:
        enabled:
          type: boolean
        account_id:
          type: string
          format: uuid
          nullable: true
        address:
          type: string
          nullable: true
        smtp_host:
          type: string
        smtp_port:
          type: integer
          example: 25
        auth_method:
          type: string
          example: PLAIN
        username:
          type: string
          description: Any value accepted
        note:
          type: string
          example: Use any API token as the SMTP password
        quota:
          type: integer
          nullable: true
        used:
          type: integer
          nullable: true
