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

# Create alert API

> Save a signal query plus a webhook URL and receive only new matching events, HMAC-signed. Free. Returns the webhook secret once — store it.

## Frequently asked questions

<AccordionGroup>
  <Accordion title="What can I alert on?">
    Anything you can query. There are five practical shapes: **adoption** on a technology or category, **churn** on a technology or category, **switch** on a technology with a direction or across a category, an **account watchlist** (`["churn","adoption"]` on a `domains` scope, for any stack change on domains you name), and a **whitespace entrant** (an adoption alert plus `missing_category_id`, which fires when a domain adopts your anchor while still running nothing in the gap category).
  </Accordion>

  <Accordion title="Where do I get the webhook secret?">
    It is in the create response as `webhook_secret`, prefixed `whsec_`. Store it immediately — it signs every delivery. You can also read it back later from [the alert detail endpoint](/docs/api-reference/alerts/get-alert-api), which is the only other place it appears.
  </Accordion>

  <Accordion title="What is a HOT_SCOPE rejection?">
    A guard against alerts that can never work. Each delivery carries at most 100 events, so a scope generating more events per hour than that can never drain its backlog and would deliver permanently stale data. At creation we estimate the scope's rate over the last 7 days and reject the ones that are too hot, with guidance. The fix is always to narrow: add firmographic filters, raise `min_confidence`, or use a technology scope instead of a whole category.
  </Accordion>

  <Accordion title="What does backfill_days do?">
    It sets the initial watermark to that many days ago, so your very first sweep delivers real events instead of waiting for something new to happen. The default is 7 and the maximum is 30. Set it to `0` if you only want events from now on.
  </Accordion>

  <Accordion title="Why can't I change the scope later?">
    The watermark tracks how far through a specific scope we have delivered, so its meaning is bound to that scope. Changing the scope would make the watermark meaningless and either replay or skip events. Delete the alert and create a new one instead. Everything else — name, status, webhook URL, filters, cadence, delivery frequency — is editable.
  </Accordion>

  <Accordion title="How do I stop churn alerts being noisy?">
    Two independent knobs. `filters.churn_reliability` gates on the detection *method* and already defaults to `["high","medium"]`, which excludes technologies whose detection flaps. `filters.min_confidence_label` gates on the combined per-event score and applies to adoption too. Setting it to `medium` or higher drops single-scan churns, which are the bulk of the noise — around half of them turn out to be false positives on live re-detection.
  </Accordion>
</AccordionGroup>


## OpenAPI

````yaml POST /v1/alerts
openapi: 3.1.0
info:
  title: TechnologyChecker API
  description: >-
    Discover technology stacks, track market trends, and enrich data with
    company intelligence.
  version: 1.0.0
  contact:
    name: TechnologyChecker Support
    email: support@technologychecker.io
    url: https://technologychecker.io
servers:
  - url: https://api.technologychecker.io
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Domain technologies
    description: Look up technologies detected on any domain
  - name: Technology data
    description: Search, look up, and get details on tracked technologies
  - name: Market intelligence
    description: Technology stats, trends, and market share
  - name: Company data
    description: Firmographic and company intelligence
  - name: Live detection
    description: Real-time technology detection with browser rendering
  - name: Signals
    description: >-
      Intent signals derived from technology state transitions: who adopted, who
      churned, who switched, and who has a gap in their stack.
  - name: Alerts
    description: >-
      Saved signal queries delivered to your webhook. Every signal you can
      query, you can subscribe to.
  - name: Audiences
    description: >-
      Saved groups of domains — uploaded lists or computed recipes — that you
      can profile, slice and monitor.
  - name: Lead lists
    description: >-
      Build and export targeted domain lists combining technology detection with
      company data.
