openapi: 3.1.0
info:
  title: Huang Temp Mail API
  version: 1.0.0
  description: |
    REST API for Huang Temp Mail — temporary email for QA/staging/development
    on domains you own.

    **Acceptable use:** Do not use this API to obtain OTPs, credentials, or
    verification codes from third-party services.

    Security is designed with layered controls (authn, authz, quotas, audit, masking).
servers:
  - url: /api
    description: Relative to application origin
tags:
  - name: Auth
  - name: Inboxes
  - name: Messages
  - name: Quotas
  - name: API Keys
  - name: Webhooks
  - name: Admin
  - name: Inbound
security:
  - sessionCookie: []
  - bearerApiKey: []
paths:
  /health:
    get:
      tags: [Auth]
      security: []
      summary: Health check
      responses:
        "200":
          description: OK
  /auth/login:
    post:
      tags: [Auth]
      security: []
      summary: Login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
      responses:
        "200":
          description: Logged in
        "401":
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /auth/logout:
    post:
      tags: [Auth]
      summary: Logout
      responses:
        "200":
          description: Logged out
  /auth/me:
    get:
      tags: [Auth]
      summary: Current user
      responses:
        "200":
          description: User profile + quota
  /v1/inboxes:
    get:
      tags: [Inboxes]
      summary: List inboxes
      parameters:
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: pageSize
          schema: { type: integer, default: 20 }
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: status
          schema: { type: string }
      responses:
        "200":
          description: Paginated inbox list
    post:
      tags: [Inboxes]
      summary: Create inbox
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateInbox"
      responses:
        "201":
          description: Created
  /v1/inboxes/{id}:
    get:
      tags: [Inboxes]
      summary: Get inbox
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Inbox detail
    delete:
      tags: [Inboxes]
      summary: Delete inbox
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Deleted
  /v1/inboxes/{id}/extend:
    post:
      tags: [Inboxes]
      summary: Extend inbox TTL
      parameters:
        - $ref: "#/components/parameters/Id"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [amount, unit]
              properties:
                amount: { type: integer }
                unit: { type: string, enum: [minutes, hours, days] }
      responses:
        "200":
          description: Extended
  /v1/inboxes/{id}/messages:
    get:
      tags: [Messages]
      summary: List messages in inbox
      parameters:
        - $ref: "#/components/parameters/Id"
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: since
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: Message list
  /v1/messages/{id}:
    get:
      tags: [Messages]
      summary: Get message (codes masked by default)
      parameters:
        - $ref: "#/components/parameters/Id"
        - in: query
          name: unmask
          schema: { type: string, enum: ["0", "1"] }
        - in: query
          name: images
          schema: { type: string, enum: ["0", "1"] }
      responses:
        "200":
          description: Message detail
    delete:
      tags: [Messages]
      summary: Delete message
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Deleted
  /v1/quotas/me:
    get:
      tags: [Quotas]
      summary: Current user quota usage
      responses:
        "200":
          description: Quota snapshot
  /v1/api-keys:
    get:
      tags: [API Keys]
      summary: List API keys (prefix only)
      responses:
        "200":
          description: Keys
    post:
      tags: [API Keys]
      summary: Create API key (plaintext returned once)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                scopes:
                  type: array
                  items:
                    type: string
                    enum:
                      - inbox:read
                      - inbox:create
                      - inbox:delete
                      - message:read
                      - message:delete
                      - webhook:manage
      responses:
        "201":
          description: Created with plaintext key
  /v1/webhooks:
    get:
      tags: [Webhooks]
      summary: List webhooks
      responses:
        "200":
          description: Webhooks
    post:
      tags: [Webhooks]
      summary: Create webhook
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name, url]
              properties:
                name: { type: string }
                url: { type: string, format: uri }
                events:
                  type: array
                  items: { type: string }
      responses:
        "201":
          description: Created with secret once
  /inbound/email:
    post:
      tags: [Inbound]
      security:
        - emailWebhookSecret: []
      summary: Inbound email webhook
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InboundEmail"
      responses:
        "201":
          description: Accepted
  /admin/overview:
    get:
      tags: [Admin]
      summary: Dashboard overview stats
      responses:
        "200":
          description: Stats
components:
  securitySchemes:
    sessionCookie:
      type: apiKey
      in: cookie
      name: htm_session
    bearerApiKey:
      type: http
      scheme: bearer
      bearerFormat: htm_
      description: API key shown once at creation. Stored hashed server-side.
    emailWebhookSecret:
      type: apiKey
      in: header
      name: X-Email-Webhook-Secret
  parameters:
    Id:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
  schemas:
    CreateInbox:
      type: object
      required: [domainId]
      properties:
        domainId: { type: string, format: uuid }
        localPart: { type: string }
        permanent: { type: boolean }
        durationAmount: { type: integer }
        durationUnit: { type: string, enum: [minutes, hours, days] }
        pin: { type: string }
        retentionMinutes: { type: integer }
    InboundEmail:
      type: object
      required: [to]
      properties:
        to: { type: string }
        from: { type: string }
        fromName: { type: string }
        subject: { type: string }
        text: { type: string }
        html: { type: string }
        messageId: { type: string }
        headers:
          type: object
          additionalProperties: { type: string }
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
            requestId: { type: string }
            timestamp: { type: string, format: date-time }
  examples:
    curlCreateInbox:
      summary: cURL create inbox
      value: |
        curl -X POST "$APP_URL/api/v1/inboxes" \
          -H "Authorization: Bearer htm_xxx" \
          -H "Content-Type: application/json" \
          -d '{"domainId":"<uuid>","localPart":"qa-test","durationAmount":24,"durationUnit":"hours"}'
    jsListMessages:
      summary: JavaScript list messages
      value: |
        const res = await fetch(`${APP_URL}/api/v1/inboxes/${inboxId}/messages`, {
          headers: { Authorization: `Bearer ${apiKey}` },
        });
        const json = await res.json();
    pyGetMessage:
      summary: Python get message
      value: |
        import requests
        r = requests.get(
          f"{APP_URL}/api/v1/messages/{message_id}",
          headers={"Authorization": f"Bearer {api_key}"},
        )
        print(r.json())
    webhookVerify:
      summary: Webhook HMAC verification
      value: |
        import hmac, hashlib
        expected = hmac.new(secret.encode(), body.encode(), hashlib.sha256).hexdigest()
        assert hmac.compare_digest(expected, signature_header)
