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

# Test a discovery watch

> Delivers a sample notification so you can verify a receiver end to end before waiting for a real
run.

The sample is built from **real current matches** of the watch's own filter, ignoring the
watermark and the already-seen set, and shaped through the same processor a real run uses, so
the payload is identical to production apart from `metadata.test: true`. Nothing is recorded:
no run, no seen-set advance, no credit, no persistence.

Every body field is optional. Send `deliver: false` to get the envelope back without any
outbound request. If the watch's filter currently matches nothing there is nothing to sample,
and the call returns `400` with the envelope still attached.




## OpenAPI

````yaml /openapi-specs/2025-11-01/watch.yaml post /watch/{dataset}/search/{watch_id}/test
openapi: 3.0.3
info:
  title: Watch API
  version: '2025-11-01'
  description: >
    The Watch API turns a Crustdata query into a recurring feed. A watch runs on
    your schedule and

    delivers only what is new or what changed since its previous run, to a
    webhook, Slack, Google Chat,

    or email.


    There are two kinds of watch, and the kind decides the URL:


    - **Discovery watches** (`/watch/{dataset}/search`) re-run a *search filter*
    and deliver records that
      newly match it. Use one to find people, companies, or jobs you do not know yet. Available for
      `person`, `company`, and `job`.
    - **Entity watches** (`/watch/{dataset}`) monitor a *list you supply* and
    deliver a notification when
      a tracked field on one of those records changes. Use one to follow a known set. Available for
      `person` and `company`.

    Both kinds share the same `config`, `notifications`, and run-history
    surfaces, and both start with a

    free baseline run: a discovery watch delivers a sample of up to 5 current
    matches, an entity watch

    records each subject's starting values and never fires. Every request needs
    an `Authorization` Bearer

    API key and the `x-api-version: 2025-11-01` header.


    **Credits.** A discovery watch is charged per new record delivered: 0.5 for
    a person, 2 for a company,

    0.5 for a job. An entity watch is charged per notification, tiered by
    `config.refresh_frequency_days`:

    5 credits at the 30-day default, 10 at 14 days, 20 at 7 days, 50 at 3 days,
    150 at 1 day. A run that

    finds nothing costs nothing, and the baseline run is free for both kinds.


    **Rate limit.** Watch-management requests are limited to 10 requests per
    minute per API key.


    **Access.** Creating a watch is open to any authenticated API customer, and
    the 10-requests-per-minute

    limit is what bounds abuse. What is gated is the data underneath: a create
    is rejected with `403` when

    the key is not entitled to the dataset, or when `fields` or `track` name
    field groups the key may not

    receive. Contact info@crustdata.com to request access.
servers:
  - url: https://api.crustdata.com
    description: Production API server
security:
  - bearerAuth: []
tags:
  - name: Watch APIs
    description: Recurring feeds over the Crustdata datasets
  - name: Discovery Watches
    description: Watches that re-run a search filter and deliver newly matching records
  - name: Entity Watches
    description: >-
      Watches that monitor a supplied list of people or companies for field
      changes
  - name: Watch Runs
    description: Run history and per-run delivery detail, shared by both watch kinds
