openapi: 3.1.0
info:
  title: NameSniper API
  version: "1.0.0"
  description: >
    Programmatic access to NameSniper: check brand name, domain, and social
    handle availability across 16 platforms, generate AI name ideas, screen
    trademarks, suggest alternatives, and monitor handles for when they drop.


    All endpoints require an API key (Pro or Business plan). Authenticate with
    an `Authorization: Bearer ns_sk_...` header. Generate a key at
    https://namesniper.pro/dashboard/api-keys.


    Every response uses the envelope `{ "success": boolean, "data"?: object,
    "meta"?: object, "error"?: string }`.
  contact:
    name: NameSniper
    url: https://namesniper.pro
  license:
    name: Proprietary
    url: https://namesniper.pro/terms
servers:
  - url: https://namesniper.pro
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Check
    description: Availability checks for names, domains, and social handles
  - name: Generate
    description: AI name generation and alternatives
  - name: Trademark
    description: USPTO trademark screening
  - name: Monitoring
    description: Watch handles and receive notifications
  - name: Webhooks
    description: Manage webhook endpoints for monitoring events
  - name: Account
    description: Plan and usage information

paths:
  /api/v1/check:
    post:
      tags: [Check]
      summary: All-in-one check
      description: Check a name across domains and social platforms, with optional brand score and trademark screening.
      operationId: checkAll
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  description: Brand name or handle to check.
                  example: acmerobotics
                platforms:
                  type: array
                  items: { $ref: '#/components/schemas/PlatformName' }
                  description: Social platforms to check. Defaults to all 16.
                domains:
                  type: array
                  items: { $ref: '#/components/schemas/TLD' }
                  description: TLDs to check. Defaults to com, org, net, app, dev, tech, io, co, ai.
                brandScore:
                  type: boolean
                  default: true
                  description: Include brand score analysis.
                trademark:
                  type: boolean
                  default: true
                  description: Include USPTO trademark screening.
      responses:
        '200':
          description: Combined results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      name: { type: string }
                      domains: { $ref: '#/components/schemas/DomainCheckResponse' }
                      social: { $ref: '#/components/schemas/UsernameCheckResponse' }
                      brandScore: { $ref: '#/components/schemas/BrandScore' }
                      trademark: { $ref: '#/components/schemas/TrademarkCheckResponse' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/check/social:
    post:
      tags: [Check]
      summary: Check social handles
      operationId: checkSocial
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  example: acmerobotics
                platforms:
                  type: array
                  items: { $ref: '#/components/schemas/PlatformName' }
                  description: Defaults to all 16 platforms.
      responses:
        '200':
          description: Social availability results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    allOf:
                      - type: object
                        properties:
                          name: { type: string }
                      - $ref: '#/components/schemas/UsernameCheckResponse'
                  meta: { $ref: '#/components/schemas/Meta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/check/domains:
    post:
      tags: [Check]
      summary: Check domain availability
      operationId: checkDomains
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  description: Domain name without TLD.
                  example: acmerobotics
                tlds:
                  type: array
                  items: { $ref: '#/components/schemas/TLD' }
                  description: TLDs to check. Defaults to com, org, net, app, dev, tech, io, co, ai.
      responses:
        '200':
          description: Domain availability results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    allOf:
                      - type: object
                        properties:
                          name: { type: string }
                      - $ref: '#/components/schemas/DomainCheckResponse'
                  meta: { $ref: '#/components/schemas/Meta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/brand-score:
    post:
      tags: [Generate]
      summary: Brand name analysis
      operationId: brandScore
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, example: acmerobotics }
      responses:
        '200':
          description: Brand score.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    allOf:
                      - type: object
                        properties:
                          name: { type: string }
                      - $ref: '#/components/schemas/BrandScore'
                  meta: { $ref: '#/components/schemas/Meta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/alternatives:
    post:
      tags: [Generate]
      summary: Brand name alternatives
      operationId: alternatives
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, example: acmerobotics }
                count:
                  type: integer
                  default: 8
                  description: Number of alternatives to return.
      responses:
        '200':
          description: Alternative name suggestions.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      name: { type: string }
                      alternatives:
                        type: array
                        items: { $ref: '#/components/schemas/BrandAlternative' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/generate:
    post:
      tags: [Generate]
      summary: AI name generation
      operationId: generate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [description]
              properties:
                description:
                  type: string
                  minLength: 3
                  description: Business or project description.
                  example: A robotics startup building warehouse automation arms
                count:
                  type: integer
                  default: 10
                style:
                  $ref: '#/components/schemas/GenerationStyle'
      responses:
        '200':
          description: Generated names with .com availability.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      description: { type: string }
                      names:
                        type: array
                        items: { $ref: '#/components/schemas/GeneratedName' }
                      generatedAt: { type: string, format: date-time }
                      cached: { type: boolean }
                  meta: { $ref: '#/components/schemas/Meta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/trademark:
    post:
      tags: [Trademark]
      summary: Trademark screening
      operationId: trademark
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, example: acmerobotics }
                includeVariations:
                  type: boolean
                  default: false
                classes:
                  type: array
                  items: { type: string }
                  description: International trademark classes to focus on.
      responses:
        '200':
          description: Trademark screening result.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data: { $ref: '#/components/schemas/TrademarkCheckResponse' }
                  meta: { $ref: '#/components/schemas/Meta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/usage:
    get:
      tags: [Account]
      summary: Plan and usage
      operationId: usage
      responses:
        '200':
          description: Current plan limits and usage.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      plan: { $ref: '#/components/schemas/PlanName' }
                      limits:
                        type: object
                        properties:
                          checksPerDay:
                            oneOf:
                              - type: integer
                              - type: string
                                enum: [unlimited]
                          aiGenerationsPerMonth:
                            oneOf:
                              - type: integer
                              - type: string
                                enum: [unlimited]
                          apiRequestsPerDay: { type: integer }
                      usage:
                        type: object
                        properties:
                          checksToday: { type: integer }
                          apiRequestsToday: { type: integer }
                          aiGenerationsThisMonth: { type: integer }
                      rateLimit:
                        type: [object, "null"]
                        properties:
                          perMinute: { type: integer }
                          perHour: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/watch:
    get:
      tags: [Monitoring]
      summary: List watched handles
      operationId: listWatches
      responses:
        '200':
          description: All watches for the authenticated user.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      watches:
                        type: array
                        items: { $ref: '#/components/schemas/Watch' }
                  meta:
                    type: object
                    properties:
                      slots: { $ref: '#/components/schemas/Slots' }
                      total: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }
    post:
      tags: [Monitoring]
      summary: Create watches
      description: Start monitoring a handle across one or more platforms. Instagram, TikTok, and X require a paid plan.
      operationId: createWatch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, platforms]
              properties:
                username: { type: string, example: acmerobotics }
                platforms:
                  type: array
                  minItems: 1
                  items: { $ref: '#/components/schemas/PlatformName' }
      responses:
        '200':
          description: Created watches (duplicates are skipped).
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      created:
                        type: array
                        items: { $ref: '#/components/schemas/Watch' }
                      skipped:
                        type: array
                        items: { $ref: '#/components/schemas/PlatformName' }
                  meta:
                    type: object
                    properties:
                      slots: { $ref: '#/components/schemas/Slots' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/watch/{id}:
    parameters:
      - $ref: '#/components/parameters/IdPath'
    patch:
      tags: [Monitoring]
      summary: Pause or resume a watch
      operationId: updateWatch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status:
                  type: string
                  enum: [watching, paused]
      responses:
        '200':
          description: Updated watch.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data: { $ref: '#/components/schemas/Watch' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Monitoring]
      summary: Remove a watch
      operationId: deleteWatch
      responses:
        '200':
          description: Watch removed.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DeleteResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }

  /api/v1/webhooks:
    get:
      tags: [Webhooks]
      summary: List webhook endpoints
      operationId: listWebhooks
      responses:
        '200':
          description: All webhook endpoints.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      webhooks:
                        type: array
                        items: { $ref: '#/components/schemas/Webhook' }
        '401': { $ref: '#/components/responses/Unauthorized' }
    post:
      tags: [Webhooks]
      summary: Create a webhook endpoint
      description: Requires Pro or Business plan. The signing secret is returned only at creation.
      operationId: createWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                  example: https://example.com/hooks/namesniper
                events:
                  type: array
                  minItems: 1
                  items: { $ref: '#/components/schemas/WebhookEvent' }
      responses:
        '200':
          description: Created webhook (includes the signing secret).
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data: { $ref: '#/components/schemas/WebhookWithSecret' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/webhooks/{id}:
    parameters:
      - $ref: '#/components/parameters/IdPath'
    patch:
      tags: [Webhooks]
      summary: Update a webhook endpoint
      operationId: updateWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url: { type: string, format: uri }
                events:
                  type: array
                  minItems: 1
                  items: { $ref: '#/components/schemas/WebhookEvent' }
                active: { type: boolean }
      responses:
        '200':
          description: Updated webhook.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data: { $ref: '#/components/schemas/Webhook' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Webhooks]
      summary: Remove a webhook endpoint
      operationId: deleteWebhook
      responses:
        '200':
          description: Webhook removed.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DeleteResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }

  /api/v1/notifications:
    get:
      tags: [Monitoring]
      summary: List in-app notifications
      operationId: listNotifications
      parameters:
        - name: unread
          in: query
          schema: { type: boolean }
          description: When true, only return unread notifications.
        - name: limit
          in: query
          schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
        - name: offset
          in: query
          schema: { type: integer, default: 0, minimum: 0 }
      responses:
        '200':
          description: Paginated notifications.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      notifications:
                        type: array
                        items: { $ref: '#/components/schemas/Notification' }
                      pagination:
                        type: object
                        properties:
                          total: { type: integer }
                          limit: { type: integer }
                          offset: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/notifications/{id}:
    parameters:
      - $ref: '#/components/parameters/IdPath'
    patch:
      tags: [Monitoring]
      summary: Mark a notification as read
      operationId: markNotificationRead
      responses:
        '200':
          description: Updated notification.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data: { $ref: '#/components/schemas/Notification' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }

  /api/v1/notifications/read-all:
    post:
      tags: [Monitoring]
      summary: Mark all notifications as read
      operationId: markAllNotificationsRead
      responses:
        '200':
          description: Count of notifications marked read.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { const: true }
                  data:
                    type: object
                    properties:
                      markedRead: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ns_sk
      description: "API key issued from the dashboard, sent as `Authorization: Bearer ns_sk_...`."

  parameters:
    IdPath:
      name: id
      in: path
      required: true
      schema: { type: string }

  responses:
    BadRequest:
      description: Invalid request (missing field, bad JSON, or invalid value).
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
    Unauthorized:
      description: Missing or invalid API key. When x402 is enabled, a 402 may be returned instead.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
    Forbidden:
      description: Authenticated but not permitted (revoked key or a feature your plan does not include).
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
    NotFound:
      description: Resource not found or not owned by the caller.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
    RateLimited:
      description: Rate limit or daily quota exceeded. Inspect the `Retry-After` header.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }

  schemas:
    ErrorResponse:
      type: object
      properties:
        success: { const: false }
        error: { type: string }
      required: [success, error]

    Meta:
      type: object
      description: Standard response metadata.
      properties:
        plan: { $ref: '#/components/schemas/PlanName' }
        duration:
          type: integer
          description: Server processing time in milliseconds.
        timestamp: { type: string, format: date-time }

    Slots:
      type: object
      properties:
        used: { type: integer }
        limit: { type: integer }

    DeleteResult:
      type: object
      properties:
        success: { const: true }
        data:
          type: object
          properties:
            deleted: { const: true }
            id: { type: string }

    PlatformName:
      type: string
      enum:
        - youtube
        - reddit
        - tiktok
        - instagram
        - twitter
        - github
        - npm
        - twitch
        - threads
        - bluesky
        - pinterest
        - snapchat
        - kick
        - substack
        - linktree
        - roblox

    TLD:
      type: string
      enum:
        - com
        - org
        - net
        - app
        - dev
        - tech
        - pro
        - biz
        - fun
        - online
        - site
        - digital
        - studio
        - store
        - shop
        - buy
        - info
        - me
        - cc
        - tv
        - ai
        - io
        - co

    GenerationStyle:
      type: string
      default: modern
      enum:
        - modern
        - classic
        - tech
        - creative
        - professional
        - playful
        - abstract
        - luxury
        - minimalist

    PlanName:
      type: string
      enum: [guest, free, day_pass, pro, business]

    WebhookEvent:
      type: string
      enum: [handle_available, handle_change]

    CheckResult:
      type: object
      description: Availability of a single handle on one platform.
      properties:
        platform: { $ref: '#/components/schemas/PlatformName' }
        username: { type: string }
        available: { type: boolean }
        confidence:
          type: number
          description: Confidence in the verdict, 0 to 1.
        error: { type: string }
        invalid:
          type: boolean
          description: Username violates the platform's format rules and can never be registered there.
        loginWallDetected: { type: boolean }
        botDetected: { type: boolean }
        checkedAt: { type: string, format: date-time }
        url: { type: string, format: uri }
      required: [platform, username, available, confidence, checkedAt, url]

    CheckSummary:
      type: object
      properties:
        totalChecks: { type: integer }
        available: { type: integer }
        taken: { type: integer }
        errors: { type: integer }

    UsernameCheckResponse:
      type: object
      properties:
        username: { type: string }
        results:
          type: array
          items: { $ref: '#/components/schemas/CheckResult' }
        summary: { $ref: '#/components/schemas/CheckSummary' }

    DomainResult:
      type: object
      properties:
        domain: { $ref: '#/components/schemas/TLD' }
        domainName: { type: string }
        available: { type: boolean }
        status:
          type: string
          enum: [available, taken, uncertain]
        checkedAt: { type: string, format: date-time }
        confidence:
          type: number
          description: Confidence level, 0 to 100.
        error: { type: string }
        tier:
          type: integer
          enum: [1, 2, 3]
          description: Which checking tier produced the verdict.
        checkTime:
          type: integer
          description: Time taken in milliseconds.
      required: [domain, domainName, available, checkedAt]

    DomainCheckResponse:
      type: object
      properties:
        domainName: { type: string }
        results:
          type: array
          items: { $ref: '#/components/schemas/DomainResult' }
        summary: { $ref: '#/components/schemas/CheckSummary' }

    GeneratedName:
      type: object
      properties:
        name: { type: string }
        explanation: { type: string }
        tags:
          type: array
          items: { type: string }
        confidence: { type: number }
        comAvailable:
          type: boolean
          description: Whether the .com is available (DNS check).
        source:
          type: string
          enum: [ai, fallback]
          description: "'fallback' means the AI was unavailable and a mock was returned; not charged against quota."
      required: [name, explanation, tags, confidence]

    BrandAlternative:
      type: object
      properties:
        name: { type: string }
        pattern: { type: string }
        description: { type: string }
        brandScore:
          type: integer
          description: 0 to 100, higher is more brandable.
        type:
          type: string
          enum: [prefix, suffix, variation, compound]
      required: [name, pattern, description, brandScore, type]

    SubScore:
      type: object
      properties:
        score: { type: integer }
        factors:
          type: object
          additionalProperties: true
      required: [score]

    BrandScore:
      type: object
      properties:
        overall: { type: integer }
        memorability: { $ref: '#/components/schemas/SubScore' }
        seo: { $ref: '#/components/schemas/SubScore' }
        professional: { $ref: '#/components/schemas/SubScore' }
        recommendations:
          type: array
          items: { type: string }
      required: [overall]

    TrademarkConflict:
      type: object
      properties:
        mark: { type: string }
        similarity: { type: integer }
        owner: { type: string }
        classes:
          type: array
          items: { type: string }
        status: { type: string }
        riskLevel: { $ref: '#/components/schemas/RiskLevel' }
        serialNumber: { type: string }
        goodsAndServices: { type: string }
        filingStatus: { type: string }

    RiskLevel:
      type: string
      enum: [low, medium, high]

    TrademarkResult:
      type: object
      properties:
        name: { type: string }
        status:
          type: string
          enum: [no-matches, similar-found, unknown]
        confidence: { type: integer }
        riskLevel: { $ref: '#/components/schemas/RiskLevel' }
        details:
          type: object
          additionalProperties: true
        conflicts:
          type: array
          items: { $ref: '#/components/schemas/TrademarkConflict' }
        checkedAt: { type: string, format: date-time }
        source:
          type: string
          enum: [uspto-search, similarity-analysis]
        isAutomatedScreening: { const: true }
        disclaimer: { type: string }

    TrademarkCheckResponse:
      type: object
      properties:
        name: { type: string }
        result: { $ref: '#/components/schemas/TrademarkResult' }
        variations:
          type: array
          items: { $ref: '#/components/schemas/TrademarkResult' }
        summary:
          type: object
          properties:
            overallRisk: { $ref: '#/components/schemas/RiskLevel' }
            recommendAction: { type: string }
            conflictCount: { type: integer }
        disclaimer: { type: string }
        isAutomatedScreening: { const: true }

    Watch:
      type: object
      properties:
        id: { type: string }
        username: { type: string }
        platform: { $ref: '#/components/schemas/PlatformName' }
        status: { type: string, description: "Current watch state, e.g. watching or paused." }
        lastStatus:
          type: [string, "null"]
          description: Last observed availability status.
        lastCheckedAt: { type: [string, "null"], format: date-time }
        lastChangedAt: { type: [string, "null"], format: date-time }
        checkCount: { type: integer }
        createdAt: { type: string, format: date-time }
      required: [id, username, platform, status, createdAt]

    Webhook:
      type: object
      properties:
        id: { type: string }
        url: { type: string, format: uri }
        events:
          type: array
          items: { $ref: '#/components/schemas/WebhookEvent' }
        active: { type: boolean }
        failCount: { type: integer }
        lastError: { type: [string, "null"] }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    WebhookWithSecret:
      type: object
      description: Returned only at creation. The secret signs the HMAC-SHA256 payload header and is never shown again.
      properties:
        id: { type: string }
        url: { type: string, format: uri }
        events:
          type: array
          items: { $ref: '#/components/schemas/WebhookEvent' }
        active: { type: boolean }
        secret: { type: string }
        createdAt: { type: string, format: date-time }

    Notification:
      type: object
      properties:
        id: { type: string }
        type: { type: string }
        title: { type: string }
        body: { type: string }
        read: { type: boolean }
        metadata:
          type: [object, "null"]
          additionalProperties: true
        watchId: { type: [string, "null"] }
        createdAt: { type: string, format: date-time }
