> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sitespy.app/llms.txt
> Use this file to discover all available pages before exploring further.

# List all watches

> Return concise list of available web page change monitors (watches) and basic info



## OpenAPI

````yaml /api-reference/openapi.yaml get /watch
openapi: 3.1.0
info:
  title: Site Spy API
  description: >
    # Site Spy web page monitoring and notifications API


    REST API for managing page watches, group tags, and notifications.


    Site Spy can be driven by this simple REST API — the endpoint pages include
    `curl`, Python, and JavaScript examples to help you get started faster.


    ## Where to find my API key?


    Your API key is under [**Settings →
    API**](https://sitespy.app/dashboard/settings?tab=api) in the Site Spy
    dashboard.

    Simply click the API key to copy it to your clipboard.


    ## Connection URL


    All endpoints live under `https://api.sitespy.app/api/v1`, for example:

    `https://api.sitespy.app/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history`


    ## Authentication


    Almost all API requests require authentication, provided as an **API key**
    in the header of the HTTP request.


    For example: `x-api-key: YOUR_API_KEY`
  version: 0.1.6
  contact:
    name: Site Spy
    url: https://sitespy.app
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
  - url: https://api.sitespy.app/api/v1
    description: Site Spy production
security:
  - ApiKeyAuth: []
tags:
  - name: Watch Management
    description: >
      Core functionality for managing web page monitors. Create, retrieve,
      update, and delete individual watches.

      Each watch represents a single URL being monitored for changes, with
      configurable settings for check intervals,

      notification preferences, and content filtering options.
  - name: Watch History
    description: |
      Get a list of timestamps of all changes detected for a watch.
  - name: Snapshots
    description: >
      Retrieve individual text snapshot of monitored content according to the
      `timestamp`. The text snapshot is the HTML

      to Text at page check time.


      Set the query argument `html` to any value to retrieve the last HTML
      fetched, the system only keeps the last two

      (2) HTML files fetched.


      Use the Watch History API endpoint to get a list of timestamps to pass to
      this query.
  - name: Favicon
    description: >
      Retrieve favicon images associated with monitored web pages. These are
      used in the dashboard interface

      to visually identify different watches in your monitoring list.
  - name: Tag Management
    description: >
      Organize your watches using tags and groups. Tags (also known as Groups)
      allow you to categorize monitors, set group-wide

      notification preferences, and perform bulk operations like mass rechecking
      or status changes across

      multiple related watches.
  - name: Notifications
    description: >
      Configure global notification endpoints that can be used across all your
      watches. Supports various

      notification services including email, Discord, Slack, webhooks, and many
      other popular platforms.

      These settings serve as defaults that can be overridden at the individual
      watch or tag level.


      The notification syntax uses
      [https://github.com/caronc/apprise](https://github.com/caronc/apprise).
  - name: User Settings
    description: >
      Manage user-specific settings such as default recheck intervals. These
      settings are stored per API key

      and apply to all watches created by that user unless overridden at the
      watch level.
  - name: Search
    description: >
      Search and filter your watches by URL patterns, titles, or tags. Useful
      for quickly finding specific

      monitors in large collections or identifying watches that match certain
      criteria.
  - name: Import
    description: >
      Bulk import multiple URLs for monitoring. Accepts plain text lists of URLs
      and can automatically

      apply tags, proxy settings, and other configurations to all imported
      watches simultaneously.
  - name: System Information
    description: >
      Retrieve system status and statistics about your Site Spy instance,
      including total watch

      counts, uptime information, and version details.
  - name: Email Verification
    description: >
      Manage email notification settings for your API key. Each API key can have
      one email address

      associated with it for receiving notifications. The email must be verified
      before notifications

      can be sent to it.
  - name: Telegram Notifications
    description: >
      Connect your Telegram account to receive instant change notifications.

      Generate a connect link, open it in Telegram, tap Start — done. Free for
      all plans.
  - name: Web Push Notifications
    description: >
      Subscribe to browser push notifications for instant alerts when watched
      pages change.

      Uses the Web Push protocol (RFC 8030) with VAPID authentication. Free for
      all plans.
  - name: RSS Feeds
    description: >
      Manage per-user RSS feed access tokens and retrieve feed URLs. Each user
      can generate a

      personal RSS token that provides access to their watches via standard RSS
      readers. Feeds

      are scoped to the user's watches only, ensuring multi-tenant privacy.
  - name: API Key Management
    description: >
      Generate and manage API keys. API keys are used to authenticate all
      requests to the API.

      Each API key is associated with a unique user and their watches.
  - name: Admin Reliability
    description: Master-admin audit and remediation of classified watch failures.
  - name: Feature Flags
    description: >
      Retrieve feature flags for the current API key. Feature flags control
      which features are

      enabled or disabled for specific users.
  - name: Authentication
    description: >
      User account registration and authentication. Users can create accounts
      with email/password

      and receive an API key that links to their account. This enables syncing
      watches across

      the browser extension and web dashboard.
  - name: Plan
    description: >
      Retrieve the current subscription plan and usage for the authenticated API
      key. Plans control

      the maximum number of watches a user can create (Free: 2, Starter: 25,
      Pro: 100).
  - name: Billing
    description: >
      Manage subscriptions via Lemon Squeezy. Create checkout sessions to
      upgrade, access the

      customer billing portal, and receive webhook events from Lemon Squeezy.
  - name: Feedback
    description: >
      Collect product research responses from authenticated users. One-time PMF
      (Sean Ellis)

      survey submission per user.
  - name: Change Feedback
    description: Mark an exact detected change as useful or noisy.
  - name: Screenshots
    description: >
      Retrieve the history of visual snapshots captured for a watch. Use the
      returned timestamps

      with the Visual Diff endpoints to compare any two captures.
  - name: Visual Diff
    description: >
      Retrieve individual screenshots and pixel-level visual comparisons between
      any two captured

      snapshots of a watch, including difference metadata such as changed
      regions.
paths:
  /watch:
    get:
      tags:
        - Watch Management
      summary: List all watches
      description: >-
        Return concise list of available web page change monitors (watches) and
        basic info
      operationId: listWatches
      parameters:
        - name: recheck_all
          in: query
          description: Set to 1 to force recheck of all watches
          schema:
            type: string
            enum:
              - '1'
        - name: tag
          in: query
          description: Tag name to filter results
          schema:
            type: string
      responses:
        '200':
          description: List of watches
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  $ref: '#/components/schemas/Watch'
              example:
                095be615-a8ad-4c33-8e9c-c7612fbf6c9f:
                  uuid: 095be615-a8ad-4c33-8e9c-c7612fbf6c9f
                  url: http://example.com?id={{1+1}} - the raw URL
                  link: >-
                    http://example.com?id=2 - the rendered URL, always use this
                    for listing.
                  title: Example Website Monitor - manually entered title/description
                  page_title: The HTML <title> from the page
                  tags:
                    - 550e8400-e29b-41d4-a716-446655440000
                  paused: false
                  notification_muted: false
                  method: GET
                  fetch_backend: html_requests
                  last_checked: 1640995200
                  last_changed: 1640995200
                7c9e6b8d-f2a1-4e5c-9d3b-8a7f6e4c2d1a:
                  uuid: 7c9e6b8d-f2a1-4e5c-9d3b-8a7f6e4c2d1a
                  url: http://example.com?id={{1+1}} - the raw URL
                  link: >-
                    http://example.com?id=2 - the rendered URL, always use this
                    for listing.
                  title: News Site Tracker - manually entered title/description
                  page_title: The HTML <title> from the page
                  tags:
                    - 330e8400-e29b-41d4-a716-446655440001
                  paused: false
                  notification_muted: true
                  method: GET
                  fetch_backend: html_webdriver
                  last_checked: 1640998800
                  last_changed: 1640995200
      x-codeSamples:
        - lang: curl
          source: |
            curl -X GET "https://api.sitespy.app/api/v1/watch" \
              -H "x-api-key: YOUR_API_KEY"
        - lang: Python
          source: >
            import requests


            headers = {'x-api-key': 'YOUR_API_KEY'}

            response = requests.get('https://api.sitespy.app/api/v1/watch',
            headers=headers)

            print(response.json())
components:
  schemas:
    Watch:
      allOf:
        - $ref: '#/components/schemas/WatchBase'
        - type: object
          properties:
            last_checked:
              type: integer
              description: Unix timestamp of last check
              readOnly: true
            last_changed:
              type: integer
              description: Unix timestamp of last change
              readOnly: true
              x-computed: true
            last_error:
              type:
                - string
                - boolean
                - 'null'
              description: >-
                Last error message (false when no error, string when error
                occurred, null if not checked yet)
              readOnly: true
            failure_category:
              type:
                - string
                - 'null'
              enum:
                - internal_error
                - network_error
                - target_unavailable
                - blocked_or_challenged
                - rate_limited
                - authentication_required
                - not_found
                - empty_content
                - filter_broken
                - browser_failure
                - self_reference
                - notification_failure
                - null
              description: Stable category for the active monitoring failure.
              readOnly: true
            failure_code:
              type:
                - string
                - 'null'
              description: Stable machine-readable code for the active failure.
              readOnly: true
            consecutive_failures:
              type: integer
              minimum: 0
              description: Consecutive failures in the active category.
              readOnly: true
            failure_started_at:
              type: integer
              minimum: 0
              description: Unix timestamp when the active failure lifecycle started.
              readOnly: true
            last_failure_at:
              type: integer
              minimum: 0
              description: Unix timestamp of the most recent failed check.
              readOnly: true
            next_retry_at:
              type: integer
              minimum: 0
              description: >-
                Unix timestamp for the next scheduled recovery attempt; 0 when
                none is scheduled.
              readOnly: true
            recovery_stage:
              type: integer
              minimum: 0
              description: Current category-aware retry/backoff stage.
              readOnly: true
            last_viewed:
              type: integer
              description: >-
                Unix timestamp in seconds of the last time the watch was viewed.
                Setting it to a value higher than `last_changed` in the "Update
                watch" endpoint marks the watch as viewed.
              minimum: 0
            link:
              type: string
              format: uri
              description: >-
                The watch URL rendered in case of any Jinja2 markup, always use
                this for listing.
              readOnly: true
              x-computed: true
            label:
              type: string
              description: >-
                What to call this watch on screen: the stored title, or the
                page's own <title> when the stored title is only the watch's own
                address, or the URL. Resolved server-side so every surface that
                names a watch — the dashboard row, the email subject, the web
                push body, the RSS item — agrees. Use `title` instead when you
                need the raw stored value, for example to pre-fill a rename
                field.
              readOnly: true
              x-computed: true
            page_title:
              type:
                - string
                - 'null'
              description: HTML <title> tag extracted from the page
              readOnly: true
            check_count:
              type: integer
              description: Total number of checks performed
              readOnly: true
            filter_count:
              type: integer
              description: >-
                Number of filters configured on this watch (include filters,
                subtractive selectors and ignore text combined). Distinguishes a
                watch the user deliberately filtered from an untouched one,
                since filters_source defaults to 'manual' either way.
              readOnly: true
              x-computed: true
            fetch_time:
              type: number
              description: Duration of last fetch in seconds
              readOnly: true
            previous_md5:
              type:
                - string
                - boolean
              description: MD5 hash of previous content (false if not set)
              readOnly: true
            previous_md5_before_filters:
              type:
                - string
                - boolean
              description: MD5 hash before filters applied (false if not set)
              readOnly: true
            consecutive_filter_failures:
              type: integer
              description: Counter for consecutive filter match failures
              readOnly: true
            tune_count_today:
              type: integer
              description: >-
                User-initiated filter tunes charged to this watch inside the
                current 24-hour window. Automatic repairs are not counted.
              readOnly: true
            tune_limit:
              type: integer
              description: >-
                How many user-initiated filter tunes this watch's owner may
                spend on it per day, from their plan. Zero or less means
                unlimited.
              readOnly: true
            last_notification_error:
              type:
                - string
                - 'null'
              description: Last notification error message
              readOnly: true
            notification_alert_count:
              type: integer
              description: Number of notifications sent
              readOnly: true
            notifications_per_day:
              type: number
              description: >
                Notifications queued per day this watch actually ran, over the
                last 30 days.

                The denominator is days observed, never the check count — the
                check interval

                comes from the owner's plan, so the same daily volume would
                otherwise score

                sixty times lower on a one-minute watch than on an hourly one.
              readOnly: true
            noisy_notifications_per_day:
              type: number
              description: >
                The daily-volume mark above which this watch counts as loud,
                resolved for its

                owner's plan. Compare `notifications_per_day` against this
                rather than against

                a locally held constant: it is the same mark the server's own
                noise-healing

                gate reads, and it moves with the deployment's configuration.
              readOnly: true
            last_notification_delivery_reason:
              type:
                - string
                - 'null'
              description: >
                Why the most recent delivery attempt succeeded or failed.
                Populated on every

                change-detected event so the dashboard can explain a silent
                watch to the user.

                Possible values include `delivered_via_user_email`,
                `delivered_via_telegram`,

                `delivered_via_user_webhook`, `delivered_via_webpush`,
                `delivered_via_watch_url`,

                `delivered_via_tag_url`, `delivered_via_global_url`,
                `email_unverified`,

                `email_notifications_muted`, `smtp_not_configured`,
                `no_owner_api_key`,

                `no_urls_configured`, `rate_limited`, `web_push_send_failed`,

                `held_search_term_never_seen`, `held_rotating_selector`.
              readOnly: true
            last_notification_delivery_ts:
              type: integer
              description: >-
                Unix timestamp of the most recent delivery attempt (success or
                silent drop)
              readOnly: true
            change_detected_but_not_delivered_count:
              type: integer
              description: >
                Incremented every time a change was detected but no notification
                was queued.

                A non-zero value combined with `notification_alert_count=0`
                means the detection

                engine is working but delivery is blocked — check
                `last_notification_delivery_reason`.
              readOnly: true
            content-type:
              type:
                - string
                - 'null'
              description: Content-Type from last fetch
              readOnly: true
            remote_server_reply:
              type:
                - string
                - 'null'
              description: Server header from last response
              readOnly: true
            browser_steps_last_error_step:
              type:
                - integer
                - 'null'
              description: Last browser step that caused an error
              readOnly: true
            viewed:
              type:
                - integer
                - boolean
              description: >-
                Computed property - true if watch has been viewed, false
                otherwise (deprecated, use last_viewed instead)
              readOnly: true
              x-computed: true
            history_n:
              type: integer
              description: Number of history snapshots available
              readOnly: true
              x-computed: true
            community_published:
              type: boolean
              description: Whether this watch is published to the community library.
              readOnly: true
            community_slug:
              type:
                - string
                - 'null'
              description: URL slug of the published community entry.
              readOnly: true
            community_published_at:
              type:
                - integer
                - 'null'
              description: Unix timestamp the watch was published.
              readOnly: true
            community_clone_count:
              type: integer
              minimum: 0
              description: >-
                Historical count, frozen. One-click cloning of a community entry
                was removed; the field is still returned for entries that were
                cloned while it existed and can no longer increase.
              readOnly: true
            community_cloned_by:
              type: array
              items:
                type: string
              description: >-
                Historical list, frozen. API keys that cloned the entry while
                one-click cloning existed. No new entries are added.
              readOnly: true
            community_category:
              type:
                - string
                - 'null'
              description: Category chosen when publishing.
              readOnly: true
            community_author_name:
              type:
                - string
                - 'null'
              description: Display name shown on the published entry.
              readOnly: true
            consecutive_check_failures:
              type: integer
              minimum: 0
              description: Failed checks in a row, used for owner health alerts.
              readOnly: true
            last_broken_alert_ts:
              type: integer
              minimum: 0
              description: Unix timestamp of the last generic broken-watch alert.
              readOnly: true
            last_health_alert_category:
              type:
                - string
                - 'null'
              description: failure_category last emailed to the owner this episode.
              readOnly: true
            last_health_alert_ts:
              type: integer
              minimum: 0
              description: Unix timestamp of that category-specific health alert.
              readOnly: true
            paused_alert_ts:
              type: integer
              minimum: 0
              description: >-
                Unix timestamp the owner was told about an auto-pause; 0 when
                not yet told.
              readOnly: true
            last_fetch_backend_used:
              type: string
              description: Fetcher that actually ran, e.g. html_webdriver or html_requests.
              readOnly: true
            last_fetch_content_length:
              type: integer
              minimum: 0
              description: >-
                Size in bytes of the last raw HTML body; 0 before the first
                check.
              readOnly: true
            last_fetch_suspicious:
              type:
                - object
                - 'null'
              description: Set when the last fetch looked like a block or challenge page.
              readOnly: true
            filter_signature:
              type: string
              description: >-
                Hash of the filter config that produced the newest stored
                snapshot.
              readOnly: true
            last_filter_empty_at:
              type: integer
              minimum: 0
              description: >-
                Unix timestamp when include_filters last returned nothing; 0
                when healthy.
              readOnly: true
            last_filter_output_length:
              type: integer
              minimum: 0
              description: Size in characters of the latest filter output.
              readOnly: true
            last_filter_selector_matches:
              type: object
              description: Match count per configured selector from the last check.
              readOnly: true
            last_filter_degraded:
              type:
                - string
                - 'null'
              description: >-
                Code for how the filter degraded, e.g. include_no_match; null
                when healthy.
              readOnly: true
            last_filter_degraded_at:
              type: integer
              minimum: 0
              description: Unix timestamp the filter was first seen degraded.
              readOnly: true
            consecutive_filter_degraded:
              type: integer
              minimum: 0
              description: Consecutive checks where the filter was degraded.
              readOnly: true
            filter_heal_in_flight_since:
              type: integer
              minimum: 0
              description: >-
                Unix timestamp an automatic filter repair started; 0 when none
                is running.
              readOnly: true
            last_watch_quality_intervention_id:
              type:
                - string
                - 'null'
              description: >-
                Identifier of the most recent quality intervention on this
                watch.
              readOnly: true
            tune_count:
              type: integer
              minimum: 0
              description: AI filter tunes used inside the current day window.
              readOnly: true
            tune_day_start:
              type: integer
              minimum: 0
              description: Unix timestamp the current tune-quota day started.
              readOnly: true
            tune_source:
              type:
                - string
                - 'null'
              description: Who requested the last tune - user or system.
              readOnly: true
            last_ai_summary_error:
              type: string
              description: Last error from AI summary generation.
              readOnly: true
            notification_window_start:
              type: integer
              minimum: 0
              description: Start of the current hourly notification budget.
              readOnly: true
            notification_window_count:
              type: integer
              minimum: 0
              description: Notifications already sent inside the current hour.
              readOnly: true
            notification_day_start:
              type: integer
              minimum: 0
              description: Start of the current daily notification budget.
              readOnly: true
            notification_day_count:
              type: integer
              minimum: 0
              description: Notifications already sent inside the current day.
              readOnly: true
            notifications_suppressed_count:
              type: integer
              minimum: 0
              description: Changes held back by the budget, reported by the next send.
              readOnly: true
            notifications_suppressed_since:
              type: integer
              minimum: 0
              description: Unix timestamp suppression started; 0 when nothing is held back.
              readOnly: true
            notification_failure_count:
              type: integer
              minimum: 0
              description: Consecutive notification delivery failures.
              readOnly: true
            notification_next_retry_at:
              type: integer
              minimum: 0
              description: >-
                Unix timestamp of the next notification retry; 0 when none is
                scheduled.
              readOnly: true
            notification_recovery_stage:
              type: integer
              minimum: 0
              description: Current notification retry/backoff stage.
              readOnly: true
            checking_now:
              type: boolean
              description: Whether a worker is fetching this watch right now.
              readOnly: true
              x-computed: true
            queued:
              type: boolean
              description: Whether this watch is waiting in the recheck queue.
              readOnly: true
              x-computed: true
            health:
              type: object
              readOnly: true
              x-computed: true
              description: >
                User-facing health summary derived from the persisted failure
                lifecycle.

                Messages are safe to display and do not include stack traces or
                URL secrets.
              properties:
                state:
                  type: string
                  enum:
                    - healthy
                    - never_checked
                    - recovering
                    - blocked
                    - rate_limited
                    - authentication_required
                    - target_unavailable
                    - empty_content
                    - filter_broken
                    - notification_failure
                    - delivery_blocked
                    - auto_paused
                  description: Single-word state for badge display.
                reason:
                  type: string
                  description: Human-readable explanation safe to show in UI.
                action:
                  type:
                    - string
                    - 'null'
                  enum:
                    - retry_fetch
                    - resume
                    - update_url
                    - provide_auth
                    - reconfigure_filter
                    - switch_monitor_type
                    - reduce_frequency
                    - fix_notifications
                    - null
                  description: >-
                    Suggested next action; the dashboard maps this to a CTA
                    button.
                next_step:
                  type: string
                  description: >
                    One concrete move the owner can make, in plain language.
                    Empty while

                    the watch is healthy or has never run.
                pointing_repairs:
                  type:
                    - boolean
                    - 'null'
                  description: >
                    Whether selecting an element on the stored page repairs this
                    watch.

                    `true` when the page arrived intact and only the filter
                    stopped

                    matching, so the picker is the remedy. `false` when no
                    selector

                    written against that page can help — the response was a
                    sign-in wall

                    or a bot check, the page arrived without the items it lists,
                    or the

                    address never returns what was asked for. `null` when there
                    is no

                    verdict for this failure, in which case make no claim either
                    way.
                since_ts:
                  type: integer
                  description: Unix timestamp the current state was first observed.
                category:
                  type:
                    - string
                    - 'null'
                  enum:
                    - internal_error
                    - network_error
                    - target_unavailable
                    - blocked_or_challenged
                    - rate_limited
                    - authentication_required
                    - not_found
                    - empty_content
                    - filter_broken
                    - browser_failure
                    - self_reference
                    - notification_failure
                    - null
                code:
                  type:
                    - string
                    - 'null'
                consecutive_failures:
                  type: integer
                  minimum: 0
                next_retry_at:
                  type: integer
                  minimum: 0
                recovery_stage:
                  type: integer
                  minimum: 0
                auto_paused:
                  type: boolean
                delivery_reason:
                  type:
                    - string
                    - 'null'
                  description: >-
                    Delivery-layer reason when a detected change could not be
                    queued.
                missed_deliveries:
                  type: integer
                  minimum: 0
                  description: >-
                    Detected changes that did not reach a configured
                    notification channel.
    WatchBase:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          description: Unique identifier
          readOnly: true
        date_created:
          type:
            - integer
            - 'null'
          description: Unix timestamp of creation
          readOnly: true
        owner_api_key:
          type:
            - string
            - 'null'
          description: API key that owns this watch (multi-tenant isolation)
          readOnly: true
        url:
          type: string
          format: uri
          description: URL to monitor for changes
          maxLength: 5000
        title:
          type:
            - string
            - 'null'
          description: >-
            Custom title for the web page change monitor (watch), not to be
            confused with page_title
          maxLength: 5000
        tag:
          type: string
          description: Tag UUID to associate with this web page change monitor (watch)
          maxLength: 5000
        tags:
          type: array
          items:
            type: string
          description: Array of tag UUIDs
        paused:
          type: boolean
          description: Whether the web page change monitor (watch) is paused
        paused_by:
          type:
            - string
            - 'null'
          enum:
            - plan_limit
            - reliability
            - null
          description: >
            Non-null when `paused=true` was set automatically. Values:
              - `plan_limit` — owner exceeded their plan's watch cap; auto-deleted 30d after `paused_at` unless user upgrades or un-pauses.
              - `reliability` — persistent classified failure; retained until a user or administrator resumes it.
            Null means the watch was paused manually by the user (no
            auto-delete).
          readOnly: true
        paused_at:
          type: integer
          description: >
            Unix timestamp when auto-pause was applied. Starts the auto-delete
            clock for

            `paused_by != null` watches. 0 when never auto-paused.
          readOnly: true
        notification_muted:
          type: boolean
          description: Whether notifications are muted
        notification_rate_limit_per_hour:
          type:
            - integer
            - 'null'
          minimum: 0
          description: >-
            Per-watch cap on notifications sent per hour. null uses the system
            default, 0 means unlimited.
        notification_rate_limit_per_day:
          type:
            - integer
            - 'null'
          minimum: 0
          description: >-
            Per-watch cap on notifications sent per day. null uses the system
            default, 0 means unlimited.
        method:
          type: string
          enum:
            - GET
            - POST
            - DELETE
            - PUT
          description: HTTP method to use
        fetch_backend:
          type: string
          description: |
            Backend to use for fetching content. Common values:
            - `system` (default) - Use the system-wide default fetcher
            - `html_requests` - Fast requests-based fetcher
            - `html_webdriver` - Browser-based fetcher (Playwright/Puppeteer)
            - `extra_browser_*` - Custom browser configurations (if configured)
            - Plugin-provided fetchers (if installed)
          pattern: ^(system|html_requests|html_hybrid|html_webdriver|extra_browser_.+)$
          default: system
        headers:
          type: object
          additionalProperties:
            type: string
          description: HTTP headers to include in requests
        body:
          type:
            - string
            - 'null'
          description: HTTP request body
          maxLength: 5000
        proxy:
          type:
            - string
            - 'null'
          description: Proxy configuration
          maxLength: 5000
        ignore_status_codes:
          type:
            - boolean
            - 'null'
          description: Ignore HTTP status code errors (boolean or null)
        webdriver_delay:
          type:
            - integer
            - 'null'
          minimum: 0
          maximum: 60
          description: >-
            Seconds to wait after page load before extracting content (browser
            backends only). null uses the system default.
        disable_javascript:
          type: boolean
          default: false
          description: >-
            Render the page in the browser with JavaScript execution disabled.
            Forces a browser backend.
        mobile_view:
          type: boolean
          default: false
          description: >-
            Render the page with a mobile viewport, device pixel ratio, touch
            support and a mobile User-Agent. Forces a browser backend.
        noise_reduction_ads:
          type:
            - boolean
            - 'null'
          description: >-
            Block ads and cookie banners for this watch; null inherits the
            account-level setting of the same name
        webdriver_js_execute_code:
          type:
            - string
            - 'null'
          description: JavaScript code to execute
          maxLength: 5000
        time_between_check:
          type: object
          properties:
            weeks:
              type:
                - integer
                - 'null'
              minimum: 0
              maximum: 52000
            days:
              type:
                - integer
                - 'null'
              minimum: 0
              maximum: 365000
            hours:
              type:
                - integer
                - 'null'
              minimum: 0
              maximum: 8760000
            minutes:
              type:
                - integer
                - 'null'
              minimum: 0
              maximum: 525600000
            seconds:
              type:
                - integer
                - 'null'
              minimum: 0
              maximum: 31536000000
          description: >-
            Time intervals between checks. All fields must be non-negative. At
            least one non-zero value required when not using default settings.
        time_between_check_use_default:
          type: boolean
          default: true
          description: >-
            Whether to use global settings for time between checks - defaults to
            true if not set
        notification_urls:
          type: array
          items:
            type: string
            maxLength: 1000
          maxItems: 100
          description: >-
            Notification URLs for this web page change monitor (watch). Maximum
            100 URLs.
        notification_title:
          type:
            - string
            - 'null'
          description: Custom notification title
          maxLength: 5000
        notification_body:
          type:
            - string
            - 'null'
          description: Custom notification body
          maxLength: 5000
        notification_format:
          type: string
          enum:
            - text
            - html
            - htmlcolor
            - markdown
            - System default
          description: Format for notifications
        track_ldjson_price_data:
          type:
            - boolean
            - 'null'
          description: Whether to track JSON-LD price data
        browser_steps:
          type: array
          items:
            type: object
            properties:
              operation:
                type:
                  - string
                  - 'null'
                maxLength: 5000
              selector:
                type:
                  - string
                  - 'null'
                maxLength: 5000
              optional_value:
                type:
                  - string
                  - 'null'
                maxLength: 5000
            required:
              - operation
              - selector
              - optional_value
            additionalProperties: false
          maxItems: 100
          description: Browser automation steps. Maximum 100 steps allowed.
        processor:
          type: string
          enum:
            - restock_diff
            - text_json_diff
            - image_ssim_diff
          default: text_json_diff
          description: >-
            Optional processor mode to use for change detection.
            `image_ssim_diff` (screenshot monitoring) requires Pro plan.
            Defaults to `text_json_diff` if not specified.
        screenshot_enabled:
          type: boolean
          default: false
          description: >-
            When true, capture and store screenshots alongside text snapshots on
            each check. Requires Pro plan and `fetch_backend` set to a
            browser-based fetcher (e.g. `html_webdriver`).
        include_filters:
          type: array
          items:
            type: string
            maxLength: 5000
          maxItems: 100
          description: >-
            CSS/XPath selectors naming the part of the page this watch follows,
            and the only part that can trigger a notification. In the dashboard,
            the AI chat and the browser extension this is set by pointing at the
            page in the element picker rather than typed; the API accepts it
            directly. A watch with an empty list follows the whole page, which
            means every timestamp, banner and counter on it counts as a change.
            Selectors that the backend cannot compile are dropped on save rather
            than rejected.
        subtractive_selectors:
          type: array
          items:
            type: string
            maxLength: 5000
          maxItems: 100
          description: CSS/XPath selectors to remove content from the page
        ignore_text:
          type: array
          items:
            type: string
            maxLength: 5000
          maxItems: 100
          description: Text patterns to ignore in change detection
        auto_ignore_text:
          type: array
          items:
            type: string
            maxLength: 5000
          maxItems: 20
          readOnly: true
          description: >-
            Reversible noise patterns applied by automatic watch-quality
            remediation
        auto_noise_reduction_enabled:
          type:
            - boolean
            - 'null'
          description: >-
            Enable automatic noise reduction for this watch; null inherits the
            account smart-filter setting
        trigger_text:
          type: array
          items:
            type: string
            maxLength: 5000
          maxItems: 100
          description: Text/regex patterns that must be present to trigger a change
        text_should_not_be_present:
          type: array
          items:
            type: string
            maxLength: 5000
          maxItems: 100
          description: Text that should NOT be present (triggers alert if found)
        alert_on_state_change:
          type:
            - boolean
            - 'null'
          description: >-
            Notify on the check where text_should_not_be_present stops matching,
            and stay silent otherwise, instead of notifying on every page diff
            while it does not match. Null derives it: a watch with
            text_should_not_be_present is waiting for that message to clear
            unless its stated intent is a generic "any change".
        extract_text:
          type: array
          items:
            type: string
            maxLength: 5000
          maxItems: 100
          description: Regex patterns to extract specific text after filtering
        trim_text_whitespace:
          type: boolean
          default: false
          description: Strip leading/trailing whitespace from text
        sort_text_alphabetically:
          type: boolean
          default: false
          description: Sort lines alphabetically before comparison
        remove_duplicate_lines:
          type: boolean
          default: false
          description: Remove duplicate lines from content
        check_unique_lines:
          type: boolean
          default: false
          description: Compare against all history for unique lines
        strip_ignored_lines:
          type:
            - boolean
            - 'null'
          description: Remove lines matching ignore patterns
        filter_text_added:
          type: boolean
          default: true
          description: Include added text in change detection
        filter_text_removed:
          type: boolean
          default: true
          description: Include removed text in change detection
        filter_text_replaced:
          type: boolean
          default: true
          description: Include replaced text in change detection
        in_stock_only:
          type: boolean
          default: true
          description: Only trigger on in-stock transitions (restock_diff processor)
        follow_price_changes:
          type: boolean
          default: true
          description: Monitor and track price changes (restock_diff processor)
        price_change_threshold_percent:
          type:
            - number
            - 'null'
          description: Minimum price change percentage to trigger notification
        has_ldjson_price_data:
          type:
            - boolean
            - 'null'
          description: Whether page has LD-JSON price data (auto-detected)
          readOnly: true
        notification_screenshot:
          type: boolean
          default: false
          description: >-
            Include screenshot in notifications (if supported by notification
            URL)
        filter_failure_notification_send:
          type: boolean
          default: true
          description: Send notification when filters fail to match content
        use_page_title_in_list:
          type:
            - boolean
            - 'null'
          description: Display page title in watch list (null = use system default)
        history_snapshot_max_length:
          type:
            - integer
            - 'null'
          minimum: 1
          maximum: 1000
          description: >-
            Maximum number of history snapshots to keep (null = use system
            default)
        time_schedule_limit:
          type: object
          description: Weekly schedule limiting when checks can run
          properties:
            enabled:
              type: boolean
              default: false
            monday:
              $ref: '#/components/schemas/DaySchedule'
            tuesday:
              $ref: '#/components/schemas/DaySchedule'
            wednesday:
              $ref: '#/components/schemas/DaySchedule'
            thursday:
              $ref: '#/components/schemas/DaySchedule'
            friday:
              $ref: '#/components/schemas/DaySchedule'
            saturday:
              $ref: '#/components/schemas/DaySchedule'
            sunday:
              $ref: '#/components/schemas/DaySchedule'
        conditions:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
                description: Field to check (e.g., 'page_filtered_text', 'page_title')
              operator:
                type: string
                description: >-
                  Comparison operator (e.g., 'contains_regex', 'equals',
                  'not_equals')
              value:
                type: string
                description: Value to compare against
            required:
              - field
              - operator
              - value
          maxItems: 100
          description: >-
            Array of condition rules for change detection logic (empty array
            when not set)
        conditions_match_logic:
          type: string
          enum:
            - ALL
            - ANY
          default: ALL
          description: >-
            Logic operator - ALL (match all conditions) or ANY (match any
            condition)
        ai_alert_condition:
          type:
            - string
            - 'null'
          description: >-
            Plain-English condition for AI smart alerts (Pro+ only). When set,
            AI evaluates each change against this condition and suppresses
            notifications that don't match. Example - "Only notify me if the
            price drops below $50".
          maxLength: 500
        last_ai_summary:
          type:
            - string
            - 'null'
          description: >-
            AI-generated plain-English summary of the most recent change (Pro+
            only, read-only — generated automatically when a change is
            detected).
          readOnly: true
        last_ai_summary_from:
          type: integer
          minimum: 0
          description: >
            The older of the two snapshot timestamps `last_ai_summary` was
            written from. The summary

            describes one change, so a client rendering it beside a diff must
            check that this pair is

            the pair being shown — otherwise it captions one change with another
            change's summary.

            0 means the pair was not recorded (a summary written before this
            field existed), in which

            case the summary is only trustworthy against the watch's newest
            pair.
          readOnly: true
        last_ai_summary_to:
          type: integer
          minimum: 0
          description: >-
            The newer of the two snapshot timestamps `last_ai_summary` was
            written from. 0 when not recorded.
          readOnly: true
        last_filter_degraded_alert_ts:
          type: integer
          minimum: 0
          description: When the owner was last told this watch's filters had degraded.
          readOnly: true
        state_blocked:
          type:
            - boolean
            - 'null'
          description: >-
            The state gate's verdict at the previous check; null before the
            first one.
          readOnly: true
        state_last_transition_at:
          type: integer
          minimum: 0
          description: When the state gate last changed its verdict.
          readOnly: true
        state_waiting_alert_ts:
          type: integer
          minimum: 0
          description: When the owner was last sent a "still waiting" note.
          readOnly: true
        track_what:
          type:
            - string
            - 'null'
          description: >-
            User's monitoring intent, e.g. "price changes", "new job listings".
            Set by the add-watch form or AI chat when the user describes what
            they want to track.
          maxLength: 500
        ai_tuning_started_at:
          type: integer
          description: >-
            Unix timestamp set while an automatic filter repair is running for
            this watch, and zero when it is not. Lets the dashboard show the
            work as watch state that survives a page refresh.
          default: 0
        filters_source:
          type: string
          description: >-
            Who chose this watch's include filter - 'manual' for a person,
            'smart' for the removed smart-configure generator. It decides what
            happens when the include filter stops matching: a person's selector
            raises an error, a machine's silently falls back to the whole page.
            Nothing writes 'smart' any more, and watches configured before it
            was removed still carry the value with their filters honoured
            unchanged - but an edit that changes include_filters now records
            'manual', since whoever made that edit chose the selector. Send the
            field explicitly to override that.
          enum:
            - manual
            - smart
          default: manual
    DaySchedule:
      type: object
      properties:
        enabled:
          type: boolean
          default: true
        start_time:
          type: string
          pattern: ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$
          default: '00:00'
          description: Start time in HH:MM format
        duration:
          type: object
          properties:
            hours:
              type: string
              pattern: ^[0-9]+$
              default: '24'
            minutes:
              type: string
              pattern: ^[0-9]+$
              default: '00'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        API key for authentication. You can find your API key in the Site Spy
        dashboard under Settings → API.

````