paths:
  /watch/{dataset}/search/{watch_id}/test:
    post:
      tags:
        - Watch APIs
        - Discovery Watches
      summary: Send a test notification from a discovery watch
      description: >
        Delivers a sample notification so you can verify a receiver end to end
        before waiting for a real

        run.


        The sample is built from **real current matches** of the watch's own
        filter, ignoring the

        watermark and the already-seen set, and shaped through the same
        processor a real run uses, so

        the payload is identical to production apart from `metadata.test: true`.
        Nothing is recorded:

        no run, no seen-set advance, no credit, no persistence.


        Every body field is optional. Send `deliver: false` to get the envelope
        back without any

        outbound request. If the watch's filter currently matches nothing there
        is nothing to sample,

        and the call returns `400` with the envelope still attached.
      operationId: testDiscoveryWatch
      parameters:
        - $ref: '#/components/parameters/DatasetPath'
        - $ref: '#/components/parameters/WatchId'
        - $ref: '#/components/parameters/ApiVersion'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DiscoveryTestRequest'
            examples:
              configured_channels:
                summary: Deliver one sample to the watch's own channels
                value: {}
              override_endpoint:
                summary: Deliver to a request-inspection URL instead
                value:
                  notification_endpoint: https://your-server.example.com/webhook
                  count: 3
              envelope_only:
                summary: Return the envelope without sending anything
                value:
                  deliver: false
      responses:
        '200':
          description: >-
            A delivery was attempted. Read `delivered[].http_status` for each
            channel's outcome.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestSendResponse'
        '400':
          description: >
            A body field failed validation, the endpoint override is not a safe
            public URL, the

            watch has no channel configured, or the filter currently matches
            nothing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestSendErrorResponse'
              examples:
                no_matches:
                  summary: The filter matches nothing right now
                  value:
                    delivered: []
                    error: >-
                      This watch's filter currently matches 0 records - nothing
                      to sample. Broaden the filter or try again later.
                no_channel:
                  summary: No channel configured and no override given
                  value:
                    delivered: []
                    error: >-
                      No delivery channel configured. Pass a
                      'notification_endpoint' to test.
                bad_count:
                  summary: count out of range
                  value:
                    error: count must be an integer in [1, 25].
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/WatchForbidden'
        '404':
          $ref: '#/components/responses/WatchNotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: The test notification could not be sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
              example:
                error: Failed to send test notification.
