components:
  schemas:
    CreateTag:
      allOf:
      - $ref: '#/components/schemas/Tag'
      - required:
        - title
        type: object
    CreateWatch:
      allOf:
      - $ref: '#/components/schemas/WatchBase'
      - required:
        - url
        type: object
    DaySchedule:
      properties:
        duration:
          properties:
            hours:
              default: '24'
              pattern: ^[0-9]+$
              type: string
            minutes:
              default: '00'
              pattern: ^[0-9]+$
              type: string
          type: object
        enabled:
          default: true
          type: boolean
        start_time:
          default: 00:00
          description: Start time in HH:MM format
          pattern: ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$
          type: string
      type: object
    EmailNotifications:
      description: User email notification preferences
      properties:
        email:
          description: Email address to send notifications to
          format: email
          type: string
        enabled:
          description: Whether email notifications are enabled for this user
          type: boolean
      type: object
    Error:
      properties:
        message:
          description: Error message
          type: string
      type: object
    NotificationUrls:
      properties:
        notification_urls:
          description: List of notification URLs
          items:
            format: uri
            type: string
          type: array
      required:
      - notification_urls
      type: object
    SearchResult:
      properties:
        watches:
          additionalProperties:
            $ref: '#/components/schemas/Watch'
          description: Dictionary of matching web page change monitors (watches) keyed
            by UUID
          type: object
      type: object
    SystemInfo:
      properties:
        tag_count:
          description: Total number of tags
          type: integer
        uptime:
          description: System uptime
          type: string
        version:
          description: Application version
          type: string
        watch_count:
          description: Total number of web page change monitors (watches)
          type: integer
      type: object
    Tag:
      allOf:
      - $ref: '#/components/schemas/WatchBase'
      - properties:
          overrides_watch:
            description: 'Whether this tag''s settings override watch settings for
              all watches in this tag/group.

              - true: Tag settings override watch settings

              - false: Tag settings do not override (watches use their own settings)

              - null: Not decided yet / inherit default behavior

              '
            type:
            - boolean
            - 'null'
        type: object
    TimeBetweenCheck:
      description: Time intervals between checks
      properties:
        days:
          minimum: 0
          type: integer
        hours:
          minimum: 0
          type: integer
        minutes:
          minimum: 0
          type: integer
        seconds:
          minimum: 0
          type: integer
        weeks:
          minimum: 0
          type: integer
      type: object
    UpdateWatch:
      allOf:
      - $ref: '#/components/schemas/WatchBase'
      - properties:
          last_viewed:
            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
            type: integer
        type: object
    Watch:
      allOf:
      - $ref: '#/components/schemas/WatchBase'
      - properties:
          browser_steps_last_error_step:
            description: Last browser step that caused an error
            readOnly: true
            type:
            - integer
            - 'null'
          check_count:
            description: Total number of checks performed
            readOnly: true
            type: integer
          consecutive_filter_failures:
            description: Counter for consecutive filter match failures
            readOnly: true
            type: integer
          content-type:
            description: Content-Type from last fetch
            readOnly: true
            type:
            - string
            - 'null'
          fetch_time:
            description: Duration of last fetch in seconds
            readOnly: true
            type: number
          history_n:
            description: Number of history snapshots available
            readOnly: true
            type: integer
            x-computed: true
          last_changed:
            description: Unix timestamp of last change
            readOnly: true
            type: integer
            x-computed: true
          last_checked:
            description: Unix timestamp of last check
            readOnly: true
            type: integer
          last_error:
            description: Last error message (false when no error, string when error
              occurred, null if not checked yet)
            readOnly: true
            type:
            - string
            - boolean
            - 'null'
          last_notification_error:
            description: Last notification error message
            readOnly: true
            type:
            - string
            - 'null'
          last_viewed:
            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
            type: integer
          link:
            description: The watch URL rendered in case of any Jinja2 markup, always
              use this for listing.
            format: string
            readOnly: true
            type: string
            x-computed: true
          notification_alert_count:
            description: Number of notifications sent
            readOnly: true
            type: integer
          page_title:
            description: HTML <title> tag extracted from the page
            readOnly: true
            type:
            - string
            - 'null'
          previous_md5:
            description: MD5 hash of previous content (false if not set)
            readOnly: true
            type:
            - string
            - boolean
          previous_md5_before_filters:
            description: MD5 hash before filters applied (false if not set)
            readOnly: true
            type:
            - string
            - boolean
          remote_server_reply:
            description: Server header from last response
            readOnly: true
            type:
            - string
            - 'null'
          viewed:
            description: Computed property - true if watch has been viewed, false
              otherwise (deprecated, use last_viewed instead)
            readOnly: true
            type:
            - integer
            - boolean
            x-computed: true
        type: object
    WatchBase:
      properties:
        ai_alert_condition:
          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
          type:
          - string
          - 'null'
        body:
          description: HTTP request body
          maxLength: 5000
          type:
          - string
          - 'null'
        browser_steps:
          description: Browser automation steps. Maximum 100 steps allowed.
          items:
            additionalProperties: false
            properties:
              operation:
                maxLength: 5000
                type:
                - string
                - 'null'
              optional_value:
                maxLength: 5000
                type:
                - string
                - 'null'
              selector:
                maxLength: 5000
                type:
                - string
                - 'null'
            required:
            - operation
            - selector
            - optional_value
            type: object
          maxItems: 100
          type: array
        check_unique_lines:
          default: false
          description: Compare against all history for unique lines
          type: boolean
        conditions:
          description: Array of condition rules for change detection logic (empty
            array when not set)
          items:
            properties:
              field:
                description: Field to check (e.g., 'page_filtered_text', 'page_title')
                type: string
              operator:
                description: Comparison operator (e.g., 'contains_regex', 'equals',
                  'not_equals')
                type: string
              value:
                description: Value to compare against
                type: string
            required:
            - field
            - operator
            - value
            type: object
          maxItems: 100
          type: array
        conditions_match_logic:
          default: ALL
          description: Logic operator - ALL (match all conditions) or ANY (match any
            condition)
          enum:
          - ALL
          - ANY
          type: string
        date_created:
          description: Unix timestamp of creation
          readOnly: true
          type:
          - integer
          - 'null'
        extract_text:
          description: Regex patterns to extract specific text after filtering
          items:
            maxLength: 5000
            type: string
          maxItems: 100
          type: array
        fetch_backend:
          default: system
          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_webdriver|extra_browser_.+)$
          type: string
        filter_failure_notification_send:
          default: true
          description: Send notification when filters fail to match content
          type: boolean
        filter_text_added:
          default: true
          description: Include added text in change detection
          type: boolean
        filter_text_removed:
          default: true
          description: Include removed text in change detection
          type: boolean
        filter_text_replaced:
          default: true
          description: Include replaced text in change detection
          type: boolean
        follow_price_changes:
          default: true
          description: Monitor and track price changes (restock_diff processor)
          type: boolean
        has_ldjson_price_data:
          description: Whether page has LD-JSON price data (auto-detected)
          readOnly: true
          type:
          - boolean
          - 'null'
        headers:
          additionalProperties:
            type: string
          description: HTTP headers to include in requests
          type: object
        history_snapshot_max_length:
          description: Maximum number of history snapshots to keep (null = use system
            default)
          maximum: 1000
          minimum: 1
          type:
          - integer
          - 'null'
        ignore_status_codes:
          description: Ignore HTTP status code errors (boolean or null)
          type:
          - boolean
          - 'null'
        ignore_text:
          description: Text patterns to ignore in change detection
          items:
            maxLength: 5000
            type: string
          maxItems: 100
          type: array
        in_stock_only:
          default: true
          description: Only trigger on in-stock transitions (restock_diff processor)
          type: boolean
        include_filters:
          description: CSS/XPath selectors to extract specific content from the page
          items:
            maxLength: 5000
            type: string
          maxItems: 100
          type: array
        last_ai_summary:
          description: AI-generated plain-English summary of the most recent change
            (Pro+ only, read-only — generated automatically when a change is detected).
          readOnly: true
          type:
          - string
          - 'null'
        method:
          description: HTTP method to use
          enum:
          - GET
          - POST
          - DELETE
          - PUT
          type: string
        notification_body:
          description: Custom notification body
          maxLength: 5000
          type:
          - string
          - 'null'
        notification_format:
          description: Format for notifications
          enum:
          - text
          - html
          - htmlcolor
          - markdown
          - System default
          type: string
        notification_muted:
          description: Whether notifications are muted
          type: boolean
        notification_screenshot:
          default: false
          description: Include screenshot in notifications (if supported by notification
            URL)
          type: boolean
        notification_title:
          description: Custom notification title
          maxLength: 5000
          type:
          - string
          - 'null'
        notification_urls:
          description: Notification URLs for this web page change monitor (watch).
            Maximum 100 URLs.
          items:
            maxLength: 1000
            type: string
          maxItems: 100
          type: array
        owner_api_key:
          description: API key that owns this watch (multi-tenant isolation)
          readOnly: true
          type:
          - string
          - 'null'
        paused:
          description: Whether the web page change monitor (watch) is paused
          type: boolean
        price_change_threshold_percent:
          description: Minimum price change percentage to trigger notification
          type:
          - number
          - 'null'
        processor:
          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.
          enum:
          - restock_diff
          - text_json_diff
          - image_ssim_diff
          type: string
        processor_config_restock_diff:
          $ref: '#/components/schemas/processor_config_restock_diff'
        proxy:
          description: Proxy configuration
          maxLength: 5000
          type:
          - string
          - 'null'
        remove_duplicate_lines:
          default: false
          description: Remove duplicate lines from content
          type: boolean
        screenshot_enabled:
          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`).
          type: boolean
        sort_text_alphabetically:
          default: false
          description: Sort lines alphabetically before comparison
          type: boolean
        strip_ignored_lines:
          description: Remove lines matching ignore patterns
          type:
          - boolean
          - 'null'
        subtractive_selectors:
          description: CSS/XPath selectors to remove content from the page
          items:
            maxLength: 5000
            type: string
          maxItems: 100
          type: array
        tag:
          description: Tag UUID to associate with this web page change monitor (watch)
          maxLength: 5000
          type: string
        tags:
          description: Array of tag UUIDs
          items:
            type: string
          type: array
        text_should_not_be_present:
          description: Text that should NOT be present (triggers alert if found)
          items:
            maxLength: 5000
            type: string
          maxItems: 100
          type: array
        time_between_check:
          description: Time intervals between checks. All fields must be non-negative.
            At least one non-zero value required when not using default settings.
          properties:
            days:
              maximum: 365000
              minimum: 0
              type:
              - integer
              - 'null'
            hours:
              maximum: 8760000
              minimum: 0
              type:
              - integer
              - 'null'
            minutes:
              maximum: 525600000
              minimum: 0
              type:
              - integer
              - 'null'
            seconds:
              maximum: 31536000000
              minimum: 0
              type:
              - integer
              - 'null'
            weeks:
              maximum: 52000
              minimum: 0
              type:
              - integer
              - 'null'
          type: object
        time_between_check_use_default:
          default: true
          description: Whether to use global settings for time between checks - defaults
            to true if not set
          type: boolean
        time_schedule_limit:
          description: Weekly schedule limiting when checks can run
          properties:
            enabled:
              default: false
              type: boolean
            friday:
              $ref: '#/components/schemas/DaySchedule'
            monday:
              $ref: '#/components/schemas/DaySchedule'
            saturday:
              $ref: '#/components/schemas/DaySchedule'
            sunday:
              $ref: '#/components/schemas/DaySchedule'
            thursday:
              $ref: '#/components/schemas/DaySchedule'
            tuesday:
              $ref: '#/components/schemas/DaySchedule'
            wednesday:
              $ref: '#/components/schemas/DaySchedule'
          type: object
        title:
          description: Custom title for the web page change monitor (watch), not to
            be confused with page_title
          maxLength: 5000
          type:
          - string
          - 'null'
        track_ldjson_price_data:
          description: Whether to track JSON-LD price data
          type:
          - boolean
          - 'null'
        trigger_text:
          description: Text/regex patterns that must be present to trigger a change
          items:
            maxLength: 5000
            type: string
          maxItems: 100
          type: array
        trim_text_whitespace:
          default: false
          description: Strip leading/trailing whitespace from text
          type: boolean
        url:
          description: URL to monitor for changes
          format: uri
          maxLength: 5000
          type: string
        use_page_title_in_list:
          description: Display page title in watch list (null = use system default)
          type:
          - boolean
          - 'null'
        uuid:
          description: Unique identifier
          format: uuid
          readOnly: true
          type: string
        webdriver_delay:
          description: Delay in seconds for webdriver
          type:
          - integer
          - 'null'
        webdriver_js_execute_code:
          description: JavaScript code to execute
          maxLength: 5000
          type:
          - string
          - 'null'
      type: object
    WatchHistory:
      additionalProperties:
        description: Path to snapshot file
        type: string
      description: Dictionary of timestamps and snapshot paths
      type: object
    processor_config_restock_diff:
      description: Configuration for the restock_diff processor (restock and price
        tracking)
      properties:
        follow_price_changes:
          default: true
          description: Monitor and track price changes
          type: boolean
        in_stock_processing:
          default: in_stock_only
          description: 'When to trigger on stock changes:

            - `in_stock_only`: Only trigger on Out Of Stock -> In Stock transitions

            - `all_changes`: Trigger on any availability change

            - `off`: Disable stock/availability tracking

            '
          enum:
          - in_stock_only
          - all_changes
          - 'off'
          type: string
        price_change_max:
          description: Trigger a notification when the price rises above this value
          type:
          - number
          - 'null'
        price_change_min:
          description: Trigger a notification when the price drops below this value
          type:
          - number
          - 'null'
        price_change_threshold_percent:
          description: Minimum price change percentage since the original price to
            trigger a notification
          maximum: 100
          minimum: 0
          type:
          - number
          - 'null'
      type: object
  securitySchemes:
    ApiKeyAuth:
      description: 'API key for authentication. You can find your API key in the changedetection.io
        dashboard under Settings > API.


        Enter your API key in the "Authorize" button above to automatically populate
        all code examples.

        '
      in: header
      name: x-api-key
      type: apiKey
info:
  contact:
    name: ChangeDetection.io
    url: https://github.com/dgtlmoon/changedetection.io
  description: '# ChangeDetection.io Web page monitoring and notifications API


    REST API for managing Page watches, Group tags, and Notifications.


    changedetection.io can be driven by its built in simple API, in the examples below
    you will also find `curl` command line and `python` examples to help you get started
    faster.


    ## Where to find my API key?


    The API key can be easily found under the **SETTINGS** then **API** tab of changedetection.io
    dashboard.

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


    ![Where to find the API key](./where-to-get-api-key.jpeg)


    ## Connection URL


    The API can be found at `/api/v1/`, so for example if you run changedetection.io
    locally on port 5000, then URL would be `http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history`.


    If you are using the hosted/subscription version of changedetection.io, then the
    URL is based on your login URL, for example:

    `https://<your login url>/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history`


    ## Authentication


    Almost all API requests require some authentication, this is provided as an **API
    Key** in the header of the HTTP request.


    For example: `x-api-key: YOUR_API_KEY`

    '
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
  title: ChangeDetection.io API
  version: 0.1.6
