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

# Create a post engagement monitor

> Follow up to 100 LinkedIn company pages and profiles and receive the people who react to or comment on their posts, with one results table and an optional webhook. Rate limit: 120 requests per fixed minute per API key, shared by all post engagement monitor endpoints.

> [!NOTE]
>
> Post engagement monitoring requires a **V2 workspace API key**. Legacy V1 API keys are not supported by these endpoints. Monitors and their credits belong to the V2 workspace associated with the key.

Create an active monitor with [Bearer authentication](https://docs.airscale.io/api-reference/authentication.md). A post engagement monitor follows LinkedIn company pages and personal profiles. On every check it reads the recent posts of each source and finds the people who reacted to or commented on them. Each newly detected engager is added as a row to the monitor's results table and, when configured, pushed to your webhook.

Each successful request creates a new monitor, and creation has no idempotency key. If a network failure leaves the result unknown, [list your monitors](https://docs.airscale.io/api-reference/post-engagement-monitors/list.md) before retrying; otherwise you may create a second monitor that charges the same people again.

> [!NOTE]
>
> **Sources are company pages and profiles only.** `sources` takes 1 to 100 LinkedIn company (`/company/...`) or profile (`/in/...`) URLs; they are canonicalized and de-duplicated. Single post URLs are rejected with `422 invalid_monitor`; use [Post likers](https://docs.airscale.io/api-reference/post-likers.md) or [Post commenters](https://docs.airscale.io/api-reference/post-commenters.md) for one post.

## Settings

* `frequency`: `daily` (default) or `weekly`.
* `engagement_types`: any of `reactions` and `comments`; default both.
* `include_existing_engagers`: default `false`, so the first check only records a baseline. `true` also delivers and charges people who engaged before the monitor existed.
* `filters`: `titles` and `locations` (case-insensitive contains matches, up to 50 each) and `excluded_company_urls` (LinkedIn company URLs, up to 100). Engagers that do not match are not delivered or charged.
* `webhook_url`: a public HTTPS URL without embedded credentials. Private, loopback, and internal hosts are rejected with `422` before creation and checked again at every send.

Unknown fields are rejected with `422 invalid_monitor`. The JSON request body must be at most **256 KiB (262,144 bytes)**; a larger body returns `413`.

## Limits and admission

A workspace can hold at most **25 monitors** (`422 monitor_limit_reached`), counting monitors created in the Airscale app. Creation requires at least 0.2 credits (`402 insufficient_credits`). While post engagement monitoring is closed, creation returns `503 monitoring_not_enabled`.

The `202 Accepted` response contains `monitor`, `sources`, `signing_secret`, and `signing_secret_warning`. Store `signing_secret` immediately; it is encrypted at rest and never returned again. Checks run asynchronously: the first check is due immediately and runs within a few minutes. Monitors share a check queue, so under load `next_check_at` can slip, but nothing is lost.

## Credits

Each engager delivered to the results table costs **0.2 credits**, charged once per person per monitor however many posts they engage with later. Reading posts and engagement pages is free, and reads, webhook deliveries, redeliveries, mark-read, and configuration changes never spend credits. When credits run out, checks keep running but new engagers wait unpaid: the monitor shows `results_status: "blocked"` and `results_error: "insufficient_credits"`, and they are charged and delivered within a few minutes of a top-up. Monitors are independent: the same person engaging with sources of two monitors is delivered and charged by each.

## Results table

Each monitor owns a regular Airscale results table named `<name> — Post engagement`. If the table is deleted or full in the app, delivery stops without charging and the monitor reports `results_status: "blocked"` with the reason. Deleting the monitor keeps its results table.

## Verify webhook signatures

One webhook is sent per delivered engager, including existing engagers imported on request. The JSON body has `"type": "post_engagement.engager_detected"`, `id` (the engager ID), `monitor_id`, `detected_at` (the delivery time), `engager` (the engager object from [List engagers](https://docs.airscale.io/api-reference/post-engagement-monitors/engagers.md) without `read_at` and `webhook`), and `engagements` (up to the 25 most recent engagement objects from [List engagements](https://docs.airscale.io/api-reference/post-engagement-monitors/engagements.md)).

The request includes `X-Airscale-Event: post_engagement.engager_detected`, `X-Airscale-Event-Id: <engager_id>`, `X-Airscale-Timestamp`, `X-Airscale-Signature: sha256=<hex>`, and `Idempotency-Key: post-engagement-engager:<engager_id>`. Compute HMAC-SHA256 with the `signing_secret` over `<timestamp>.<raw request body>`, compare it in constant time, and reject timestamps more than five minutes from the current time. This is the same scheme as job-change monitor webhooks. Deduplicate on the event ID and return a 2xx response only after durable handling.

Deliveries are dispatched every couple of minutes with a 15-second timeout. Airscale makes up to five attempts with exponential backoff: `408`, `425`, `429`, and `5xx` responses are retried; other responses fail permanently (`webhook_http_<status>`); a blocked destination fails with `webhook_url_rejected`; timeouts and dropped connections become `uncertain` and are never resent automatically. Use [Redeliver a webhook](https://docs.airscale.io/api-reference/post-engagement-monitors/engagers/redeliver.md) to send an engager again.

## Next step

Use [Get a post engagement monitor](https://docs.airscale.io/api-reference/post-engagement-monitors/get.md) to follow the first check of each source.


## OpenAPI

````yaml openapi.json POST /v1/post-engagement-monitors
openapi: 3.1.0
info:
  title: Airscale Public API
  version: '2026-09-11'
  description: >-
    Search, enrich, resolve public business data, and monitor job changes with
    Airscale.
  x-airscale-source-repository: ViceScale/airscale-code
  x-airscale-source-sha: 1de19e1b70a052a4b8d9c2075021a7e5e7a94d51
servers:
  - url: https://api.airscale.io
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Search and discovery
    description: Search people, companies, and the web.
  - name: Contact data
    description: Find professional and personal contact data.
  - name: Profiles and reverse lookup
    description: Extract profiles or resolve a person from known contact data.
  - name: Post engagement
    description: Retrieve and enrich people who liked or commented on LinkedIn posts.
  - name: LinkedIn content
    description: >-
      Search LinkedIn posts, read profile and company feeds, and retrieve
      profile comments and comment reactions.
  - name: Job change monitoring
    description: Track LinkedIn profiles and receive job-change events.
  - name: New hire monitoring
    description: Watch companies or the market for people who start a matching role.
  - name: Post engagement monitoring
    description: >-
      Follow LinkedIn company pages and profiles and receive the people who
      engage with their posts.
  - name: Account
    description: Inspect workspace account state.
  - name: Miscellaneous
    description: >-
      Find website technologies and check WhatsApp availability, Meta ads, and
      email deliverability.
paths:
  /v1/post-engagement-monitors:
    post:
      tags:
        - Post engagement monitoring
      summary: Create a post engagement monitor
      description: >-
        Creates an active monitor that follows 1–100 LinkedIn company pages and
        personal profiles. On every check it reads the recent posts of each
        source and finds the people who reacted to or commented on them; each
        newly detected engager is added to the monitor's Airscale results table
        and, when configured, sent to your webhook. Single post URLs are not
        accepted. A workspace can have at most 25 monitors. The first check is
        due immediately and runs asynchronously. If webhook_url is set, the 202
        response includes a signing_secret exactly once. Every request creates a
        new monitor and there is no idempotency key: if the response is lost,
        list monitors before retrying. Requires a V2 workspace API key. Legacy
        V1 API keys are not supported. Monitors and their credits belong to the
        V2 workspace.
      operationId: createPostEngagementMonitor
      requestBody:
        required: true
        description: >-
          Provide a name and 1–100 LinkedIn company or profile URLs in a JSON
          body of at most 256 KiB (262,144 bytes). Unknown fields are rejected.
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - name
                - sources
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 120
                  description: 'Also names the results table: `<name> — Post engagement`.'
                sources:
                  type: array
                  minItems: 1
                  maxItems: 100
                  items:
                    type: string
                    minLength: 1
                    maxLength: 1000
                  description: >-
                    1–100 LinkedIn company (/company/...) or personal profile
                    (/in/...) URLs. URLs are canonicalized and de-duplicated.
                    Single post URLs are rejected.
                frequency:
                  type: string
                  enum:
                    - daily
                    - weekly
                  default: daily
                engagement_types:
                  type: array
                  minItems: 1
                  maxItems: 2
                  uniqueItems: true
                  items:
                    type: string
                    enum:
                      - reactions
                      - comments
                  default:
                    - reactions
                    - comments
                include_existing_engagers:
                  type: boolean
                  default: false
                  description: >-
                    false: the first check only records a baseline. true: people
                    who engaged before the monitor existed are also delivered
                    and charged.
                filters:
                  type: object
                  additionalProperties: false
                  description: >-
                    Engagers that do not match are not delivered or charged.
                    Titles and locations are case-insensitive contains matches.
                  properties:
                    titles:
                      type: array
                      maxItems: 50
                      items:
                        type: string
                        minLength: 1
                        maxLength: 200
                      default: []
                    locations:
                      type: array
                      maxItems: 50
                      items:
                        type: string
                        minLength: 1
                        maxLength: 200
                      default: []
                    excluded_company_urls:
                      type: array
                      maxItems: 100
                      items:
                        type: string
                        maxLength: 500
                      default: []
                      description: LinkedIn company URLs whose employees are left out.
                webhook_url:
                  type:
                    - string
                    - 'null'
                  format: uri
                  maxLength: 2048
                  description: >-
                    A public HTTPS URL without embedded credentials. Internal
                    hostnames and private or reserved addresses are rejected,
                    and destinations are checked again at every send.
            examples:
              monitor:
                summary: Follow a company page and a profile
                value:
                  name: Competitor audiences
                  sources:
                    - https://www.linkedin.com/company/example-company
                    - https://www.linkedin.com/in/example-person
                  frequency: daily
                  engagement_types:
                    - reactions
                    - comments
                  include_existing_engagers: false
                  filters:
                    titles:
                      - VP
                      - Head of
                    locations:
                      - France
                    excluded_company_urls:
                      - https://www.linkedin.com/company/example-company
                  webhook_url: https://hooks.example.com/airscale/engagers
      responses:
        '202':
          description: >-
            The monitor was accepted and its first check scheduled. Checks run
            asynchronously; acceptance does not mean a check has completed.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - monitor
                  - sources
                  - signing_secret
                  - signing_secret_warning
                properties:
                  monitor:
                    $ref: '#/components/schemas/PostEngagementMonitor'
                  sources:
                    type: array
                    items:
                      $ref: '#/components/schemas/PostEngagementSource'
                    description: The monitor's active sources, sorted by URL.
                  signing_secret:
                    type:
                      - string
                      - 'null'
                    description: >-
                      Returned once when a webhook is configured; null
                      otherwise.
                  signing_secret_warning:
                    type:
                      - string
                      - 'null'
              examples:
                success:
                  summary: Successful response
                  value:
                    monitor:
                      id: 71111111-1111-4111-8111-111111111111
                      name: Competitor audiences
                      status: active
                      frequency: daily
                      engagement_types:
                        - reactions
                        - comments
                      include_existing_engagers: false
                      filters:
                        titles:
                          - VP
                          - Head of
                        locations:
                          - France
                        excluded_company_urls:
                          - https://www.linkedin.com/company/example-company
                      results_table_id: 74444444-4444-4444-8444-444444444444
                      results_status: ready
                      results_error: null
                      next_check_at: '2026-11-02T08:58:00.000Z'
                      last_checked_at: null
                      last_error: null
                      webhook_url: https://hooks.example.com/airscale/engagers
                      webhook_configured: true
                      source_count: 2
                      engager_count: 0
                      unread_engager_count: 0
                      created_at: '2026-11-02T08:58:00.000Z'
                      updated_at: '2026-11-02T08:58:00.000Z'
                    sources:
                      - id: 72222222-2222-4222-8222-222222222222
                        linkedin_url: https://www.linkedin.com/company/example-company
                        kind: company
                        status: pending
                        last_checked_at: null
                        last_error: null
                      - id: 72222223-2222-4222-8222-222222222223
                        linkedin_url: https://www.linkedin.com/in/example-person
                        kind: profile
                        status: pending
                        last_checked_at: null
                        last_error: null
                    signing_secret: example-signing-secret
                    signing_secret_warning: Store this secret now. It is not returned again.
        '400':
          description: >-
            The request body is missing or is not valid JSON. The public API
            returns {"error":"Invalid JSON"}.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                error:
                  summary: Error response
                  value:
                    error: Invalid JSON
        '401':
          $ref: '#/components/responses/MonitorUnauthorized'
        '402':
          description: 'insufficient_credits: creation requires at least 0.2 credits.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MonitorApiError'
        '413':
          description: The JSON request body exceeds the 256 KiB (262,144 bytes) limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: >-
            invalid_monitor (for example a missing name, an unknown field, a URL
            that is not a LinkedIn company or profile, a single post URL,
            invalid filters, or a webhook_url that is not a public HTTPS
            destination) or monitor_limit_reached when the workspace already has
            25 monitors.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MonitorApiError'
        '429':
          description: >-
            The API key has exceeded 120 requests in the current minute across
            post engagement monitor endpoints. Wait for the Retry-After seconds
            before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                error:
                  summary: Error response
                  value:
                    error: Too Many Requests
                    message: 'Rate limit exceeded: 120 requests per minute'
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: string
              example: '60'
        '503':
          description: >-
            monitoring_not_enabled while post engagement monitoring is closed,
            or service_unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MonitorApiError'
components:
  schemas:
    PostEngagementMonitor:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - status
        - frequency
        - engagement_types
        - include_existing_engagers
        - filters
        - results_table_id
        - results_status
        - results_error
        - next_check_at
        - last_checked_at
        - last_error
        - webhook_url
        - webhook_configured
        - source_count
        - engager_count
        - unread_engager_count
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
          enum:
            - active
            - paused
        frequency:
          type: string
          enum:
            - daily
            - weekly
        engagement_types:
          type: array
          items:
            type: string
            enum:
              - reactions
              - comments
        include_existing_engagers:
          type: boolean
        filters:
          type: object
          additionalProperties: false
          required:
            - titles
            - locations
            - excluded_company_urls
          properties:
            titles:
              type: array
              items:
                type: string
            locations:
              type: array
              items:
                type: string
            excluded_company_urls:
              type: array
              items:
                type: string
        results_table_id:
          type: string
          description: >-
            The Airscale results table that receives one row per delivered
            engager.
        results_status:
          type: string
          enum:
            - ready
            - pending
            - blocked
          description: >-
            ready, pending (engagers waiting for delivery) or blocked (see
            results_error).
        results_error:
          type:
            - string
            - 'null'
          description: >-
            Why delivery is blocked, for example insufficient_credits,
            results_table_deleted, table_row_limit_reached or
            results_columns_missing.
        next_check_at:
          type:
            - string
            - 'null'
          format: date-time
        last_checked_at:
          type:
            - string
            - 'null'
          format: date-time
        last_error:
          type:
            - string
            - 'null'
          description: A short code when the last check could not complete.
        webhook_url:
          type:
            - string
            - 'null'
          format: uri
        webhook_configured:
          type: boolean
        source_count:
          type: integer
          minimum: 0
          description: Active sources.
        engager_count:
          type: integer
          minimum: 0
          description: Engagers delivered to the results table.
        unread_engager_count:
          type: integer
          minimum: 0
          description: Exact count, independent of any page size.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PostEngagementSource:
      type: object
      additionalProperties: false
      required:
        - id
        - linkedin_url
        - kind
        - status
        - last_checked_at
        - last_error
      properties:
        id:
          type: string
          format: uuid
        linkedin_url:
          type: string
        kind:
          type: string
          enum:
            - company
            - profile
            - post
          description: >-
            The API accepts company and profile sources only; post appears on
            single-post sources added in the Airscale app.
        status:
          type: string
          enum:
            - pending
            - monitoring
            - removed
          description: >-
            pending until the source's first check, then monitoring. removed
            appears only in a remove response.
        last_checked_at:
          type:
            - string
            - 'null'
          format: date-time
        last_error:
          type:
            - string
            - 'null'
    Error:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
        message:
          type: string
    MonitorApiError:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
  responses:
    MonitorUnauthorized:
      description: >-
        A missing Bearer token returns
        {"error":"Unauthorized","message":"Missing or invalid bearer token"}. An
        unknown API key returns invalid_api_key.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/MonitorApiError'
              - $ref: '#/components/schemas/Error'
          examples:
            invalidKey:
              summary: Unknown API key
              value:
                error:
                  code: invalid_api_key
                  message: The API key is not valid
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Use an Airscale workspace API key. Never expose the key in client-side
        code.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.