components:
  parameters:
    DatasetPath:
      name: dataset
      in: path
      required: true
      description: The dataset the watch is built on.
      schema:
        type: string
        enum:
          - person
          - company
          - job
        example: person
    WatchId:
      name: watch_id
      in: path
      required: true
      description: The watch's `id`, returned when it was created.
      schema:
        type: integer
        example: 46936
    ApiVersion:
      name: x-api-version
      in: header
      required: true
      schema:
        type: string
        enum:
          - '2025-11-01'
        default: '2025-11-01'
        example: '2025-11-01'
      description: >
        API version to use. `2025-11-01` is the only accepted value. Send it on
        every call: it is

        enforced today on `POST /watch/{dataset}/search`, where a missing or
        unrecognized value returns

        `400`, and the other watch routes accept a request without it rather
        than relying on that.
  schemas:
    DiscoveryTestRequest:
      type: object
      description: >-
        Options for a discovery watch's test send. Every field is optional, so
        an empty object is a valid body.
      properties:
        notification_endpoint:
          type: string
          format: uri
          description: >
            Deliver this one test to a URL you control instead of the watch's
            configured channels,

            which pairs well with a request-inspection service. Must be a public
            `http(s)` URL:

            endpoints resolving to private, loopback, or link-local addresses
            are rejected.
          example: https://your-server.example.com/webhook
        count:
          type: integer
          minimum: 1
          maximum: 25
          default: 1
          description: >-
            How many real current matches to include in the one envelope, so you
            can exercise multi-record handling.
          example: 3
        deliver:
          type: boolean
          default: true
          description: >-
            Set `false` to build and return the envelope without sending
            anything.
          example: true
    TestSendResponse:
      type: object
      description: >-
        The result of a test send, carrying what each channel answered and the
        exact envelope that was delivered.
      properties:
        delivered:
          type: array
          description: >-
            One entry per channel the test was sent to. Empty when `deliver` was
            `false`.
          items:
            $ref: '#/components/schemas/DeliveryAttempt'
        envelope:
          $ref: '#/components/schemas/NotificationEnvelope'
    TestSendErrorResponse:
      type: object
      description: >
        A test send that could not be attempted. The envelope is still returned
        when one was built, so

        you can inspect the shape even though nothing went out.
      properties:
        delivered:
          type: array
          items:
            $ref: '#/components/schemas/DeliveryAttempt'
        envelope:
          $ref: '#/components/schemas/NotificationEnvelope'
        error:
          type: string
          description: What stopped the delivery.
          example: >-
            No delivery channel configured. Pass a 'notification_endpoint' to
            test.
    SimpleErrorResponse:
      type: object
      description: Error payload with a single human-readable message.
      required:
        - error
      example:
        error: Watch not found
      properties:
        error:
          type: string
          description: Human-readable description of what went wrong.
          example: Watch not found
    DeliveryAttempt:
      type: object
      description: The outcome of one channel's delivery attempt.
      properties:
        type:
          type: string
          description: The channel type the attempt used.
          example: webhook
        endpoint:
          type: string
          description: Where it was delivered.
          example: https://your-server.example.com/webhook
        http_status:
          type: integer
          nullable: true
          description: >-
            Status your receiver answered with. `null` if the request never
            completed.
          example: 200
        ok:
          type: boolean
          description: True when `http_status` was 2xx.
          example: true
        response:
          type: string
          description: Your receiver's response body, truncated to 500 characters.
          example: '{"received": true}'
    NotificationEnvelope:
      type: object
      description: >
        The body POSTed to each webhook channel, and the same body a test send
        returns under `envelope`.


        The two watch kinds shape `results` differently. A discovery watch keys
        results by membership

        polarity, so records arrive under `results.added`, each one the raw
        dataset record. An entity

        watch delivers a flat list, one object per fired subject, each with its
        own `changes` and

        `record`.
      properties:
        metadata:
          type: object
          properties:
            watch_id:
              type: integer
              example: 46936
            kind:
              type: string
              enum:
                - discovery
                - entity
              example: entity
            dataset:
              type: string
              enum:
                - person
                - company
                - job
              example: person
            api_version:
              type: string
              example: '2025-11-01'
            run_id:
              type: integer
              example: 64200
            notification_id:
              type: string
              description: >-
                Unique id for this delivery, echoed in the
                `x-crustdata-event-id` header. Key your receiver's deduplication
                on it.
              example: ntf_64200
            delivered_at:
              type: string
              format: date-time
              example: '2026-07-16T03:20:00.351849+00:00'
            test:
              type: boolean
              description: >-
                Present and `true` only on a test send. Absent on a real
                delivery.
              example: true
            summary:
              type: object
              properties:
                delivered:
                  type: integer
                  description: Records in this envelope.
                  example: 1
                total_count:
                  type: integer
                  description: >-
                    All records the run matched, which can exceed `delivered`
                    when the run was capped.
                  example: 1
                max_results_per_run:
                  type: integer
                  example: 100
                truncated:
                  type: boolean
                  description: True when `total_count` exceeded `delivered`.
                  example: false
        results:
          description: >-
            The payload. An object keyed by polarity for a discovery watch, a
            flat array for an entity watch.
          oneOf:
            - $ref: '#/components/schemas/DiscoveryResults'
            - $ref: '#/components/schemas/EntityResults'
    AuthErrorResponse:
      type: object
      description: >
        Authentication error payload. The message distinguishes the two
        failures: `Missing API key in

        request` when no `Authorization` header was sent, and `Invalid API key
        in request` when the

        Bearer token is not a valid key.
      required:
        - message
      example:
        message: Invalid API key in request
      properties:
        message:
          type: string
          description: Human-readable authentication error message.
          example: Invalid API key in request
    StructuredErrorResponse:
      type: object
      description: >-
        Structured error payload, used for rate-limit and API-version failures.
        The flatter `SimpleErrorResponse` shape is used elsewhere, so parse
        defensively.
      required:
        - error
      example:
        error:
          type: rate_limit_error
          message: >-
            Rate limit exceeded for this endpoint. Please write to
            gtm@crustdata.co.
          metadata: []
      properties:
        error:
          type: object
          required:
            - type
            - message
          properties:
            type:
              type: string
              description: Machine-readable error type identifier.
              example: rate_limit_error
            message:
              type: string
              description: Human-readable description of what went wrong.
              example: >-
                Rate limit exceeded for this endpoint. Please write to
                gtm@crustdata.co.
            metadata:
              type: array
              description: Extra context, empty on most errors.
              items:
                type: object
                additionalProperties: true
              example: []
    DiscoveryResults:
      type: object
      description: >-
        Discovery payload. Records that newly matched the filter arrive under
        `added`.
      properties:
        added:
          type: array
          description: >
            The newly matching records, each in the dataset's own record shape
            projected to the

            watch's `fields`. A person or company record is the enrich shape; a
            job record carries

            `job_details`, `company`, `location`, and `metadata`.
          items:
            type: object
            additionalProperties: true
          example:
            - job_details:
                title: Backend Engineer, Vehicle Software
                url: https://www.example-jobs.com/postings/4448814407
                category: Engineering
                workplace_type: On-site
                number_of_openings: 1
              company:
                basic_info:
                  name: Tesla
                  primary_domain: tesla.com
              location:
                raw: Palo Alto, California, United States
                country: United States
              metadata:
                date_added: '2026-08-04T05:32:47'
    EntityResults:
      type: array
      description: Entity payload. One object per subject that fired on this run.
      items:
        type: object
        properties:
          changes:
            type: array
            description: >-
              Exactly what moved. Reported whatever `fields` says, so a change
              is always legible even with the default projection.
            items:
              $ref: '#/components/schemas/RecordChange'
          record:
            type: object
            additionalProperties: true
            description: >
              The subject's current data, projected to the watch's `fields`, in
              the same shape the

              dataset's enrich endpoint returns. The subject's identity travels
              inside the record

              as `crustdata_person_id` or `crustdata_company_id`.
    RecordChange:
      type: object
      description: >
        One field-level change. A `changed` scalar reports `from` and `to`. An
        `added` array reports

        `new_elements`. On a comparison operator, `value` carries the threshold
        that was crossed.
      properties:
        field:
          type: string
          example: experience.employment_details.current
        type:
          type: string
          enum:
            - changed
            - added
          example: added
        from:
          nullable: true
          description: Previous value, on a `changed` scalar.
        to:
          nullable: true
          description: New value, on a `changed` scalar.
        value:
          nullable: true
          description: >-
            The threshold target for a comparison operator, `null` for a plain
            `changed`.
        new_elements:
          type: array
          description: The elements that appeared, on an `added` array.
          items:
            type: object
            additionalProperties: true
  responses:
    Unauthorized:
      description: The API key is missing or not valid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AuthErrorResponse'
          examples:
            invalid_api_key:
              summary: Bearer token present but not a valid key
              value:
                message: Invalid API key in request
            missing_api_key:
              summary: No Authorization header at all
              value:
                message: Missing API key in request
    WatchForbidden:
      description: >
        The key is not entitled to this dataset, or to a field group the request
        names. The watch

        surface itself is open to any authenticated API customer.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SimpleErrorResponse'
          example:
            error: >-
              You do not have access to the person dataset. Contact
              info@crustdata.com to request access.
    WatchNotFound:
      description: No watch with that id belongs to this key on this dataset.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SimpleErrorResponse'
          example:
            error: Watch not found
    RateLimited:
      description: >
        Too many watch requests. The per-endpoint limit is 10 requests per
        minute per API key, which is

        what bounds bursty create and update loops. Steady use is unaffected.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/StructuredErrorResponse'
          example:
            error:
              type: rate_limit_error
              message: >-
                Rate limit exceeded for this endpoint. Please write to
                gtm@crustdata.co.
              metadata: []
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key passed as a Bearer token in the Authorization header.

````

## Related topics

- [Test an entity watch](/api-reference/watch-apis/send-a-test-notification-from-an-entity-watch.md)
- [Get a discovery watch](/api-reference/watch-apis/get-one-discovery-watch.md)
- [Update a discovery watch](/api-reference/watch-apis/pause-resume-or-reconfigure-a-discovery-watch.md)
- [List discovery watches](/api-reference/watch-apis/list-your-discovery-watches.md)
- [Cancel a discovery watch](/api-reference/watch-apis/cancel-a-discovery-watch.md)
