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

# Post commenters

> Retrieve and enrich people who commented on a LinkedIn post. Rate limit: 180 requests per fixed minute per workspace, shared by post-likers and post-commenters; pagination requests and retries count.

Retrieve one synchronous page of people who commented on a LinkedIn post, with public profile fields enriched for each returned engagement.

<Note>
  The API reserves one credit for each requested slot (`limit`) before retrieving the page. Unused slots are refunded. Each successfully enriched profile consumes one credit. Definitive `not_found` and failed `error` outcomes remain in the page and are refunded, so they have zero net credit cost. An uncertain response can retain its reservation until the same page finishes; it does not prove a refund.
</Note>

## Rate limit

The limit is **180 requests per minute per workspace**, shared by [Post likers](/api-reference/post-likers) and Post commenters, including different API keys for the same workspace. Pagination requests and retries each count toward this limit.

The counter uses a fixed minute window and resets at the next minute boundary. Requests above the limit receive HTTP `429` before provider or billing work. Wait until the next minute, then retry the identical page with the same `Idempotency-Key` using bounded backoff. This is a request limit, not a guarantee of enrichment throughput; the maximum page size remains 25.

## Pagination

Omit `cursor` on the first request. When `pagination.next_cursor` is not `null`, send it unchanged with the same post URL and `limit` to fetch the next page. The page size is 1–25 (default 25). The provider and continuation account remain pinned to the cursor. New cursors expire after 10 minutes and are bound to the workspace, post, engagement type, and page size.

Optional `retrieval` metadata contains `reported_total`, `returned_count`, `status`, and `stop_reason`. A `provider_exhausted` status means that provider has no further cursor; it does not guarantee that every LinkedIn engagement was accessible. Provider totals can change, and comment events are not the same count as unique authors. Public pagination does not promise every reply returned by the dashboard's background extraction.

## Safe retries

Supply an optional UUID in the `Idempotency-Key` header. Save that key together with the endpoint and exact request body before sending the request. Reuse the same key with the identical `post_url`, `limit`, and `cursor` if the response is lost or processing is uncertain. Use a new key for the next page.

The code examples read `AIRSCALE_PAGE_KEY` from your environment. Set it to a UUID once for each new page, and keep that value for every retry of that page. Replace the synthetic post URL and cursor with your own values.

Omitting the header remains supported. However, an independent retry without the original key can reserve credits again. When a pending or uncertain response includes `idempotency_key`, retain and send that returned key even if your first request omitted the header.

Completed results remain replayable for 24 hours after completion, including after the input cursor expires. Already-saved profile or billing work can also resume after cursor expiry. An expired cursor cannot start a new provider fetch. A reservation for unresolved work does not automatically become a refund after 24 hours.

## Pending and error responses

A `202` response with `status: "pending"` and `code: "page_pending"` means the bridge did not respond within 90 seconds. The outcome is unconfirmed; processing may still be running or may not have started. Wait for `Retry-After` (15 seconds for this response), then resend the identical page with the returned `idempotency_key`. The body also supplies `retry_after_ms`.

| Response                                                      | Next action                                                                                                                                        |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `409 replay_in_progress`                                      | Wait, then retry the identical page with the same key.                                                                                             |
| `409 replay_request_mismatch`                                 | Restore the original request fields for that key. Use a new key only for intentionally different work.                                             |
| `503 account_busy`, `rate_limited`, or `provider_unavailable` | Keep the page and key unchanged. Wait for the supplied delay and retry with bounded backoff.                                                       |
| `503 provider_page_unavailable`                               | Retry the same cursor and key; continuation stays on its pinned provider.                                                                          |
| `503 account_restart_required`                                | Start again without a cursor and with a new key. Deduplicate results already received.                                                             |
| Other `502` or `503` responses carrying `idempotency_key`     | Preserve the original body and returned key. Retry with bounded backoff; do not assume that an uncertain profile or billing response was refunded. |
| `429 Too Many Requests`                                       | Wait and retry the same page and key with bounded exponential backoff.                                                                             |

If a cooldown outlasts an unfetched cursor, wait for the cooldown, then begin a fresh traversal without the cursor and with a new key. Deduplicate earlier results. A fresh traversal is new billable work. Saved work and completed replay results can still be recovered under the rules above.

