> ## 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 an entity watch

> Pushes one sample notification through a saved entity watch so you can verify a receiver end to
end: signature check, JSON parsing, routing, without waiting for a subject to actually move.

The envelope is exactly what a real run sends, flagged with `metadata.test: true`. The record
is built from the dataset's own enrich model and filled with a fixed, public demo profile, so
your parser sees production structure without real subject data. The signature is computed the
same way as a real delivery, HMAC-SHA256 over `<timestamp>.<raw-body>` keyed by your API key,
sent as `x-crustdata-signature: t=<unix>,v1=<hex>`.

It runs no diff, records no snapshot, costs no credits, and persists nothing. All body fields
are optional.




## OpenAPI

````yaml /openapi-specs/2025-11-01/watch.yaml post /watch/{dataset}/{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}/{watch_id}/test:
    post:
      tags:
        - Watch APIs
        - Entity Watches
      summary: Send a test notification from an entity watch
      description: >
        Pushes one sample notification through a saved entity watch so you can
        verify a receiver end to

        end: signature check, JSON parsing, routing, without waiting for a
        subject to actually move.


        The envelope is exactly what a real run sends, flagged with
        `metadata.test: true`. The record

        is built from the dataset's own enrich model and filled with a fixed,
        public demo profile, so

        your parser sees production structure without real subject data. The
        signature is computed the

        same way as a real delivery, HMAC-SHA256 over `<timestamp>.<raw-body>`
        keyed by your API key,

        sent as `x-crustdata-signature: t=<unix>,v1=<hex>`.


        It runs no diff, records no snapshot, costs no credits, and persists
        nothing. All body fields

        are optional.
      operationId: testEntityWatch
      parameters:
        - $ref: '#/components/parameters/EntityDatasetPath'
        - $ref: '#/components/parameters/WatchId'
        - $ref: '#/components/parameters/ApiVersion'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EntityTestRequest'
            examples:
              override_endpoint_and_fields:
                summary: Deliver to your own URL with a chosen field projection
                value:
                  notification_endpoint: https://your-server.example.com/webhook
                  fields:
                    - basic_profile
                    - experience
              multi_record:
                summary: Three sample subjects in one envelope
                value:
                  count: 3
              configured_channels:
                summary: Deliver one sample to the watch's own channels
                value: {}
      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, or the watch has no channel configured.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestSendErrorResponse'
              examples:
                no_channel:
                  summary: No channel configured and no override given
                  value:
                    delivered: []
                    error: >-
                      No delivery channel configured. Pass a
                      'notification_endpoint' to test.
                unsafe_endpoint:
                  summary: The override resolves to a private address
                  value:
                    error: notification_endpoint must be a public http(s) URL.
        '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:
    EntityDatasetPath:
      name: dataset
      in: path
      required: true
      description: >
        The dataset the watch is built on. Entity watches support `person` and
        `company`; there is no

        job entity watch, and `job` returns `404` here. Use `/watch/job/search`
        for jobs.
      schema:
        type: string
        enum:
          - person
          - company
        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:
    EntityTestRequest:
      type: object
      description: Options for an entity watch's test send. Every field is optional.
      properties:
        notification_endpoint:
          type: string
          format: uri
          description: >-
            Deliver this one test to a public URL you control instead of the
            watch's configured channels.
          example: https://your-server.example.com/webhook
        fields:
          type: array
          items:
            type: string
          description: >-
            Project the sample `record` to a chosen field set for this test.
            Defaults to the watch's own `fields`, and is entitlement-checked
            like a create.
          example:
            - basic_profile
            - experience
        count:
          type: integer
          minimum: 1
          maximum: 25
          default: 1
          description: >-
            Deliver this many sample subjects in one envelope. Each gets a
            distinct record id.
          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 a discovery watch](/api-reference/watch-apis/send-a-test-notification-from-a-discovery-watch.md)
- [Company Entity Watcher](/watcher-docs/company/entity.md)
- [Person Entity Watcher](/watcher-docs/person/entity.md)
- [Update an entity watch](/api-reference/watch-apis/edit-the-watched-list-pause-or-reconfigure-an-entity-watch.md)
- [List entity watches](/api-reference/watch-apis/list-your-entity-watches.md)