openapi: 3.1.0
paths:
  /auth/change-password:
    post:
      description: 'Change the password for the currently authenticated user.

        Requires the current password for verification.

        '
      requestBody:
        content:
          application/json:
            schema:
              properties:
                current_password:
                  description: Current password for verification
                  type: string
                new_password:
                  description: New password (minimum 8 characters)
                  example: newSecurePassword123
                  minLength: 8
                  type: string
              required:
              - current_password
              - new_password
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Password changed successfully
                    type: string
                type: object
          description: Password changed successfully
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Current password is incorrect
                    type: string
                type: object
          description: Invalid input or incorrect current password
        '401':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: API key is required
                    type: string
                type: object
          description: Unauthorized - API key required
        '404':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: User not found
                    type: string
                type: object
          description: User not found
      summary: Change password for authenticated user
      tags:
      - Authentication
  /auth/delete-account:
    delete:
      description: 'Permanently deletes the authenticated user''s account and all
        associated data,

        including all watches, settings, notification configurations, and password
        reset tokens.

        This action cannot be undone.

        '
      operationId: deleteAccount
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Account deleted successfully
                    type: string
                type: object
          description: Account deleted successfully
        '401':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: API key is required
                    type: string
                type: object
          description: Unauthorized - API key required or invalid
        '403':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: API key is not linked to a user account
                    type: string
                type: object
          description: API key is not linked to a user account
      summary: Permanently delete user account
      tags:
      - Authentication
  /auth/forgot-password:
    post:
      description: 'Request a password reset token for the given email address.

        Always returns success to avoid leaking whether an email is registered.

        '
      requestBody:
        content:
          application/json:
            schema:
              properties:
                email:
                  description: Email address of the account to reset
                  example: user@example.com
                  format: email
                  type: string
              required:
              - email
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: If an account with that email exists, a password reset
                      link has been generated.
                    type: string
                  reset_token:
                    description: Reset token (temporary — will be removed when email
                      sending is implemented)
                    nullable: true
                    type: string
                type: object
          description: Reset request processed
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Email is required
                    type: string
                type: object
          description: Invalid input
      security: []
      summary: Request a password reset
      tags:
      - Authentication
  /auth/login:
    post:
      description: 'Authenticate with email and password to retrieve your API key.

        Use this API key for all subsequent authenticated requests.

        '
      requestBody:
        content:
          application/json:
            schema:
              properties:
                email:
                  description: User's email address
                  example: user@example.com
                  format: email
                  type: string
                password:
                  description: User's password
                  example: secretpassword123
                  type: string
              required:
              - email
              - password
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Login successful
                    type: string
                  user:
                    properties:
                      api_key:
                        example: abc123def456...
                        type: string
                      email:
                        example: user@example.com
                        type: string
                      email_verified:
                        description: Whether the user's email has been verified
                        example: true
                        type: boolean
                      name:
                        example: John Doe
                        type: string
                    type: object
                type: object
          description: Login successful
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Email and password are required
                    type: string
                type: object
          description: Invalid input
        '401':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Invalid credentials
                    type: string
                type: object
          description: Invalid credentials
      security: []
      summary: Login to existing account
      tags:
      - Authentication
  /auth/me:
    get:
      description: 'Retrieve information about the currently authenticated user.

        Requires a valid API key in the x-api-key header.

        '
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  user:
                    properties:
                      api_key:
                        example: abc123def456...
                        type: string
                      created_at:
                        description: Unix timestamp of account creation
                        example: 1704067200
                        type: integer
                      email:
                        example: user@example.com
                        type: string
                      email_verified:
                        description: Whether the user's email has been verified
                        example: true
                        type: boolean
                      name:
                        example: John Doe
                        type: string
                    type: object
                type: object
          description: User information
        '401':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: API key required
                    type: string
                type: object
          description: Unauthorized - API key required
        '404':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: User not found
                    type: string
                type: object
          description: User not found for this API key
      summary: Get current user info
      tags:
      - Authentication
  /auth/regenerate-key:
    post:
      description: 'Regenerate the API key for the currently authenticated user.

        The old key is invalidated and all watches are migrated to the new key.

        Only works for API keys linked to a user account.

        '
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: API key regenerated successfully
                    type: string
                  user:
                    properties:
                      api_key:
                        example: a1b2c3d4e5f6...
                        type: string
                      email:
                        example: user@example.com
                        format: email
                        type: string
                      name:
                        example: John Doe
                        nullable: true
                        type: string
                    type: object
                type: object
          description: API key regenerated successfully
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: API key is not linked to a user account
                    type: string
                type: object
          description: API key is not linked to a user account
        '401':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: API key is required
                    type: string
                type: object
          description: Unauthorized - API key required or invalid
      summary: Regenerate API key for authenticated user
      tags:
      - Authentication
  /auth/register:
    post:
      description: 'Create a new user account with email and password. An API key
        is automatically

        generated and linked to the account. This API key can be used for all subsequent

        authenticated requests.

        '
      requestBody:
        content:
          application/json:
            schema:
              properties:
                email:
                  description: User's email address
                  example: user@example.com
                  format: email
                  type: string
                name:
                  description: User's display name (optional)
                  example: John Doe
                  type: string
                password:
                  description: Password (minimum 8 characters)
                  example: secretpassword123
                  minLength: 8
                  type: string
              required:
              - email
              - password
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Account created successfully
                    type: string
                  user:
                    properties:
                      api_key:
                        example: abc123def456...
                        type: string
                      email:
                        example: user@example.com
                        type: string
                      email_verified:
                        description: Whether the user's email has been verified
                        example: false
                        type: boolean
                      name:
                        example: John Doe
                        type: string
                    type: object
                type: object
          description: Account created successfully
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Email and password are required
                    type: string
                type: object
          description: Invalid input (missing fields, invalid email, or disposable
            email)
        '409':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: User with this email already exists
                    type: string
                type: object
          description: User already exists
        '429':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Too many signup attempts. Please try again later.
                    type: string
                type: object
          description: Too many signup attempts from this IP
      security: []
      summary: Register a new user account
      tags:
      - Authentication
  /auth/resend-verification:
    post:
      description: 'Resend the account verification email. Requires authentication.

        Rate limited to one request per 60 seconds.

        '
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  expires_in_hours:
                    example: 24
                    type: integer
                  message:
                    example: Verification email sent
                    type: string
                type: object
          description: Verification email sent
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Email already verified
                    type: string
                type: object
          description: Email already verified or no email set
        '401':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: API key is required
                    type: string
                type: object
          description: API key required
        '429':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Please wait before requesting another verification email
                    type: string
                type: object
          description: Rate limit - wait before requesting again
      summary: Resend account verification email
      tags:
      - Authentication
  /auth/reset-password:
    post:
      description: 'Reset the user''s password using a valid reset token obtained
        from the forgot-password endpoint.

        Tokens expire after 1 hour.

        '
      requestBody:
        content:
          application/json:
            schema:
              properties:
                new_password:
                  description: New password (minimum 8 characters)
                  example: newSecurePassword123
                  minLength: 8
                  type: string
                token:
                  description: Password reset token
                  type: string
              required:
              - token
              - new_password
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Password has been reset successfully
                    type: string
                type: object
          description: Password reset successful
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Invalid or expired reset token
                    type: string
                type: object
          description: Invalid or expired token, or invalid password
      security: []
      summary: Reset password with token
      tags:
      - Authentication
  /auth/social-login:
    post:
      description: 'Authenticate a user via a social login provider (e.g., Google).

        If the user already exists, returns their API key.

        If the user doesn''t exist, auto-creates an account and returns the new API
        key.

        This endpoint is intended to be called server-side from the NextAuth callback.

        '
      requestBody:
        content:
          application/json:
            schema:
              properties:
                email:
                  description: User's email address from the OAuth provider
                  example: user@example.com
                  format: email
                  type: string
                name:
                  description: User's display name from the OAuth provider
                  example: John Doe
                  type: string
                provider:
                  description: The OAuth provider name
                  enum:
                  - google
                  example: google
                  type: string
              required:
              - email
              - provider
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Login successful
                    type: string
                  user:
                    properties:
                      api_key:
                        example: abc123def456...
                        type: string
                      email:
                        example: user@example.com
                        type: string
                      email_verified:
                        description: Whether the user's email has been verified
                        example: true
                        type: boolean
                      name:
                        example: John Doe
                        type: string
                    type: object
                type: object
          description: Login successful
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: Email is required
                    type: string
                type: object
          description: Invalid input or unsupported provider
      security: []
      summary: Login or register via social/OAuth provider
      tags:
      - Authentication
  /auth/verify-email:
    get:
      description: 'Verify a user''s account email using the token sent via email.

        Returns an HTML page with the verification result.

        '
      parameters:
      - description: Verification token from the email link
        in: query
        name: token
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            text/html:
              schema:
                type: string
          description: Email verified successfully (HTML response)
        '400':
          content:
            text/html:
              schema:
                type: string
          description: Invalid or expired token (HTML response)
      security: []
      summary: Verify account email
      tags:
      - Authentication
  /billing/checkout:
    post:
      description: Creates a hosted checkout URL for upgrading to a paid plan.
      operationId: createBillingCheckout
      requestBody:
        content:
          application/json:
            schema:
              properties:
                billing_period:
                  default: monthly
                  enum:
                  - monthly
                  - yearly
                  type: string
                plan:
                  enum:
                  - starter
                  - pro
                  - business
                  example: starter
                  type: string
                success_url:
                  description: URL to redirect to after successful checkout
                  example: https://sitespy.app/dashboard/billing?checkout=success
                  type: string
              required:
              - plan
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  checkout_url:
                    example: https://checkout.lemonsqueezy.com/...
                    type: string
                type: object
          description: Checkout URL
        '400':
          description: Invalid plan
        '503':
          description: Billing not configured on server
      summary: Create a Lemon Squeezy checkout session
      tags:
      - Billing
  /billing/portal:
    get:
      description: Returns the Lemon Squeezy customer portal URL for managing the
        subscription.
      operationId: getBillingPortal
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  portal_url:
                    type: string
                type: object
          description: Portal URL
        '404':
          description: No active subscription
      summary: Get customer billing portal URL
      tags:
      - Billing
  /billing/status:
    get:
      description: Returns the current subscription status and metadata.
      operationId: getBillingStatus
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  current_period_end:
                    nullable: true
                    type: string
                  ls_subscription_id:
                    nullable: true
                    type: string
                  plan:
                    enum:
                    - free
                    - starter
                    - pro
                    type: string
                  subscription_status:
                    nullable: true
                    type: string
                type: object
          description: Billing status
      summary: Get billing subscription status
      tags:
      - Billing
  /billing/webhook:
    post:
      description: Receives subscription lifecycle events from Lemon Squeezy. Called
        by Lemon Squeezy, not clients.
      operationId: billingWebhook
      requestBody:
        content:
          application/json:
            schema:
              type: object
        required: true
      responses:
        '200':
          description: Webhook processed
        '401':
          description: Invalid signature
      security: []
      summary: Lemon Squeezy webhook receiver
      tags:
      - Billing
  /email:
    get:
      description: Get the current email address and verification status for your
        API key.
      operationId: getEmailStatus
      responses:
        '200':
          content:
            application/json:
              example:
                email: user@example.com
                verified: true
              schema:
                properties:
                  email:
                    description: The email address, or null if not set
                    nullable: true
                    type: string
                  verified:
                    description: Whether the email is verified
                    type: boolean
                type: object
          description: Email status retrieved successfully
        '400':
          description: Email settings not available for admin keys
        '401':
          description: Unauthorized - API key required
      summary: Get email verification status
      tags:
      - Email Verification
    post:
      description: 'Set or update the email address for your API key. A verification
        email will be sent

        to the provided address. The email must be verified before notifications can
        be sent.


        To disable email notifications, send an empty string for the email field.

        '
      operationId: setEmail
      requestBody:
        content:
          application/json:
            examples:
              disableEmail:
                summary: Disable email notifications
                value:
                  email: ''
              setEmail:
                summary: Set or update email
                value:
                  email: user@example.com
            schema:
              properties:
                email:
                  description: The email address to set, or empty string to disable
                    notifications
                  type: string
              required:
              - email
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              examples:
                emailDisabled:
                  summary: Email notifications disabled
                  value:
                    email: null
                    email_sent: false
                    message: Email notifications disabled
                emailSet:
                  summary: Email verification sent
                  value:
                    email: user@example.com
                    email_sent: true
                    expires_in_hours: 24
                    message: Verification email sent
              schema:
                properties:
                  email:
                    description: The email address, or null if disabled
                    nullable: true
                    type: string
                  email_sent:
                    description: Whether the verification email was successfully sent
                    type: boolean
                  expires_in_hours:
                    type: integer
                  message:
                    type: string
                type: object
          description: Email updated or disabled successfully
        '400':
          description: Invalid email address or email settings not available for admin
            keys
        '401':
          description: Unauthorized - API key required
      summary: Set, update, or disable email address
      tags:
      - Email Verification
  /email/resend:
    post:
      description: Resend the verification email for the email address associated
        with your API key.
      operationId: resendVerificationEmail
      responses:
        '200':
          content:
            application/json:
              example:
                email: user@example.com
                expires_in_hours: 24
                message: Verification email sent
              schema:
                properties:
                  email:
                    type: string
                  expires_in_hours:
                    type: integer
                  message:
                    type: string
                type: object
          description: Verification email sent
        '400':
          description: No email set, email already verified, or email settings not
            available for admin keys
        '401':
          description: Unauthorized - API key required
      summary: Resend verification email
      tags:
      - Email Verification
  /email/verify:
    get:
      description: 'Verify an email address using the token from the verification
        email.

        This endpoint does not require authentication as the token serves as proof.

        '
      operationId: verifyEmail
      parameters:
      - description: The verification token from the email
        in: query
        name: token
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              example:
                email: user@example.com
                message: Email verified successfully
              schema:
                properties:
                  email:
                    type: string
                  message:
                    type: string
                type: object
          description: Email verified successfully
        '400':
          description: Invalid or expired token
      security: []
      summary: Verify email address
      tags:
      - Email Verification
  /feature-flags:
    get:
      description: 'Retrieve the feature flags for the current API key. Feature flags
        control which

        features are enabled or disabled for specific users.

        '
      operationId: getFeatureFlags
      responses:
        '200':
          content:
            application/json:
              example:
                feature_flags:
                  email_notifications:
                    description: Enable email notifications for watch changes
                    enabled: true
                    name: Email Notifications
              schema:
                properties:
                  feature_flags:
                    additionalProperties:
                      properties:
                        description:
                          type: string
                        enabled:
                          type: boolean
                        name:
                          type: string
                      type: object
                    type: object
                type: object
          description: Feature flags retrieved successfully
        '401':
          description: Unauthorized - API key required
      summary: Get feature flags
      tags:
      - Feature Flags
  /full-spec:
    get:
      description: 'Return the fully merged OpenAPI specification for this instance.


        Unlike the static `api-spec.yaml` shipped with the application, this endpoint
        returns the

        spec dynamically merged with any `api.yaml` schemas provided by installed
        processor plugins.

        Use this URL with Swagger UI or Redoc to get accurate documentation for your
        specific install.

        '
      operationId: getFullApiSpec
      responses:
        '200':
          content:
            application/yaml:
              schema:
                type: string
          description: Merged OpenAPI specification in YAML format
      security: []
      summary: Get full live API spec
      tags:
      - Plugin API Extensions
      x-code-samples:
      - lang: curl
        source: 'curl -X GET "http://localhost:5000/api/v1/full-spec"

          '
  /generate_key:
    post:
      description: 'Generate a new unique API key for accessing the API. This endpoint
        does not require

        authentication. Each API key is associated with a unique user account.


        **Rate Limiting**: This endpoint is rate-limited to prevent abuse. Excessive
        requests

        from the same IP address will result in a 429 Too Many Requests response.

        '
      operationId: generateApiKey
      responses:
        '200':
          content:
            application/json:
              example:
                api_key: abc123def456ghi789jkl012mno345pqr
              schema:
                properties:
                  api_key:
                    description: The newly generated API key
                    type: string
                required:
                - api_key
                type: object
          description: API key generated successfully
        '429':
          description: Too many requests - rate limit exceeded
          headers:
            Retry-After:
              description: Number of seconds to wait before retrying
              schema:
                type: integer
        '500':
          description: Server error generating API key
      security: []
      summary: Generate a new API key
      tags:
      - API Key Management
  /import:
    post:
      description: 'Import a list of URLs to monitor with optional watch configuration.
        Accepts line-separated URLs in request body.


        **Configuration via Query Parameters:**


        You can pass ANY watch configuration field as query parameters to apply settings
        to all imported watches.

        All parameters from the Watch schema are supported (processor, fetch_backend,
        notification_urls, etc.).


        **Special Parameters:**

        - `tag` / `tag_uuids` - Assign tags to imported watches

        - `proxy` - Use specific proxy for imported watches

        - `dedupe` - Skip duplicate URLs (default: true)


        **Type Conversion:**

        - Booleans: `true`, `false`, `1`, `0`, `yes`, `no`

        - Arrays: Comma-separated or JSON format (`[item1,item2]`)

        - Objects: JSON format (`{"key":"value"}`)

        - Numbers: Parsed as int or float

        '
      operationId: importWatches
      parameters:
      - description: Tag UUID(s) to apply to imported watches (comma-separated for
          multiple)
        example: 550e8400-e29b-41d4-a716-446655440000
        in: query
        name: tag_uuids
        schema:
          type: string
      - description: Tag name to apply to imported watches
        example: production
        in: query
        name: tag
        schema:
          type: string
      - description: Proxy key to use for imported watches
        example: proxy1
        in: query
        name: proxy
        schema:
          type: string
      - description: Skip duplicate URLs (default true)
        in: query
        name: dedupe
        schema:
          default: true
          type: boolean
      requestBody:
        content:
          text/plain:
            example: 'https://example.com

              https://example.org

              https://example.net

              '
            schema:
              type: string
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                description: List of created watch UUIDs
                items:
                  format: uuid
                  type: string
                type: array
          description: URLs imported successfully
        '500':
          description: Server error
      summary: Import watch URLs with configuration
      tags:
      - Import
      x-code-samples:
      - lang: curl
        source: "# Basic import\ncurl -X POST \"http://localhost:5000/api/v1/import\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: text/plain\"\
          \ \\\n  -d $'https://example.com\\nhttps://example.org\\nhttps://example.net'\n\
          \n# Import with processor and fetch backend\ncurl -X POST \"http://localhost:5000/api/v1/import?processor=restock_diff&fetch_backend=html_webdriver\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: text/plain\"\
          \ \\\n  -d $'https://example.com\\nhttps://example.org'\n\n# Import with\
          \ multiple settings\ncurl -X POST \"http://localhost:5000/api/v1/import?processor=restock_diff&paused=true&tag=production\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: text/plain\"\
          \ \\\n  -d $'https://example.com'\n"
      - lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'text/plain'\n}\n\n# Basic import\nurls = 'https://example.com\\\
          nhttps://example.org\\nhttps://example.net'\nresponse = requests.post('http://localhost:5000/api/v1/import',\n\
          \                       headers=headers, data=urls)\nprint(response.json())\n\
          \n# Import with configuration\nparams = {\n    'processor': 'restock_diff',\n\
          \    'fetch_backend': 'html_webdriver',\n    'paused': 'false',\n    'tag':\
          \ 'production'\n}\nresponse = requests.post('http://localhost:5000/api/v1/import',\n\
          \                       headers=headers, params=params, data=urls)\nprint(response.json())\n"
  /notifications:
    delete:
      description: Delete one or more notification URLs from the configuration
      operationId: deleteNotifications
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NotificationUrls'
        required: true
      responses:
        '204':
          description: Notification URLs deleted successfully
        '400':
          description: No matching notification URLs found
        '403':
          description: Upgrade required - webhook notifications require Starter plan
            or higher
      summary: Delete notification URLs
      tags:
      - Notifications
      x-code-samples:
      - lang: curl
        source: "curl -X DELETE \"http://localhost:5000/api/v1/notifications\" \\\n\
          \  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\"\
          \ \\\n  -d '{\n    \"notification_urls\": [\n      \"mailto:admin@example.com\"\
          \n    ]\n  }'\n"
      - lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ndata = {\n    'notification_urls':\
          \ [\n        'mailto:admin@example.com'\n    ]\n}\nresponse = requests.delete('http://localhost:5000/api/v1/notifications',\n\
          \                         headers=headers, json=data)\nprint(response.status_code)\n"
    get:
      description: Return the notification URL list from the configuration
      operationId: getNotifications
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationUrls'
          description: List of notification URLs
        '403':
          description: Upgrade required - webhook notifications require Starter plan
            or higher
      summary: Get notification URLs
      tags:
      - Notifications
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/notifications\" \\\n \
          \ -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          response = requests.get(''http://localhost:5000/api/v1/notifications'',
          headers=headers)

          print(response.json())

          '
    post:
      description: Add one or more notification URLs to the configuration
      operationId: addNotifications
      requestBody:
        content:
          application/json:
            example:
              notification_urls:
              - mailto:admin@example.com
              - discord://webhook_id/webhook_token
            schema:
              $ref: '#/components/schemas/NotificationUrls'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationUrls'
          description: Notification URLs added successfully
        '400':
          description: Invalid input
        '403':
          description: Upgrade required - webhook notifications require Starter plan
            or higher
      summary: Add notification URLs
      tags:
      - Notifications
      x-code-samples:
      - lang: curl
        source: "curl -X POST \"http://localhost:5000/api/v1/notifications\" \\\n\
          \  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\"\
          \ \\\n  -d '{\n    \"notification_urls\": [\n      \"mailto:admin@example.com\"\
          ,\n      \"discord://webhook_id/webhook_token\"\n    ]\n  }'\n"
      - lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ndata = {\n    'notification_urls':\
          \ [\n        'mailto:admin@example.com',\n        'discord://webhook_id/webhook_token'\n\
          \    ]\n}\nresponse = requests.post('http://localhost:5000/api/v1/notifications',\n\
          \                       headers=headers, json=data)\nprint(response.json())\n"
    put:
      description: Replace all notification URLs with the provided list (can be empty)
      operationId: replaceNotifications
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NotificationUrls'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationUrls'
          description: Notification URLs replaced successfully
        '400':
          description: Invalid input
        '403':
          description: Upgrade required - webhook notifications require Starter plan
            or higher
      summary: Replace notification URLs
      tags:
      - Notifications
      x-code-samples:
      - lang: curl
        source: "curl -X PUT \"http://localhost:5000/api/v1/notifications\" \\\n \
          \ -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\"\
          \ \\\n  -d '{\n    \"notification_urls\": [\n      \"mailto:newadmin@example.com\"\
          \n    ]\n  }'\n"
      - lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ndata = {\n    'notification_urls':\
          \ [\n        'mailto:newadmin@example.com'\n    ]\n}\nresponse = requests.put('http://localhost:5000/api/v1/notifications',\n\
          \                      headers=headers, json=data)\nprint(response.json())\n"
  /notifications/test:
    post:
      description: Send a test notification to verify webhook URL(s) are configured
        correctly. Requires Starter plan or higher.
      operationId: testNotificationUrl
      requestBody:
        content:
          application/json:
            example:
              notification_urls:
              - json://localhost
            schema:
              $ref: '#/components/schemas/NotificationUrls'
        required: true
      responses:
        '200':
          content:
            application/json:
              example:
                message: Test notification sent
                success: true
              schema:
                properties:
                  message:
                    type: string
                  success:
                    type: boolean
                type: object
          description: Test notification sent successfully
        '400':
          description: Invalid URL or failed to send
        '403':
          description: Upgrade required - webhook notifications require Starter plan
            or higher
      summary: Send a test notification
      tags:
      - Notifications
  /plan:
    get:
      description: Returns the subscription plan, limits, and watch usage for the
        authenticated API key.
      operationId: getPlan
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  billing:
                    properties:
                      current_period_end:
                        nullable: true
                        type: string
                      ls_customer_portal_url:
                        nullable: true
                        type: string
                      ls_subscription_id:
                        nullable: true
                        type: string
                      subscription_status:
                        example: active
                        nullable: true
                        type: string
                    type: object
                  limits:
                    properties:
                      history_retention_days:
                        description: Number of days snapshots are retained before
                          automatic cleanup
                        example: 30
                        type: integer
                      label:
                        example: Free
                        type: string
                      max_check_interval_seconds:
                        description: Maximum allowed recheck interval in seconds for
                          this plan
                        example: 604800
                        type: integer
                      max_snapshots_per_watch:
                        description: Maximum number of snapshots kept per watch before
                          oldest are pruned
                        example: 5
                        type: integer
                      max_watches:
                        example: 5
                        type: integer
                      min_check_interval_seconds:
                        description: Minimum allowed recheck interval in seconds for
                          this plan
                        example: 21600
                        type: integer
                      price_monthly:
                        example: 0
                        type: integer
                    type: object
                  plan:
                    enum:
                    - free
                    - starter
                    - pro
                    example: free
                    type: string
                  trial:
                    nullable: true
                    properties:
                      days_remaining:
                        description: Days remaining in the trial (0 if expired)
                        type: integer
                      trial_active:
                        description: Whether the trial is currently active
                        type: boolean
                      trial_ends_at:
                        description: Unix timestamp when trial expires
                        nullable: true
                        type: number
                    type: object
                  usage:
                    properties:
                      watches:
                        example: 3
                        type: integer
                    type: object
                type: object
          description: Plan and usage information
        '403':
          description: Unauthorized
      summary: Get current plan and usage
      tags:
      - Plan
  /rss:
    delete:
      description: Revoke the RSS feed token, disabling all RSS feed access for this
        user.
      operationId: revokeRssToken
      responses:
        '200':
          content:
            application/json:
              example:
                message: RSS feed token revoked
              schema:
                properties:
                  message:
                    type: string
                type: object
          description: RSS token revoked
        '400':
          description: RSS feed settings not available for admin keys
        '401':
          description: Unauthorized - API key required
        '404':
          description: API key not found
      summary: Revoke RSS feed token
      tags:
      - RSS Feeds
    get:
      description: 'Get the current user''s RSS feed token and status. Use the returned
        token to construct

        RSS feed URLs for use in any RSS reader.

        '
      operationId: getRssFeedSettings
      responses:
        '200':
          content:
            application/json:
              example:
                enabled: true
                rss_token: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
              schema:
                properties:
                  enabled:
                    description: Whether RSS feeds are enabled (token exists)
                    type: boolean
                  rss_token:
                    description: The RSS feed access token
                    nullable: true
                    type: string
                type: object
          description: RSS feed settings retrieved
        '400':
          description: RSS feed settings not available for admin keys
        '401':
          description: Unauthorized - API key required
      summary: Get RSS feed settings
      tags:
      - RSS Feeds
    post:
      description: 'Generate (or regenerate) a personal RSS feed token. This token
        is used in RSS feed URLs

        to authenticate access. Only watches owned by this user will appear in the
        feeds.


        Feed URLs use the format: `{server}/rss?token={rss_token}`

        '
      operationId: generateRssToken
      responses:
        '200':
          content:
            application/json:
              example:
                enabled: true
                rss_token: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
              schema:
                properties:
                  enabled:
                    type: boolean
                  rss_token:
                    description: The newly generated RSS feed token
                    type: string
                type: object
          description: RSS token generated
        '400':
          description: RSS feed settings not available for admin keys
        '401':
          description: Unauthorized - API key required
        '404':
          description: API key not found
      summary: Generate RSS feed token
      tags:
      - RSS Feeds
  /search:
    get:
      description: Search web page change monitors (watches) by URL or title text
      operationId: searchWatches
      parameters:
      - description: Search query to match against watch URLs and titles
        in: query
        name: q
        required: true
        schema:
          type: string
      - description: Tag name to limit results (name not UUID)
        in: query
        name: tag
        schema:
          type: string
      - description: Allow partial matching of URL query
        in: query
        name: partial
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              example:
                watches:
                  095be615-a8ad-4c33-8e9c-c7612fbf6c9f:
                    notification_muted: false
                    paused: false
                    tags:
                    - 550e8400-e29b-41d4-a716-446655440000
                    title: Example Website Monitor
                    url: http://example.com
                    uuid: 095be615-a8ad-4c33-8e9c-c7612fbf6c9f
              schema:
                $ref: '#/components/schemas/SearchResult'
          description: Search results
      summary: Search watches
      tags:
      - Search
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/search?q=example.com\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: "import requests\n\nheaders = {'x-api-key': 'YOUR_API_KEY'}\nparams\
          \ = {'q': 'example.com'}\nresponse = requests.get('http://localhost:5000/api/v1/search',\n\
          \                      headers=headers, params=params)\nprint(response.json())\n"
  /settings/user:
    get:
      description: Retrieve the authenticated user's custom settings, including recheck
        interval preferences and email notification settings.
      operationId: getUserSettings
      responses:
        '200':
          content:
            application/json:
              example:
                email_notifications:
                  email: user@example.com
                  enabled: true
                global_ignore_text: []
                global_subtractive_selectors: []
                time_between_check:
                  hours: 6
              schema:
                properties:
                  email_notifications:
                    $ref: '#/components/schemas/EmailNotifications'
                  global_ignore_text:
                    description: Text patterns to ignore in change detection across
                      all watches. Supports plain text and /regex/ patterns.
                    items:
                      maxLength: 5000
                      type: string
                    maxItems: 100
                    type: array
                  global_subtractive_selectors:
                    description: CSS/XPath selectors to remove content from all watched
                      pages before comparison.
                    items:
                      maxLength: 5000
                      type: string
                    maxItems: 100
                    type: array
                  time_between_check:
                    $ref: '#/components/schemas/TimeBetweenCheck'
                type: object
          description: User settings retrieved successfully
        '401':
          description: Unauthorized - API key required
      summary: Get user-specific settings
      tags:
      - User Settings
    put:
      description: Update the authenticated user's custom settings, such as default
        recheck interval for new watches, email notification preferences, and global
        ignore patterns.
      operationId: updateUserSettings
      requestBody:
        content:
          application/json:
            example:
              email_notifications:
                email: user@example.com
                enabled: true
              global_ignore_text:
              - /\d{4}-\d{2}-\d{2}/
              - cookie
              global_subtractive_selectors:
              - .cookie-banner
              - nav
              time_between_check:
                hours: 6
            schema:
              properties:
                email_notifications:
                  $ref: '#/components/schemas/EmailNotifications'
                global_ignore_text:
                  description: Text patterns to ignore in change detection across
                    all watches. Supports plain text and /regex/ patterns.
                  items:
                    maxLength: 5000
                    type: string
                  maxItems: 100
                  type: array
                global_subtractive_selectors:
                  description: CSS/XPath selectors to remove content from all watched
                    pages before comparison.
                  items:
                    maxLength: 5000
                    type: string
                  maxItems: 100
                  type: array
                time_between_check:
                  $ref: '#/components/schemas/TimeBetweenCheck'
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  email_notifications:
                    $ref: '#/components/schemas/EmailNotifications'
                  global_ignore_text:
                    description: Text patterns to ignore in change detection across
                      all watches.
                    items:
                      type: string
                    type: array
                  global_subtractive_selectors:
                    description: CSS/XPath selectors to remove content from all watched
                      pages.
                    items:
                      type: string
                    type: array
                  time_between_check:
                    $ref: '#/components/schemas/TimeBetweenCheck'
                type: object
          description: Settings updated successfully
        '400':
          description: Invalid input - at least one time value must be greater than
            0, or invalid email format
        '401':
          description: Unauthorized - API key required
      summary: Update user-specific settings
      tags:
      - User Settings
  /settings/user/test-email:
    post:
      description: Send a test email to verify email notification settings are configured
        correctly.
      operationId: testEmailNotification
      responses:
        '200':
          content:
            application/json:
              example:
                message: Test email sent successfully
                success: true
              schema:
                properties:
                  message:
                    type: string
                  success:
                    type: boolean
                type: object
          description: Test email sent successfully
        '400':
          content:
            application/json:
              example:
                message: Email notifications are not enabled
                success: false
              schema:
                properties:
                  message:
                    type: string
                  success:
                    type: boolean
                type: object
          description: Email notifications not configured or SMTP not configured
        '401':
          description: Unauthorized - API key required
      summary: Send a test email notification
      tags:
      - User Settings
  /systeminfo:
    get:
      description: Return information about the current system state
      operationId: getSystemInfo
      responses:
        '200':
          content:
            application/json:
              example:
                tag_count: 5
                uptime: 2 days, 3:45:12
                version: 0.50.10
                watch_count: 42
              schema:
                $ref: '#/components/schemas/SystemInfo'
          description: System information
      summary: Get system information
      tags:
      - System Information
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/systeminfo\" \\\n  -H\
          \ \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          response = requests.get(''http://localhost:5000/api/v1/systeminfo'', headers=headers)

          print(response.json())

          '
  /tag:
    post:
      description: Create a single tag/group
      operationId: createTag
      requestBody:
        content:
          application/json:
            example:
              title: Important Sites
            schema:
              $ref: '#/components/schemas/CreateTag'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                properties:
                  uuid:
                    description: UUID of the created tag
                    format: uuid
                    type: string
                type: object
          description: Tag created successfully
        '400':
          description: Invalid or unsupported tag
      summary: Create tag
      tags:
      - Tag Management
      x-code-samples:
      - lang: curl
        source: "curl -X POST \"http://localhost:5000/api/v1/tag\" \\\n  -H \"x-api-key:\
          \ YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n\
          \    \"title\": \"Important Sites\"\n  }'\n"
      - lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ndata = {'title': 'Important\
          \ Sites'}\nresponse = requests.post('http://localhost:5000/api/v1/tag',\n\
          \                       headers=headers, json=data)\nprint(response.json())\n"
  /tag/{uuid}:
    delete:
      description: Delete a tag/group and remove it from all web page change monitors
        (watches)
      operationId: deleteTag
      parameters:
      - description: Tag unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          description: Tag deleted successfully
      summary: Delete tag
      tags:
      - Tag Management
      x-code-samples:
      - lang: curl
        source: "curl -X DELETE \"http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          tag_uuid = ''550e8400-e29b-41d4-a716-446655440000''

          response = requests.delete(f''http://localhost:5000/api/v1/tag/{tag_uuid}'',
          headers=headers)

          print(response.text)

          '
    get:
      description: Retrieve tag information, set notification_muted status, recheck
        all web page change monitors (watches) in tag.
      operationId: getTag
      parameters:
      - description: Tag unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      - description: Set mute state
        in: query
        name: muted
        schema:
          enum:
          - muted
          - unmuted
          type: string
      - description: Queue all web page change monitors (watches) with this tag for
          recheck
        in: query
        name: recheck
        schema:
          enum:
          - 'true'
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
            text/plain:
              schema:
                example: OK
                type: string
          description: Tag information or operation result
        '404':
          description: Tag not found
      summary: Get single tag
      tags:
      - Tag Management
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          tag_uuid = ''550e8400-e29b-41d4-a716-446655440000''

          response = requests.get(f''http://localhost:5000/api/v1/tag/{tag_uuid}'',
          headers=headers)

          print(response.json())

          '
    put:
      description: Update an existing tag using JSON
      operationId: updateTag
      parameters:
      - description: Tag unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Tag'
        required: true
      responses:
        '200':
          description: Tag updated successfully
        '500':
          description: Server error
      summary: Update tag
      tags:
      - Tag Management
      x-code-samples:
      - lang: curl
        source: "curl -X PUT \"http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\"\
          \ \\\n  -d '{\n    \"title\": \"Updated Production Sites\",\n    \"notification_muted\"\
          : false\n  }'\n"
      - lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ntag_uuid = '550e8400-e29b-41d4-a716-446655440000'\n\
          data = {\n    'title': 'Updated Production Sites',\n    'notification_muted':\
          \ False\n}\nresponse = requests.put(f'http://localhost:5000/api/v1/tag/{tag_uuid}',\n\
          \                      headers=headers, json=data)\nprint(response.text)\n"
      - label: Set restock config on group/tag
        lang: curl
        source: "curl -X PUT \"http://localhost:5000/api/v1/tag/YOUR-TAG-UUID\" \\\
          \n  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\"\
          \ \\\n  -d '{\n    \"overrides_watch\": true,\n    \"processor_config_restock_diff\"\
          : {\n      \"in_stock_processing\": \"in_stock_only\",\n      \"follow_price_changes\"\
          : true,\n      \"price_change_threshold_percent\": 10\n    }\n  }'\n"
      - label: Set restock config on group/tag
        lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ntag_uuid = 'YOUR-TAG-UUID'\n\
          data = {\n    'overrides_watch': True,\n    'processor_config_restock_diff':\
          \ {\n        'in_stock_processing': 'in_stock_only',\n        'follow_price_changes':\
          \ True,\n        'price_change_threshold_percent': 10,\n    }\n}\nresponse\
          \ = requests.put(f'http://localhost:5000/api/v1/tag/{tag_uuid}',\n     \
          \                   headers=headers, json=data)\nprint(response.text)\n"
  /tags:
    get:
      description: Return list of available tags/groups
      operationId: listTags
      responses:
        '200':
          content:
            application/json:
              example:
                330e8400-e29b-41d4-a716-446655440001:
                  notification_muted: false
                  notification_urls:
                  - discord://webhook_id/webhook_token
                  title: News Sources
                  uuid: 330e8400-e29b-41d4-a716-446655440001
                550e8400-e29b-41d4-a716-446655440000:
                  notification_muted: false
                  notification_urls:
                  - mailto:admin@example.com
                  title: Production Sites
                  uuid: 550e8400-e29b-41d4-a716-446655440000
              schema:
                additionalProperties:
                  $ref: '#/components/schemas/Tag'
                type: object
          description: List of tags
      summary: List all tags
      tags:
      - Tag Management
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/tags\" \\\n  -H \"x-api-key:\
          \ YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          response = requests.get(''http://localhost:5000/api/v1/tags'', headers=headers)

          print(response.json())

          '
  /telegram:
    delete:
      description: Disconnect Telegram and stop receiving notifications.
      operationId: deleteTelegram
      responses:
        '200':
          content:
            application/json:
              example:
                message: Telegram notifications disabled
              schema:
                properties:
                  message:
                    type: string
                type: object
          description: Telegram notifications disabled
        '401':
          description: Unauthorized - API key required
        '404':
          description: API key not found
      summary: Remove Telegram notifications
      tags:
      - Telegram Notifications
    get:
      description: Get the current Telegram connection status for your API key, including
        any pending link.
      operationId: getTelegramStatus
      responses:
        '200':
          content:
            application/json:
              example:
                bot_configured: true
                chat_id: ''
                connected: false
                pending_link: https://t.me/SiteSpyBot?start=abc123
              schema:
                properties:
                  bot_configured:
                    description: Whether the Telegram bot is configured on the server
                    type: boolean
                  chat_id:
                    type: string
                  connected:
                    type: boolean
                  pending_link:
                    description: Telegram deep-link URL if connection is pending
                    nullable: true
                    type: string
                type: object
          description: Telegram status retrieved
        '400':
          description: Telegram settings not available for admin keys
        '401':
          description: Unauthorized - API key required
        '404':
          description: API key not found
      summary: Get Telegram connection status
      tags:
      - Telegram Notifications
    post:
      description: 'Generate a deep-link URL for connecting your Telegram account.

        Open the returned link in Telegram and tap Start to complete the connection.

        '
      operationId: setTelegram
      responses:
        '200':
          content:
            application/json:
              example:
                expires_in: 1800
                link_url: https://t.me/SiteSpyBot?start=abc123def456
              schema:
                properties:
                  expires_in:
                    description: Link expiry time in seconds
                    type: integer
                  link_url:
                    description: Telegram deep-link URL to open
                    type: string
                type: object
          description: Connect link generated
        '400':
          description: Already connected or invalid request
        '401':
          description: Unauthorized - API key required
        '404':
          description: API key not found
        '500':
          description: Telegram bot not configured on server
      summary: Generate Telegram connect link
      tags:
      - Telegram Notifications
  /telegram/test:
    post:
      description: Send a test notification to verify Telegram is configured correctly.
      operationId: testTelegram
      responses:
        '200':
          content:
            application/json:
              example:
                message: Test message sent
                success: true
              schema:
                properties:
                  message:
                    type: string
                  success:
                    type: boolean
                type: object
          description: Test message sent
        '400':
          description: Telegram not configured or failed to send
        '401':
          description: Unauthorized - API key required
        '500':
          description: Telegram bot not configured on server
      summary: Send a test Telegram message
      tags:
      - Telegram Notifications
  /telegram/webhook:
    post:
      description: 'Receives updates from the Telegram Bot API. This endpoint is called
        by Telegram

        when users interact with the bot (e.g. /start command). Not intended for direct
        use.

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


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

          response = requests.get(''http://localhost:5000/api/v1/watch'', headers=headers)

          print(response.json())

          '
    post:
      description: 'Create a single web page change monitor (watch). Requires at least
        `url` to be set.


        Every watch can be configured with:

        - **Processor mode**: `processor` field (`restock_diff` or `text_json_diff`
        - default)

        - **Notification settings**: `notification_urls` (array), `notification_title`,
        `notification_body`, `notification_format`, `notification_muted`

        - **Tags/Groups**: `tag` (UUID string) or `tags` (array of UUIDs)

        - **Check settings**: `time_between_check`, `paused`, `method`, `fetch_backend`

        - **Advanced options**: `headers`, `body`, `proxy`, `browser_steps`, and more

        '
      operationId: createWatch
      requestBody:
        content:
          application/json:
            example:
              time_between_check:
                hours: 1
              title: Example Site Monitor
              url: https://example.com
            schema:
              $ref: '#/components/schemas/CreateWatch'
        required: true
      responses:
        '200':
          content:
            text/plain:
              schema:
                example: OK
                type: string
          description: Web page change monitor (watch) created successfully
        '403':
          content:
            application/json:
              schema:
                properties:
                  error:
                    enum:
                    - email_not_verified
                    - watch_limit_reached
                    example: email_not_verified
                    type: string
                  message:
                    example: Please verify your email address before creating watches.
                    type: string
                type: object
          description: Forbidden - email not verified or watch limit reached
        '500':
          content:
            text/plain:
              schema:
                type: string
          description: Server error
      summary: Create a new watch
      tags:
      - Watch Management
      x-code-samples:
      - lang: curl
        source: "curl -X POST \"http://localhost:5000/api/v1/watch\" \\\n  -H \"x-api-key:\
          \ YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n\
          \    \"url\": \"https://example.com\",\n    \"title\": \"Example Site Monitor\"\
          ,\n    \"time_between_check\": {\n      \"hours\": 1\n    }\n  }'\n"
      - lang: Python
        source: "import requests\nimport json\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ndata = {\n    'url': 'https://example.com',\n\
          \    'title': 'Example Site Monitor',\n    'time_between_check': {\n   \
          \     'hours': 1\n    }\n}\nresponse = requests.post('http://localhost:5000/api/v1/watch',\n\
          \                       headers=headers, json=data)\nprint(response.text)\n"
      - label: Restock & price tracking
        lang: curl
        source: "curl -X POST \"http://localhost:5000/api/v1/watch\" \\\n  -H \"x-api-key:\
          \ YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n\
          \    \"url\": \"https://example.com/product\",\n    \"processor\": \"restock_diff\"\
          ,\n    \"processor_config_restock_diff\": {\n      \"in_stock_processing\"\
          : \"in_stock_only\",\n      \"follow_price_changes\": true,\n      \"price_change_threshold_percent\"\
          : 5\n    }\n  }'\n"
      - label: Restock & price tracking
        lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\ndata = {\n    'url': 'https://example.com/product',\n\
          \    'processor': 'restock_diff',\n    'processor_config_restock_diff':\
          \ {\n        'in_stock_processing': 'in_stock_only',\n        'follow_price_changes':\
          \ True,\n        'price_change_threshold_percent': 5,\n    }\n}\nresponse\
          \ = requests.post('http://localhost:5000/api/v1/watch',\n              \
          \           headers=headers, json=data)\nprint(response.json())\n"
  /watch/{uuid}:
    delete:
      description: Delete a web page change monitor (watch) and all related history
      operationId: deleteWatch
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            text/plain:
              schema:
                example: OK
                type: string
          description: Web page change monitor (watch) deleted successfully
      summary: Delete watch
      tags:
      - Watch Management
      x-code-samples:
      - lang: curl
        source: "curl -X DELETE \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          uuid = ''095be615-a8ad-4c33-8e9c-c7612fbf6c9f''

          response = requests.delete(f''http://localhost:5000/api/v1/watch/{uuid}'',
          headers=headers)

          print(response.text)

          '
    get:
      description: Retrieve web page change monitor (watch) information and set muted/paused
        status. Returns the FULL Watch JSON.
      operationId: getWatch
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      - description: Recheck this web page change monitor (watch)
        in: query
        name: recheck
        schema:
          enum:
          - '1'
          - 'true'
          type: string
      - description: Set pause state
        in: query
        name: paused
        schema:
          enum:
          - paused
          - unpaused
          type: string
      - description: Set mute state
        in: query
        name: muted
        schema:
          enum:
          - muted
          - unmuted
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Watch'
            text/plain:
              schema:
                example: OK
                type: string
          description: Watch information or operation result
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Web page change monitor (watch) not found
      summary: Get single watch
      tags:
      - Watch Management
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          uuid = ''095be615-a8ad-4c33-8e9c-c7612fbf6c9f''

          response = requests.get(f''http://localhost:5000/api/v1/watch/{uuid}'',
          headers=headers)

          print(response.json())

          '
    put:
      description: Update an existing web page change monitor (watch) using JSON.
        Accepts the same structure as returned in [get single watch information](#operation/getWatch).
      operationId: updateWatch
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWatch'
        required: true
      responses:
        '200':
          content:
            text/plain:
              schema:
                example: OK
                type: string
          description: Web page change monitor (watch) updated successfully
        '500':
          description: Server error
      summary: Update watch
      tags:
      - Watch Management
      x-code-samples:
      - lang: curl
        source: "curl -X PUT \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\"\
          \ \\\n  -d '{\n    \"url\": \"https://updated-example.com\",\n    \"title\"\
          : \"Updated Monitor\",\n    \"paused\": false\n  }'\n"
      - lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\nuuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f'\n\
          data = {\n    'url': 'https://updated-example.com',\n    'title': 'Updated\
          \ Monitor',\n    'paused': False\n}\nresponse = requests.put(f'http://localhost:5000/api/v1/watch/{uuid}',\n\
          \                      headers=headers, json=data)\nprint(response.text)\n"
      - label: Update restock config
        lang: curl
        source: "curl -X PUT \"http://localhost:5000/api/v1/watch/YOUR-UUID\" \\\n\
          \  -H \"x-api-key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\"\
          \ \\\n  -d '{\n    \"processor_config_restock_diff\": {\n      \"in_stock_processing\"\
          : \"all_changes\",\n      \"follow_price_changes\": true,\n      \"price_change_min\"\
          : 10.00,\n      \"price_change_max\": 500.00\n    }\n  }'\n"
      - label: Update restock config
        lang: Python
        source: "import requests\n\nheaders = {\n    'x-api-key': 'YOUR_API_KEY',\n\
          \    'Content-Type': 'application/json'\n}\nuuid = 'YOUR-UUID'\ndata = {\n\
          \    'processor_config_restock_diff': {\n        'in_stock_processing':\
          \ 'all_changes',\n        'follow_price_changes': True,\n        'price_change_min':\
          \ 10.00,\n        'price_change_max': 500.00,\n    }\n}\nresponse = requests.put(f'http://localhost:5000/api/v1/watch/{uuid}',\n\
          \                        headers=headers, json=data)\nprint(response.text)\n"
  /watch/{uuid}/difference/{from_timestamp}/{to_timestamp}:
    get:
      description: 'Generate a difference (comparison) between two historical snapshots
        of a web page change monitor (watch).


        This endpoint compares content between two points in time and returns the
        differences in your chosen format.

        Perfect for reviewing what changed between specific versions or comparing
        recent changes.


        **Timestamp Keywords:**

        - Use `''latest''` for the most recent snapshot (to_timestamp)

        - Use `''previous''` for the second-most-recent snapshot (from_timestamp)

        - Or use specific Unix timestamps from the watch history


        **Format Options:**

        - `text` (default): Plain text with (removed) and (added) prefixes

        - `html`: HTML format with (removed) and (added) text

        - `htmlcolor`: Rich HTML with colored highlights (green for additions, red
        for deletions)


        **Word-Level Diffing:**

        - Enable word-level granularity with `word_diff=true` for detailed inline
        comparisons

        - Disable with `word_diff=false` for line-level comparisons only (default
        false/off, line-level mode by default)


        **Raw Diff Output:**

        - Use `no_markup=true` to get raw diff content without any formatting applied

        - Returns content with placeholders for opening/closing tags of changes

        - Allows you to implement your own custom colorisation or formatting

        - Skips all HTML color application and service tweaks (added text, html color
        tags, etc)

        '
      operationId: getWatchHistoryDiff
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      - description: Starting snapshot timestamp, 'previous' for second-most-recent,
          or specific Unix timestamp
        example: previous
        in: path
        name: from_timestamp
        required: true
        schema:
          oneOf:
          - description: Unix timestamp of the starting snapshot
            type: integer
          - description: Use 'previous' to automatically select the second-most-recent
              snapshot
            enum:
            - previous
            type: string
      - description: Ending snapshot timestamp, 'latest' for most recent, or specific
          Unix timestamp
        example: latest
        in: path
        name: to_timestamp
        required: true
        schema:
          oneOf:
          - description: Unix timestamp of the ending snapshot
            type: integer
          - description: Use 'latest' to automatically select the most recent snapshot
            enum:
            - latest
            type: string
      - description: 'Output format for the diff:

          - `text` (default): Plain text with (removed) and (added) prefixes

          - `html`: Basic HTML format

          - `htmlcolor`: Rich HTML with colored backgrounds (red for deletions, green
          for additions)

          - `markdown`: Markdown format with HTML rendering

          '
        in: query
        name: format
        schema:
          default: text
          enum:
          - text
          - html
          - htmlcolor
          - markdown
          type: string
      - description: 'Enable word-level diffing for more granular comparisons.

          When enabled, changes are highlighted at the word level rather than line
          level.

          Default is false (line-level mode).

          Accepts: true, false, 1, 0, yes, no, on, off

          '
        in: query
        name: word_diff
        schema:
          default: 'false'
          enum:
          - 'true'
          - 'false'
          - '1'
          - '0'
          - 'yes'
          - 'no'
          - 'on'
          - 'off'
          type: string
      - description: 'When set to true, returns the raw diff content without any markup
          formatting.

          The content will include placeholders for opening/closing tags of the changes,

          allowing you to implement your own custom colorisation or formatting.

          This skips all HTML color application and service tweaks.

          Accepts: true, false, 1, 0, yes, no, on, off

          '
        in: query
        name: no_markup
        schema:
          default: 'false'
          enum:
          - 'true'
          - 'false'
          - '1'
          - '0'
          - 'yes'
          - 'no'
          - 'on'
          - 'off'
          type: string
      - description: 'Diff granularity type:

          - `diffLines` (default): Line-level comparison, showing which lines changed

          - `diffWords`: Word-level comparison, showing which words changed within
          lines


          This parameter is an alternative to `word_diff` for better alignment with
          the UI.

          If both are specified, `type=diffWords` will enable word-level diffing.

          '
        in: query
        name: type
        schema:
          default: diffLines
          enum:
          - diffLines
          - diffWords
          type: string
      - description: 'When enabled, only show lines/content that changed (no surrounding
          context).

          When disabled, include unchanged lines for context around changes.

          Accepts: true, false, 1, 0, yes, no, on, off

          '
        in: query
        name: changesOnly
        schema:
          default: 'true'
          enum:
          - 'true'
          - 'false'
          - '1'
          - '0'
          - 'yes'
          - 'no'
          - 'on'
          - 'off'
          type: string
      - description: 'When enabled, ignore whitespace-only changes (spaces, tabs,
          newlines).

          Useful for focusing on content changes and ignoring formatting differences.

          Accepts: true, false, 1, 0, yes, no, on, off

          '
        in: query
        name: ignoreWhitespace
        schema:
          default: 'false'
          enum:
          - 'true'
          - 'false'
          - '1'
          - '0'
          - 'yes'
          - 'no'
          - 'on'
          - 'off'
          type: string
      - description: 'Include removed/deleted content in the diff output.

          When disabled, content that was deleted will not appear in the diff.

          Accepts: true, false, 1, 0, yes, no, on, off

          '
        in: query
        name: removed
        schema:
          default: 'true'
          enum:
          - 'true'
          - 'false'
          - '1'
          - '0'
          - 'yes'
          - 'no'
          - 'on'
          - 'off'
          type: string
      - description: 'Include added/new content in the diff output.

          When disabled, content that was added will not appear in the diff.

          Accepts: true, false, 1, 0, yes, no, on, off

          '
        in: query
        name: added
        schema:
          default: 'true'
          enum:
          - 'true'
          - 'false'
          - '1'
          - '0'
          - 'yes'
          - 'no'
          - 'on'
          - 'off'
          type: string
      - description: 'Include replaced/modified content in the diff output.

          When disabled, content that was modified (changed from one value to another)
          will not appear in the diff.

          Accepts: true, false, 1, 0, yes, no, on, off

          '
        in: query
        name: replaced
        schema:
          default: 'true'
          enum:
          - 'true'
          - 'false'
          - '1'
          - '0'
          - 'yes'
          - 'no'
          - 'on'
          - 'off'
          type: string
      responses:
        '200':
          content:
            text/html:
              schema:
                description: HTML formatted diff with styling
                type: string
            text/plain:
              schema:
                description: Plain text diff with change markers
                type: string
          description: Formatted diff between the two snapshots
        '400':
          description: Invalid format parameter or invalid request
        '404':
          description: Watch not found, timestamps not found, or insufficient history
      summary: Get the difference between two snapshots
      tags:
      - Watch History
      x-code-samples:
      - lang: curl
        source: "# Compare previous snapshot to latest with colored HTML\ncurl -X\
          \ GET \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/difference/previous/latest?format=htmlcolor\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n\n# Compare two specific timestamps\
          \ in plain text with word-level diff\ncurl -X GET \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/difference/1640995200/1640998800?format=text&word_diff=true\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n\n# Show only additions (hide removed/replaced\
          \ content), ignore whitespace\ncurl -X GET \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/difference/previous/latest?format=htmlcolor&removed=false&replaced=false&ignoreWhitespace=true\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: "import requests\n\nheaders = {'x-api-key': 'YOUR_API_KEY'}\nuuid\
          \ = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f'\n\n# Compare previous to latest\
          \ with colored HTML output\nresponse = requests.get(\n    f'http://localhost:5000/api/v1/watch/{uuid}/difference/previous/latest',\n\
          \    headers=headers,\n    params={'format': 'htmlcolor'}\n)\nprint(response.text)\n\
          \n# Compare specific timestamps with word-level diff\nfrom_ts = '1640995200'\n\
          to_ts = '1640998800'\nresponse = requests.get(\n    f'http://localhost:5000/api/v1/watch/{uuid}/difference/{from_ts}/{to_ts}',\n\
          \    headers=headers,\n    params={'format': 'text', 'word_diff': 'true'}\n\
          )\nprint(response.text)\n\n# Show only additions, ignore whitespace and\
          \ use word-level diff\nresponse = requests.get(\n    f'http://localhost:5000/api/v1/watch/{uuid}/difference/previous/latest',\n\
          \    headers=headers,\n    params={\n        'format': 'htmlcolor',\n  \
          \      'type': 'diffWords',\n        'removed': 'false',\n        'replaced':\
          \ 'false',\n        'ignoreWhitespace': 'true'\n    }\n)\nprint(response.text)\n"
  /watch/{uuid}/favicon:
    get:
      description: Get the favicon for a web page change monitor (watch) as displayed
        in the watch overview list.
      operationId: getWatchFavicon
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            image/*:
              schema:
                format: binary
                type: string
          description: Favicon binary data
        '404':
          description: Favicon not found
      summary: Get watch favicon
      tags:
      - Favicon
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/favicon\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\" \\\n  --output favicon.ico\n"
      - lang: Python
        source: "import requests\n\nheaders = {'x-api-key': 'YOUR_API_KEY'}\nuuid\
          \ = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f'\nresponse = requests.get(f'http://localhost:5000/api/v1/watch/{uuid}/favicon',\
          \ headers=headers)\nwith open('favicon.ico', 'wb') as f:\n    f.write(response.content)\n"
  /watch/{uuid}/history:
    get:
      description: 'Get a list of all historical snapshots available for a web page
        change monitor (watch), use the key `timestamp`

        as the query argument for fetching a single watch history snapshot.

        '
      operationId: getWatchHistory
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            application/json:
              example:
                '1640995200': /path/to/snapshot1.txt
                '1640998800': /path/to/snapshot2.txt
              schema:
                $ref: '#/components/schemas/WatchHistory'
          description: List of available snapshots
        '404':
          description: Web page change monitor (watch) not found
      summary: Get watch history
      tags:
      - Watch History
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/history\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          uuid = ''095be615-a8ad-4c33-8e9c-c7612fbf6c9f''

          response = requests.get(f''http://localhost:5000/api/v1/watch/{uuid}/history'',
          headers=headers)

          print(response.json())

          '
  /watch/{uuid}/history/{timestamp}:
    get:
      description: 'Get single snapshot from web page change monitor (watch). Use
        ''latest'' for the most recent snapshot.

        Use the Watch History API to get a list of timestamps to pass.

        '
      operationId: getWatchSnapshot
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      - description: Snapshot timestamp or 'latest'
        in: path
        name: timestamp
        required: true
        schema:
          oneOf:
          - type: integer
          - enum:
            - latest
            type: string
      - description: Set to 1 to return the last HTML
        in: query
        name: html
        schema:
          enum:
          - '1'
          type: string
      responses:
        '200':
          content:
            text/plain:
              schema:
                type: string
          description: Snapshot content
        '404':
          description: Snapshot not found
      summary: Get single snapshot
      tags:
      - Snapshots
      x-code-samples:
      - lang: curl
        source: "curl -X GET \"http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/history/latest\"\
          \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n"
      - lang: Python
        source: 'import requests


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

          uuid = ''095be615-a8ad-4c33-8e9c-c7612fbf6c9f''

          timestamp = ''latest''  # or use specific timestamp like 1640995200

          response = requests.get(f''http://localhost:5000/api/v1/watch/{uuid}/history/{timestamp}'',
          headers=headers)

          print(response.text)

          '
  /watch/{uuid}/screenshot/diff:
    get:
      description: 'Returns a rendered diff image (JPEG) showing changed areas highlighted
        in red.

        Compares two screenshot snapshots using OpenCV.

        Requires Pro plan.

        '
      operationId: getWatchScreenshotDiff
      parameters:
      - in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      - in: query
        name: from_version
        schema:
          type: string
      - in: query
        name: to_version
        schema:
          type: string
      - description: Which asset to return (before, after, or rendered_diff)
        in: query
        name: asset
        schema:
          default: rendered_diff
          enum:
          - before
          - after
          - rendered_diff
          type: string
      responses:
        '200':
          content:
            image/jpeg:
              schema:
                format: binary
                type: string
          description: Diff overlay image
        '400':
          description: Watch does not use screenshot monitoring
        '403':
          description: Pro plan required
        '404':
          description: Could not generate diff
      security:
      - ApiKeyAuth: []
      summary: Get rendered diff overlay image
      tags:
      - Visual Diff
  /watch/{uuid}/screenshot/diff/metadata:
    get:
      description: 'Returns metadata about the visual diff comparison including change
        percentage,

        threshold setting, and available versions.

        Requires Pro plan.

        '
      operationId: getWatchScreenshotDiffMetadata
      parameters:
      - in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      - in: query
        name: from_version
        schema:
          type: string
      - in: query
        name: to_version
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  change_percentage:
                    nullable: true
                    type: number
                  from_version:
                    type: string
                  threshold:
                    type: number
                  to_version:
                    type: string
                  versions:
                    items:
                      type: string
                    type: array
                type: object
          description: Visual diff metadata
        '400':
          description: Watch does not use screenshot monitoring
        '403':
          description: Pro plan required
      security:
      - ApiKeyAuth: []
      summary: Get visual diff metadata
      tags:
      - Visual Diff
  /watch/{uuid}/screenshot/history:
    get:
      description: Returns the list of screenshot timestamps available for a watch
        with screenshot monitoring enabled.
      operationId: getWatchScreenshotHistory
      parameters:
      - description: Web page change monitor (watch) unique ID
        in: path
        name: uuid
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  timestamps:
                    items:
                      type: string
                    type: array
                type: object
          description: Screenshot history timestamps
        '400':
          description: Watch does not have screenshot monitoring enabled
        '403':
          description: Pro plan required
        '404':
          description: Watch not found
      security:
      - ApiKeyAuth: []
      summary: Get screenshot history timestamps
      tags:
      - Screenshots
  /watch/{uuid}/screenshot/{timestamp}:
    get:
      description: 'Returns the screenshot image (PNG/JPEG) for a watch that uses
        the `image_ssim_diff` processor.

        Requires Pro plan.

        '
      operationId: getWatchScreenshot
      parameters:
      - in: path
        name: uuid
        required: true
        schema:
          format: uuid
          type: string
      - in: path
        name: timestamp
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            image/png:
              schema:
                format: binary
                type: string
          description: Screenshot image
        '400':
          description: Watch does not use screenshot monitoring
        '403':
          description: Pro plan required
        '404':
          description: Watch or snapshot not found
      security:
      - ApiKeyAuth: []
      summary: Get a screenshot for a watch at a specific timestamp
      tags:
      - Visual Diff
  /webpush/subscription:
    delete:
      description: 'Remove a specific push subscription by endpoint, or remove all
        subscriptions

        if no endpoint is provided.

        '
      operationId: unsubscribeWebPush
      requestBody:
        content:
          application/json:
            schema:
              properties:
                endpoint:
                  description: Specific subscription endpoint to remove. Omit to remove
                    all.
                  type: string
              type: object
      responses:
        '200':
          description: Subscription(s) removed
        '404':
          description: API key not found
      summary: Remove push subscription(s)
      tags:
      - Web Push Notifications
    get:
      description: Check whether the current API key has any active push subscriptions.
      operationId: getWebPushStatus
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  connected:
                    type: boolean
                  subscription_count:
                    type: integer
                type: object
          description: Subscription status
      summary: Get web push subscription status
      tags:
      - Web Push Notifications
    post:
      description: Store a browser push subscription for the current API key.
      operationId: subscribeWebPush
      requestBody:
        content:
          application/json:
            schema:
              properties:
                subscription:
                  properties:
                    endpoint:
                      type: string
                    keys:
                      properties:
                        auth:
                          type: string
                        p256dh:
                          type: string
                      required:
                      - p256dh
                      - auth
                      type: object
                  required:
                  - endpoint
                  - keys
                  type: object
              required:
              - subscription
              type: object
        required: true
      responses:
        '201':
          description: Subscription saved
        '400':
          description: Invalid subscription data
        '404':
          description: API key not found
      summary: Save a push subscription
      tags:
      - Web Push Notifications
  /webpush/test:
    post:
      description: Send a test push notification to all subscribed browsers.
      operationId: testWebPush
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    type: string
                  success:
                    type: boolean
                type: object
          description: Test notification sent
        '400':
          description: No subscriptions or failed to send
        '401':
          description: Unauthorized - API key required
        '503':
          description: Web push not configured on server
      summary: Send a test push notification
      tags:
      - Web Push Notifications
  /webpush/vapid-key:
    get:
      description: Returns the server's public VAPID key needed to subscribe to push
        notifications.
      operationId: getWebPushVapidKey
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  public_key:
                    description: Base64url-encoded public VAPID key
                    type: string
                type: object
          description: VAPID key returned
        '503':
          description: Web push not configured on this server
      summary: Get the public VAPID key
      tags:
      - Web Push Notifications
security:
- ApiKeyAuth: []
servers:
- description: Development server
  url: http://localhost:5000/api/v1
- description: Production server
  url: https://yourdomain.com/api/v1
- description: Custom server
  url: '{protocol}://{host}/api/v1'
  variables:
    host:
      default: yourdomain.com
      description: Your changedetection.io host
    protocol:
      default: https
      enum:
      - http
      - https
tags:
- 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 Management
- description: 'Get a list of timestamps of all changes detected for a watch.

    '
  name: Watch History
- 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: Snapshots
- 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: Favicon
- 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: Tag Management
- 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: Notifications
- 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: User Settings
- 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: Search
- 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: Import
- description: 'Retrieve system status and statistics about your changedetection.io
    instance, including total watch

    counts, uptime information, and version details.

    '
  name: System Information
- 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: Email Verification
- 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: Telegram 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: Web Push Notifications
- 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: RSS Feeds
- 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: API Key Management
- description: 'Retrieve feature flags for the current API key. Feature flags control
    which features are

    enabled or disabled for specific users.

    '
  name: Feature Flags
- 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: Authentication
- 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: 5, Starter: 25, Pro: 100).

    '
  name: Plan
- description: 'Manage subscriptions via Lemon Squeezy. Create checkout sessions to
    upgrade, access the

    customer billing portal, and receive webhook events from Lemon Squeezy.

    '
  name: Billing
- description: 'Retrieve the live OpenAPI specification for this instance. Unlike
    the static spec, this endpoint

    returns the fully merged spec including schemas for any processor plugins installed
    on this instance.

    '
  name: Plugin API Extensions
