> ## 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.

# Preview an entity notification

> Builds a sample notification from a `track` and `fields` you have not saved yet, so a create
form can show or deliver the exact envelope before the watch exists.

The sample record is generated by walking the dataset's own enrich model, so every trackable
field is present in the exact production shape and the sample never drifts from the schema. Its
values are a fixed, public demo profile rather than a real subject of yours. It is signed
exactly as a real delivery is, so a receiver that verifies here verifies production, and
`metadata.watch_id` is `0` because no watch exists yet.

Same guarantees as the saved-watch test: no run, no snapshot, no credit, nothing persisted.




## OpenAPI

````yaml /openapi-specs/2025-11-01/watch.yaml post /watch/{dataset}/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}/test:
    post:
      tags:
        - Watch APIs
        - Entity Watches
      summary: Preview a notification before creating an entity watch
      description: >
        Builds a sample notification from a `track` and `fields` you have not
        saved yet, so a create

        form can show or deliver the exact envelope before the watch exists.


        The sample record is generated by walking the dataset's own enrich
        model, so every trackable

        field is present in the exact production shape and the sample never
        drifts from the schema. Its

        values are a fixed, public demo profile rather than a real subject of
        yours. It is signed

        exactly as a real delivery is, so a receiver that verifies here verifies
        production, and

        `metadata.watch_id` is `0` because no watch exists yet.


        Same guarantees as the saved-watch test: no run, no snapshot, no credit,
        nothing persisted.
      operationId: previewEntityWatchNotification
      parameters:
        - $ref: '#/components/parameters/EntityDatasetPath'
        - $ref: '#/components/parameters/ApiVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EntityTestPreviewRequest'
            examples:
              preview_only:
                summary: Build the envelope without delivering it
                value:
                  track:
                    field: experience.employment_details.current
                    type: added
                  fields:
                    - basic_profile
                    - experience
                  deliver: false
              deliver_to_endpoint:
                summary: Deliver three samples to a URL you control
                value:
                  track:
                    field: basic_profile.current_title
                    type: changed
                  notification_endpoint: https://your-server.example.com/webhook
                  count: 3
      responses:
        '200':
          description: The sample was built, and delivered when `deliver` was not `false`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestSendResponse'
        '400':
          description: >-
            A body field failed validation, or the endpoint override is not a
            safe public URL.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestSendErrorResponse'
              example:
                error: fields must be a list of non-empty field-path strings.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            The key is not entitled to this dataset, or `track` or `fields` name
            field groups it may not receive.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
              example:
                error: 'Access denied to fields: contact'
        '404':
          description: >
            The dataset segment is not `person` or `company`. There is no job
            entity watch, so

            `/watch/job` does not match a route at all and the router answers a
            plain HTML `404`

            with no JSON body. Use `/watch/job/search` for jobs.
        '429':
          $ref: '#/components/responses/RateLimited'
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
    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:
    EntityTestPreviewRequest:
      type: object
      description: >
        An in-progress entity watch config to build a sample from. Carries the
        same `track` and `fields`

        a create request would, but nothing is saved.
      properties:
        track:
          description: The track tree to build the sample `changes` array from.
          oneOf:
            - $ref: '#/components/schemas/TrackCondition'
            - $ref: '#/components/schemas/TrackConditionGroup'
        fields:
          type: array
          items:
            type: string
          description: >-
            Field groups to project the sample `record` to. Entitlement-checked
            like a create.
          example:
            - basic_profile
            - experience
        notification_endpoint:
          type: string
          format: uri
          description: >-
            Public URL to deliver the sample to. Required when `deliver` is not
            `false`, since there is no saved watch to fall back on.
          example: https://your-server.example.com/webhook
        count:
          type: integer
          minimum: 1
          maximum: 25
          default: 1
          example: 1
        deliver:
          type: boolean
          default: true
          description: Set `false` to get the envelope back without sending anything.
          example: false
    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
    TrackCondition:
      type: object
      description: >
        One `track` leaf: the change on one field that fires a notification. The
        operator must match the

        field's shape, so `changed` needs a scalar path and `added` needs an
        array path. Mixing them

        returns `400`.
      required:
        - field
        - type
      properties:
        field:
          type: string
          description: >
            Dot-path into the record, addressing the same structure the
            dataset's enrich endpoint

            returns. A person profile has more than 200 addressable fields and
            any of them can be

            tracked.
          example: experience.employment_details.current
        type:
          type: string
          enum:
            - changed
            - added
            - '='
            - '!='
            - '>'
            - <
            - '=>'
            - '=<'
          description: >
            `changed` fires when a scalar becomes different from the last
            snapshot. `added` fires

            when a new element appears in an array. The comparison operators
            fire when the

            comparison flips from false to true, for example
            `professional_network.connections`

            crossing 500.
          example: added
        value:
          description: >-
            The threshold to compare against. Required for the comparison
            operators, omitted for `changed` and `added`.
          example: 500
    TrackConditionGroup:
      type: object
      description: >
        A group of track conditions. An `or` group fires if any child fires. An
        `and` group fires only

        when all conditions hold and at least one just became true. Groups nest.
      required:
        - op
        - conditions
      properties:
        op:
          type: string
          enum:
            - and
            - or
          example: or
        conditions:
          type: array
          minItems: 1
          items:
            oneOf:
              - $ref: '#/components/schemas/TrackCondition'
              - $ref: '#/components/schemas/TrackConditionGroup'
    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
    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

- [Update an entity watch](/api-reference/watch-apis/edit-the-watched-list-pause-or-reconfigure-an-entity-watch.md)
- [Create an entity watch](/api-reference/watch-apis/create-an-entity-watch.md)
- [Get an entity watch](/api-reference/watch-apis/get-one-entity-watch.md)
- [Company Entity Watcher](/watcher-docs/company/entity.md)
- [Person Entity Watcher](/watcher-docs/person/entity.md)