paths:
  /v1/alerts:
    post:
      tags:
        - Alerts
      summary: Create alert API
      description: >-
        Save a signal query plus a webhook URL. From then on an hourly sweep
        delivers only the **new** events since the last successful delivery,
        signed with HMAC-SHA256.


        Provide exactly one scope: `technology_id`, `technology`, `category_id`,
        `domains` or `segment_id`. Scope is immutable after creation because the
        watermark's meaning is bound to it — delete and recreate to change it.


        The response includes the `webhook_secret` (prefixed `whsec_`). **Store
        it now** — it signs every delivery and is only returned here and on the
        detail endpoint.


        Creation runs a hot-scope guard: a scope producing more events per hour
        than the 100-events-per-delivery cap can never drain its backlog, so it
        is rejected with `HOT_SCOPE` and guidance to narrow. Free — alerts are a
        plan entitlement, not a metered call.
      operationId: createAlert
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - signal_types
                - webhook_url
              properties:
                name:
                  type: string
                  example: Who leaves Shopify (US, 51-200)
                signal_types:
                  type: array
                  items:
                    type: string
                    enum:
                      - churn
                      - adoption
                      - switch
                  description: >-
                    `["churn","adoption"]` means any stack change, which is the
                    natural account watch. `switch` cannot be combined with the
                    others because its event shape differs.
                  example:
                    - switch
                technology_id:
                  type: integer
                  description: Technology scope. Must be a detectable technology.
                  example: 2184
                technology:
                  type: string
                  description: Technology scope by exact name.
                category_id:
                  type: integer
                  description: >-
                    Category scope. Re-resolved fresh at every sweep, so
                    technologies added to the category are covered
                    automatically.
                domains:
                  type: array
                  items:
                    type: string
                  description: >-
                    Domains scope — the account watchlist. 1 to 1,000 domains
                    (100 on Free). Normalized to lowercase and deduped; invalid
                    entries are rejected with the offending values.
                  example:
                    - acme.com
                    - example.com
                segment_id:
                  type: string
                  description: >-
                    Audience scope. List audiences only; membership resolves
                    live at each sweep.
                direction:
                  type: string
                  enum:
                    - from
                    - to
                  default: from
                  description: >-
                    Switch alerts on a technology scope only. `from` is who left
                    the anchor; `to` is who came to it.
                missing_category_id:
                  type: integer
                  description: >-
                    Turns an adoption alert into a whitespace-entrant alert.
                    Requires `signal_types` of exactly `["adoption"]`, a
                    technology or category scope, and a category different from
                    the anchor.
                min_scans:
                  type: integer
                  minimum: 1
                  maximum: 10
                  description: >-
                    Whitespace precision gate. Defaults to 2 when
                    `missing_category_id` is set.
                filters:
                  $ref: '#/components/schemas/AlertFilters'
                webhook_url:
                  type: string
                  format: uri
                  description: >-
                    HTTPS only. Private, internal and metadata hosts are
                    rejected, and redirects are never followed.
                  example: https://example.com/hooks/tc
                delivery_frequency:
                  type: string
                  enum:
                    - hourly
                    - daily
                  default: daily
                  description: >-
                    `daily` batches a day's events into one digest delivery.
                    `hourly` is a per-sweep drip. Independent of
                    `crawl_cadence`.
                crawl_cadence:
                  type: string
                  enum:
                    - daily
                    - weekly
                    - monthly
                  default: monthly
                  description: >-
                    Domains scope on a paid plan only. Buys re-scans of the
                    watched domains so the underlying data refreshes as fast as
                    the subscription. Forced to `monthly` on other scopes.
                backfill_days:
                  type: integer
                  minimum: 0
                  maximum: 30
                  default: 7
                  description: >-
                    How far back the first sweep reaches. `0` means future
                    events only.
              example:
                name: Who leaves Shopify (US, 51-200)
                signal_types:
                  - switch
                technology_id: 2184
                direction: from
                filters:
                  min_confidence: 0.5
                  country: united states
                  employees: 51-200
                webhook_url: https://example.com/hooks/tc
                backfill_days: 7
      responses:
        '201':
          description: Alert created. `webhook_secret` is returned here — store it.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      alert:
                        allOf:
                          - $ref: '#/components/schemas/Alert'
                          - type: object
                            properties:
                              webhook_secret:
                                type: string
                                example: whsec_3f9a2b7c1d4e5f60
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  schemas:
    AlertFilters:
      type: object
      description: >-
        Row-level filters applied to every sweep. Firmographic keys use the same
        vocabulary as the Signals API.
      properties:
        min_confidence:
          type: number
          format: float
          default: 0.5
          description: Detection-rate floor.
          example: 0.5
        churn_reliability:
          type: array
          items:
            type: string
            enum:
              - high
              - medium
              - low
          description: >-
            Churn alerts only. Defaults to `["high","medium"]` — low-reliability
            technologies flap and produce false-churn spam. Unclassified
            technologies count as medium.
          example:
            - high
            - medium
        min_confidence_label:
          type: string
          enum:
            - high
            - medium
            - low
          description: >-
            Per-event confidence floor, off by default. Unlike
            `churn_reliability` this gates the combined score and applies to
            adoption too, so it is how a watchlist suppresses single-scan noise.
            Note that a churn with fewer than 2 detections is floored to `low`,
            so any value above `low` drops single-scan churns.
          example: medium
        country:
          type: string
          example: united states
        city:
          type: string
        state:
          type: string
        industry:
          type: string
        industry_code:
          type: integer
        employees:
          type: string
          example: 51-200
        company_type:
          type: string
        founded_min:
          type: integer
        founded_max:
          type: integer
        company:
          type: boolean
          description: Attach company cards without filtering.
        has_email:
          type: boolean
        has_phone:
          type: boolean
        social:
          type: string
        language:
          type: string
        web_country:
          type: string
    Alert:
      type: object
      properties:
        id:
          type: string
          example: alrt_9f2c4a1e
        name:
          type: string
          example: Who leaves Shopify (US, 51-200)
        signal_types:
          type: array
          items:
            type: string
            enum:
              - churn
              - adoption
              - switch
          example:
            - switch
        scope:
          type: object
          description: The saved scope. Immutable after creation.
          properties:
            kind:
              type: string
              enum:
                - technology
                - category
                - domains
                - segment
              example: technology
            technology_id:
              type: integer
              nullable: true
              example: 2184
            technology_name:
              type: string
              nullable: true
              example: Shopify
            category_id:
              type: integer
              nullable: true
            domains:
              type: array
              nullable: true
              items:
                type: string
            segment_id:
              type: string
              nullable: true
        direction:
          type: string
          nullable: true
          enum:
            - from
            - to
            - null
        missing_category_id:
          type: integer
          nullable: true
          description: Set turns an adoption alert into a whitespace-entrant alert.
        filters:
          $ref: '#/components/schemas/AlertFilters'
        webhook_url:
          type: string
          format: uri
          example: https://example.com/hooks/tc
        status:
          type: string
          enum:
            - active
            - paused
            - disabled
          example: active
        delivery_frequency:
          type: string
          enum:
            - hourly
            - daily
          default: daily
          example: daily
        crawl_cadence:
          type: string
          nullable: true
          enum:
            - daily
            - weekly
            - monthly
            - null
          example: monthly
        last_fired_at:
          type: string
          nullable: true
          example: '2026-06-10 17:39:39'
        consecutive_failures:
          type: integer
          example: 0
        created_at:
          type: string
          example: '2026-06-10T16:39:39.000Z'
        updated_at:
          type: string
          example: '2026-06-10T16:39:39.000Z'
    ApiError:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: string
              example: UNAUTHORIZED
            message:
              type: string
              example: Missing or invalid API key
  responses:
    BadRequest:
      description: Invalid or missing parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            success: false
            error:
              code: VALIDATION_ERROR
              message: >-
                Provide exactly one scope: technology_id, technology or
                category_id.
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    Forbidden:
      description: The plan does not allow this action, or a plan limit was reached
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            success: false
            error:
              code: PLAN_LIMIT
              message: >-
                Your plan allows up to 2 saved audiences. Delete one to create
                another.
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key in format `tapi_live_[32-char]` (live) or `tapi_test_[32-char]`
        (test)

````