Airscale can choose another eligible account before a traversal starts. A continuation stays on its original account; it cannot silently switch accounts or reorder your results. Provider failures do not turn upstream access or pagination limits into a complete result set.

## Billing recovery

A failed provider discovery attempt is refunded before a new attempt is funded. Once a page has been saved, retries resume its profile enrichment or billing settlement without rediscovering the page or repeating successfully settled profile charges. Keep the same key throughout recovery.

## Next step

Use [Post likers](/api-reference/post-likers) for the other engagement type, or read the [post engagement workflow](/docs/likers-commenters) for the dashboard flow.


## OpenAPI

````yaml openapi.json POST /v1/post-commenters
openapi: 3.1.0
info:
  title: Airschool Public API
  version: '2026-09-11'
  description: Search, enrich, and resolve public business data with Airschool.
  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: Account
    description: Inspect workspace account state.
  - name: Miscellaneous
    description: Check WhatsApp availability, Meta ads, and email deliverability.
paths:
  /v1/post-commenters:
    post:
      tags:
        - Post engagement
      summary: List LinkedIn post commenters
      description: >-
        Retrieves one synchronous, cursor-paginated page of people who commented
        on a LinkedIn post and enriches each returned profile.
      operationId: listPostCommenters
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            format: uuid
          description: >-
            Create a UUID for this page and reuse it with the identical
            endpoint, post_url, limit and cursor on every retry. Use a new key
            for the next page. Completed results are replayable for 24 hours
            after completion. Omitting the header is supported, but an
            independent retry without the original key can reserve credits
            again.
      requestBody:
        required: true
        description: >-
          Provide a LinkedIn post URL, an optional page size from 1 to 25, and
          the opaque cursor when continuing pagination. The JSON body must not
          exceed 128 KiB.
        content:
          application/json:
            schema:
              type: object
              required:
                - post_url
              additionalProperties: false
              properties:
                post_url:
                  type: string
                  format: uri
                  minLength: 1
                  maxLength: 2048
                  description: >-
                    An HTTP(S) LinkedIn post URL containing an activity
                    identifier.
                limit:
                  type: integer
                  minimum: 1
                  maximum: 25
                  default: 25
                  description: Number of engagements to request on this page.
                cursor:
                  type:
                    - string
                    - 'null'
                  minLength: 1
                  maxLength: 8192
                  description: >-
                    An opaque cursor returned by the preceding page. Send it
                    unchanged.
            examples:
              firstPage:
                summary: First page
                value:
                  post_url: >-
                    https://www.linkedin.com/posts/example_activity-7376356221991178240
                  limit: 25
              nextPage:
                summary: Next page
                value:
                  post_url: >-
                    https://www.linkedin.com/posts/example_activity-7376356221991178240
                  limit: 25
                  cursor: pje1.synthetic_cursor
      responses:
        '200':
          description: A page of enriched LinkedIn post engagements.
          content:
            application/json:
              schema:
                type: object
                required:
                  - items
                  - pagination
                  - billing
                additionalProperties: false
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      required:
                        - profile_status
                        - linkedin_url
                        - first_name
                        - last_name
                        - full_name
                        - company_name
                        - job_title
                        - location
                        - domain
                        - linkedin_company_url
                        - headline
                        - reaction_type
                        - comment
                        - post_url
                      additionalProperties: false
                      properties:
                        profile_status:
                          type: string
                          enum:
                            - success
                            - not_found
                            - error
                          description: Profile enrichment outcome for this engagement.
                        linkedin_url:
                          type:
                            - string
                            - 'null'
                        first_name:
                          type:
                            - string
                            - 'null'
                        last_name:
                          type:
                            - string
                            - 'null'
                        full_name:
                          type:
                            - string
                            - 'null'
                        company_name:
                          type:
                            - string
                            - 'null'
                        job_title:
                          type:
                            - string
                            - 'null'
                        location:
                          type:
                            - string
                            - 'null'
                        domain:
                          type:
                            - string
                            - 'null'
                        linkedin_company_url:
                          type:
                            - string
                            - 'null'
                        headline:
                          type:
                            - string
                            - 'null'
                        reaction_type:
                          type:
                            - string
                            - 'null'
                        comment:
                          type:
                            - string
                            - 'null'
                        post_url:
                          type:
                            - string
                            - 'null'
                    description: >-
                      The requested engagements with public profile fields and
                      enrichment status.
                  pagination:
                    type: object
                    required:
                      - next_cursor
                      - has_more
                    additionalProperties: false
                    properties:
                      next_cursor:
                        type:
                          - string
                          - 'null'
                        description: >-
                          Opaque cursor for the next page, or null when the
                          provider returns no continuation cursor. This does not
                          prove complete LinkedIn coverage.
                      has_more:
                        type: boolean
                  provider:
                    type: string
                    enum:
                      - b2benrichment
                      - rapidapi
                      - rapidapi_pnd
                      - unipile
                    description: >-
                      The provider selected for this page. Treat this value as
                      informational.
                  retrieval:
                    type: object
                    required:
                      - reported_total
                      - returned_count
                      - status
                      - stop_reason
                    additionalProperties: false
                    description: >-
                      Optional retrieval evidence. Exhausting a provider cursor
                      does not prove all LinkedIn engagements were accessible.
                    properties:
                      reported_total:
                        type:
                          - integer
                          - 'null'
                        minimum: 0
                      returned_count:
                        type: integer
                        minimum: 0
                      status:
                        type: string
                        enum:
                          - more_available
                          - provider_exhausted
                      stop_reason:
                        type: string
                        enum:
                          - page_limit
                          - provider_exhausted
                          - request_budget
                  billing:
                    type: object
                    required:
                      - credits_consumed
                      - credits_refunded
                      - outcomes
                    additionalProperties: false
                    properties:
                      credits_consumed:
                        type: integer
                        minimum: 0
                        maximum: 25
                      credits_refunded:
                        type: integer
                        minimum: 0
                        maximum: 25
                      outcomes:
                        type: object
                        required:
                          - success
                          - not_found
                          - error
                        additionalProperties: false
                        properties:
                          success:
                            type: integer
                            minimum: 0
                            maximum: 25
                          not_found:
                            type: integer
                            minimum: 0
                            maximum: 25
                          error:
                            type: integer
                            minimum: 0
                            maximum: 25
              examples:
                page:
                  summary: Synthetic enriched engagement page
                  value:
                    items:
                      - profile_status: success
                        linkedin_url: https://www.linkedin.com/in/example-person-000000
                        first_name: Example
                        last_name: Person
                        full_name: Example Person
                        company_name: Example Company
                        job_title: Example role
                        location: Example City
                        domain: example.org
                        linkedin_company_url: https://www.linkedin.com/company/example-company
                        headline: Example role at Example Company
                        reaction_type: COMMENT
                        comment: Example comment
                        post_url: >-
                          https://www.linkedin.com/posts/example_activity-7376356221991178240
                    pagination:
                      next_cursor: pje1.synthetic_cursor
                      has_more: true
                    billing:
                      credits_consumed: 1
                      credits_refunded: 24
                      outcomes:
                        success: 1
                        not_found: 0
                        error: 0
        '202':
          description: >-
            The bridge did not respond within 90 seconds. Its outcome is
            unconfirmed; retry the identical page with the returned key after
            Retry-After. This is not a completed result or a guarantee that
            processing started.
          headers:
            Retry-After:
              description: Wait this many seconds before retrying (15 for page_pending).
              schema:
                type: string
              example: '15'
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - status
                  - code
                  - idempotency_key
                  - retry_after_ms
                  - message
                properties:
                  status:
                    type: string
                    enum:
                      - pending
                  code:
                    type: string
                    enum:
                      - page_pending
                  idempotency_key:
                    type: string
                    format: uuid
                  retry_after_ms:
                    type: integer
                    minimum: 0
                  message:
                    type: string
              examples:
                pending:
                  summary: Unconfirmed page outcome
                  value:
                    status: pending
                    code: page_pending
                    idempotency_key: 00000000-0000-4000-8000-000000000001
                    retry_after_ms: 15000
                    message: >-
                      Retry the identical page with this Idempotency-Key to
                      retrieve its result.
        '400':
          description: The JSON body, LinkedIn post URL, or cursor is invalid.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: >-
            The workspace does not have enough credits to reserve profile
            enrichment for this page.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
        '409':
          description: >-
            replay_in_progress: wait and retry the identical page with the same
            key. replay_request_mismatch: the key was already used with
            different request fields.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
        '413':
          description: The JSON request body exceeds the 128 KiB limit.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
        '429':
          description: >-
            The workspace has exceeded the 180 requests per minute limit shared
            by post-likers and post-commenters. Wait until the next minute
            boundary, then retry the identical page with the same idempotency
            key using bounded backoff.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
        '500':
          description: >-
            The request could not be completed because of an unexpected server
            error.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
        '502':
          description: >-
            The post-engagement bridge could not be reached or returned an
            invalid response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
        '503':
          description: >-
            A provider, profile-enrichment, pagination, or credit-settlement
            dependency is temporarily unavailable.
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  code:
                    type: string
                    description: >-
                      Use the documented code to distinguish waiting, invalid
                      reuse, and account restart.
                  idempotency_key:
                    type: string
                    format: uuid
                    description: >-
                      Retain this key and the original request body for safe
                      recovery.
                  retry_after_ms:
                    type: integer
                    minimum: 0
      x-codeSamples:
        - label: firstPage
          lang: bash
          source: |-
            curl --request POST \
              --url 'https://api.airscale.io/v1/post-commenters' \
              --header "Authorization: Bearer $AIRSCALE_API_KEY" \
              --header "Content-Type: application/json" \
              --header "Idempotency-Key: ${AIRSCALE_PAGE_KEY:?Set a UUID for this page and retain it for retries}" \
              --data '{
              "post_url": "https://example.com/linkedin-post-example",
              "limit": 25
            }'
        - label: nextPage
          lang: bash
          source: |-
            curl --request POST \
              --url 'https://api.airscale.io/v1/post-commenters' \
              --header "Authorization: Bearer $AIRSCALE_API_KEY" \
              --header "Content-Type: application/json" \
              --header "Idempotency-Key: ${AIRSCALE_PAGE_KEY:?Set a UUID for this page and retain it for retries}" \
              --data '{
              "post_url": "https://example.com/linkedin-post-example",
              "limit": 25,
              "cursor": "pje1.synthetic_cursor"
            }'
        - label: firstPage
          lang: node
          source: >-
            const response = await
            fetch("https://api.airscale.io/v1/post-commenters", {
              method: "POST",
              headers: {
                Authorization: `Bearer ${process.env.AIRSCALE_API_KEY}`,
                "Content-Type": "application/json",
                "Idempotency-Key": process.env.AIRSCALE_PAGE_KEY
              },
              body: JSON.stringify({
              "post_url": "https://example.com/linkedin-post-example",
              "limit": 25
            })

            });


            const data = await response.json();

            console.log(data);
        - label: nextPage
          lang: node
          source: >-
            const response = await
            fetch("https://api.airscale.io/v1/post-commenters", {
              method: "POST",
              headers: {
                Authorization: `Bearer ${process.env.AIRSCALE_API_KEY}`,
                "Content-Type": "application/json",
                "Idempotency-Key": process.env.AIRSCALE_PAGE_KEY
              },
              body: JSON.stringify({
              "post_url": "https://example.com/linkedin-post-example",
              "limit": 25,
              "cursor": "pje1.synthetic_cursor"
            })

            });


            const data = await response.json();

            console.log(data);
        - label: firstPage
          lang: python
          source: >-
            import os

            import requests


            response =
            requests.post("https://api.airscale.io/v1/post-commenters",
              headers={
                "Authorization": f'Bearer {os.environ["AIRSCALE_API_KEY"]}',
                "Content-Type": "application/json",
                "Idempotency-Key": os.environ["AIRSCALE_PAGE_KEY"]
              },
              json={
              "post_url": "https://example.com/linkedin-post-example",
              "limit": 25
            }

            )

            print(response.json())  # Retain any returned retry key before
            handling errors.

            response.raise_for_status()
        - label: nextPage
          lang: python
          source: >-
            import os

            import requests


            response =
            requests.post("https://api.airscale.io/v1/post-commenters",
              headers={
                "Authorization": f'Bearer {os.environ["AIRSCALE_API_KEY"]}',
                "Content-Type": "application/json",
                "Idempotency-Key": os.environ["AIRSCALE_PAGE_KEY"]
              },
              json={
              "post_url": "https://example.com/linkedin-post-example",
              "limit": 25,
              "cursor": "pje1.synthetic_cursor"
            }

            )

            print(response.json())  # Retain any returned retry key before
            handling errors.

            response.raise_for_status()
components:
  responses:
    Unauthorized:
      description: The Bearer token is missing or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
        message:
          type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Use an Airschool workspace API key. Never expose the key in client-side
        code.

````