Anjal LinkedIn — Signals API

Deterministic real-time intent watchers. Every signal is a keyword plus a plain-English firing rule (e.g. "Flag as ready when the author is a founder complaining about cold email — skip vendors and agencies"). Two-pass classifier: 1. Triage — text-only LLM scores each post 0–100; obvious rejects drop without paying any LinkedIn quota. 2. Verify — survivors get a profile lookup (cached, multi-session routed) and the full classifier runs with the author's role + company as authoritative context. The firing rule decides engage/intent, the reason cites evidence from the post. Sessions auto-distribute — by default polling spreads across all healthy LinkedIn sessions for the tenant, multiplying daily throughput by the number of connected accounts. Pass a specific sessionId on the create body to pin instead. Optional HMAC-signed webhook delivery with automatic retries. Auditable end-to-end.

Operations 12

GET /linkedin/signals List signals #
POST /linkedin/signals Create a signal #
GET /linkedin/signals/{id} Fetch a signal #
PATCH /linkedin/signals/{id} Update a signal #
DELETE /linkedin/signals/{id} Delete a signal #
POST /linkedin/signals/{id}/run Run a signal now #
GET /linkedin/signals/{id}/matches List matches for a signal #
GET /linkedin/signals/{id}/deliveries Webhook delivery log #
POST /linkedin/signals/{id}/test-webhook Send a synthetic webhook payload #
GET /linkedin/signals/{id}/watchlist List watchlist entries #
POST /linkedin/signals/{id}/watchlist Add watchlist entries #
DELETE /linkedin/signals/{id}/watchlist/{entryId} Remove a watchlist entry #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/anjal-app:anjal-app-linkedin-signals-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

anjal-app-linkedin-signals-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Anjal LinkedIn — Signals API
  version: 1.0.0
  description: 'Anjal is a multi-tenant email API for AI agents.


    Create inboxes on the default `@send.anjal.app` domain or your own custom

    domains, send and receive mail, manage drafts, subscribe to real-time

    events over WebSocket, and isolate resources across workspaces so your

    reseller customers can serve their own end-customers under a single

    billing relationship.


    Install the TypeScript SDK: `npm install anjal`

    (npm)


    MCP server for Claude Desktop / Cursor: `npx -y anjal-mcp`

    (npm)


    ## Authentication


    Every request is authenticated with the `X-API-Key` header (or

    `Authorization: Bearer `). Four key prefixes, each with increasing

    scope:


    | Prefix | Scope | Use it for |

    |----------|-----------------------------------------|-----------------------------------------|

    | `sa_...` | Super admin | Tenant lifecycle (platform operator) |

    | `tk_...` | Tenant master | Everything in your tenant across workspaces |

    | `wk_...` | Workspace master | One workspace only |

    | `ak_...` | Inbox (agent) scoped | One inbox — its messages, drafts, lists |


    ## Resource hierarchy


    ```

    Tenant (billing root)

    └── Workspace (isolation container)

    ├── Inboxes

    │ ├── Messages / Threads / Attachments

    │ ├── Drafts

    │ └── Lists

    └── Domains

    ```


    ## Sending mail


    Mail is sent via `POST /v1/inboxes/{id}/send`. Replies go through

    `/reply/{messageId}`, reply-all through `/reply-all/{messageId}`, and

    forwards through `/forward/{messageId}`. The `From` header always uses

    the inbox''s **primary alias** — add a custom-domain address with

    `POST /v1/inboxes/{id}/addresses` to change it.


    ### Safe retries (Idempotency-Key)


    Every send endpoint (`/send`, `/reply`, `/reply-all`, `/forward` and

    `POST /v1/inboxes/{id}/drafts/{did}/send`) accepts an `Idempotency-Key`

    header. Send a unique value (a UUID works) and retry freely: the first

    request sends and stores its response; a retry with the same key and the

    identical request returns that stored response with the header

    `Idempotent-Replayed: true` and sends nothing.


    | Situation | Result |

    |---|---|

    | Same key, different request (body, inbox or endpoint) | `409 IDEMPOTENCY_KEY_REUSED` |

    | Same key while the first request is still running | `409 IDEMPOTENCY_IN_PROGRESS` |

    | Header present but blank, or longer than 255 characters | `400 IDEMPOTENCY_KEY_INVALID` |

    | The first send failed (non-2xx) | The key is freed; retry with the same key |


    Keys are scoped to your account and expire after 24 hours.


    ### Labels, open tracking, scheduled send


    - **Labels**: pass `labels` (array, or a comma-separated string) on any

    send, reply, reply-all, forward or draft. Change them later with

    `PATCH /v1/inboxes/{id}/messages/{messageId}` or

    `PATCH /v1/inboxes/{id}/threads/{threadId}`, and filter lists with

    `?labels=a,b`. Labels are stored lowercase and deduplicated; at most 32

    per message, each at most 64 characters, no commas.

    - **Open tracking**: `trackOpens: true` on a send with an HTML body adds a

    1x1 pixel (the stored copy never contains it). The first open fires

    `message.opened`. Opens are a hint, not proof: Apple Mail Privacy

    Protection fetches images on delivery, and clients that block images

    never report an open.

    - **Scheduled send**: set `sendAt` on a draft and Anjal sends it at that

    time. See the Drafts endpoints.

    - **Delivery status**: sent messages carry `deliveryStatus`

    (`delivered`, `bounced`, `complained` or `rejected`; null until known)

    and `deliveredAt`.


    ## Receiving mail


    Inbound mail arrives via SMTP on the default domain or any verified

    custom domain. Subscribe to delivery events either by registering a

    webhook (`POST /v1/webhooks`) or by opening a WebSocket

    (`wss://anjal.app/v1/ws`) — useful for in-line 2FA flows where

    the agent doesn''t have a public webhook receiver.'
servers:
- url: https://anjal.app
  description: Production
security:
- apiKeyHeader: []
tags:
- name: LinkedIn — Signals
  description: 'Deterministic real-time intent watchers. Every signal is a keyword

    plus a plain-English firing rule (e.g. "Flag as ready when the

    author is a founder complaining about cold email — skip vendors and

    agencies"). Two-pass classifier:


    1. Triage — text-only LLM scores each post 0–100; obvious rejects

    drop without paying any LinkedIn quota.

    2. Verify — survivors get a profile lookup (cached, multi-session

    routed) and the full classifier runs with the author''s role +

    company as authoritative context. The firing rule decides

    engage/intent, the reason cites evidence from the post.


    Sessions auto-distribute — by default polling spreads across all

    healthy LinkedIn sessions for the tenant, multiplying daily

    throughput by the number of connected accounts. Pass a specific

    sessionId on the create body to pin instead.


    Optional HMAC-signed webhook delivery with automatic retries.

    Auditable end-to-end.'
paths:
  /linkedin/signals:
    get:
      tags:
      - LinkedIn — Signals
      summary: List signals
      description: List all intent signals for the authenticated tenant.
      operationId: linkedinListSignals
      responses:
        '200':
          description: Signals owned by the tenant.
          content:
            application/json:
              schema:
                type: object
                properties:
                  signals:
                    type: array
                    items:
                      $ref: '#/components/schemas/LinkedInSignal'
    post:
      tags:
      - LinkedIn — Signals
      summary: Create a signal
      description: 'Create a real-time intent watcher. We poll LinkedIn on the chosen

        cadence using the named session, classify each NEW post against

        the keyword, and (if a webhookUrl is set) POST high-intent matches

        to your endpoint with an HMAC-SHA256 signature. The lookback window

        is fixed at 24 hours — for historical lookups across a wider window

        use POST /v1/linkedin/searches instead.'
      operationId: linkedinCreateSignal
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - intentDescription
              properties:
                name:
                  type: string
                  maxLength: 255
                kind:
                  type: string
                  enum:
                  - keyword
                  - engagement
                  - job_change_watchlist
                  default: keyword
                  description: 'keyword (default) — saved keyword search.

                    engagement — watch a profile/company for engagers; requires `target`.

                    job_change_watchlist — watch a list of profiles for role/company changes; requires `watchlist`.

                    '
                query:
                  type: string
                  minLength: 2
                  maxLength: 500
                  description: Required when kind=keyword.
                target:
                  type: object
                  description: Required when kind=engagement.
                  properties:
                    kind:
                      type: string
                      enum:
                      - profile
                      - company
                    url:
                      type: string
                      format: uri
                    label:
                      type: string
                      maxLength: 255
                  required:
                  - kind
                  - url
                watchlist:
                  type: array
                  description: Required when kind=job_change_watchlist. Up to 500 entries.
                  maxItems: 500
                  items:
                    type: object
                    properties:
                      profileUrl:
                        type: string
                        format: uri
                      label:
                        type: string
                        maxLength: 255
                    required:
                    - profileUrl
                sessionId:
                  type: string
                  nullable: true
                  description: 'Omit or send null to auto-distribute polling across

                    every healthy LinkedIn session for this tenant

                    (recommended — multiplies daily throughput by the

                    number of connected accounts and protects each

                    individual account from rate limits). Pass a

                    specific session id to pin all polls for this

                    signal to that account.

                    '
                cadence:
                  type: string
                  enum:
                  - daily
                  - every_12h
                  - every_6h
                  - manual
                  default: daily
                webhookUrl:
                  type: string
                  format: uri
                  nullable: true
                filterMinIntent:
                  type: string
                  enum:
                  - low
                  - medium
                  - high
                  default: medium
                intentDescription:
                  type: string
                  minLength: 1
                  maxLength: 2000
                  description: 'Plain-English firing rule the LLM classifier treats as

                    the authoritative definition of what should fire.

                    Example: "Flag as ready when the author is a founder/

                    operator complaining about cold email — skip vendors,

                    agencies, and content marketers."

                    '
      responses:
        '201':
          description: 'Created signal. webhookSecret is returned ONCE on creation when

            webhookUrl is set — store it now to verify webhook signatures.

            '
          content:
            application/json:
              schema:
                type: object
                properties:
                  signal:
                    $ref: '#/components/schemas/LinkedInSignal'
        '403':
          description: Signal limit reached for the tenant's tier.
        '404':
          description: LinkedIn session not found or inactive.
  /linkedin/signals/{id}:
    get:
      tags:
      - LinkedIn — Signals
      summary: Fetch a signal
      operationId: linkedinGetSignal
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  signal:
                    $ref: '#/components/schemas/LinkedInSignal'
        '404':
          description: Not found.
    patch:
      tags:
      - LinkedIn — Signals
      summary: Update a signal
      operationId: linkedinUpdateSignal
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                query:
                  type: string
                sessionId:
                  type: string
                  nullable: true
                  description: null to switch to auto-distribute, or a session id to pin.
                cadence:
                  type: string
                  enum:
                  - daily
                  - every_12h
                  - every_6h
                  - manual
                webhookUrl:
                  type: string
                  format: uri
                  nullable: true
                filterMinIntent:
                  type: string
                  enum:
                  - low
                  - medium
                  - high
                intentDescription:
                  type: string
                  minLength: 1
                  maxLength: 2000
                enabled:
                  type: boolean
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  signal:
                    $ref: '#/components/schemas/LinkedInSignal'
        '404':
          description: Not found.
    delete:
      tags:
      - LinkedIn — Signals
      summary: Delete a signal
      operationId: linkedinDeleteSignal
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Deleted.
        '404':
          description: Not found.
  /linkedin/signals/{id}/run:
    post:
      tags:
      - LinkedIn — Signals
      summary: Run a signal now
      description: Force an immediate poll regardless of cadence. Useful for testing.
      operationId: linkedinRunSignal
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
                    properties:
                      ok:
                        type: boolean
                      fetched:
                        type: integer
                      newMatches:
                        type: integer
                      webhooksQueued:
                        type: integer
                      errorCode:
                        type: string
                        nullable: true
                      errorMessage:
                        type: string
                        nullable: true
  /linkedin/signals/{id}/matches:
    get:
      tags:
      - LinkedIn — Signals
      summary: List matches for a signal
      description: All historical matches for this signal, newest first. Pull-based alternative to webhooks.
      operationId: linkedinSignalMatches
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      - in: query
        name: limit
        schema:
          type: integer
          default: 50
          maximum: 500
      - in: query
        name: sinceId
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  matches:
                    type: array
                    items:
                      $ref: '#/components/schemas/LinkedInSignalMatch'
  /linkedin/signals/{id}/deliveries:
    get:
      tags:
      - LinkedIn — Signals
      summary: Webhook delivery log
      operationId: linkedinSignalDeliveries
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      - in: query
        name: status
        schema:
          type: string
          enum:
          - pending
          - success
          - failed
          - exhausted
      - in: query
        name: limit
        schema:
          type: integer
          default: 50
  /linkedin/signals/{id}/test-webhook:
    post:
      tags:
      - LinkedIn — Signals
      summary: Send a synthetic webhook payload
      description: Fires a `signal.match.test` event to the configured webhook URL so you can verify the endpoint before any real match.
      operationId: linkedinSignalTestWebhook
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  status:
                    type: integer
                  responseBody:
                    type: string
        '400':
          description: Signal has no webhook URL configured.
  /linkedin/signals/{id}/watchlist:
    get:
      tags:
      - LinkedIn — Signals
      summary: List watchlist entries
      description: List all profile entries on a watchlist signal, with last-known role/company.
      operationId: linkedinSignalWatchlistList
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  entries:
                    type: array
                    items:
                      $ref: '#/components/schemas/LinkedInWatchlistEntry'
        '400':
          description: Signal is not a watchlist signal.
    post:
      tags:
      - LinkedIn — Signals
      summary: Add watchlist entries
      operationId: linkedinSignalWatchlistAdd
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - entries
              properties:
                entries:
                  type: array
                  minItems: 1
                  maxItems: 500
                  items:
                    type: object
                    properties:
                      profileUrl:
                        type: string
                        format: uri
                      label:
                        type: string
                        maxLength: 255
                    required:
                    - profileUrl
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  added:
                    type: integer
                  skipped:
                    type: integer
                    description: Duplicates or invalid URLs.
                  entries:
                    type: array
                    items:
                      $ref: '#/components/schemas/LinkedInWatchlistEntry'
  /linkedin/signals/{id}/watchlist/{entryId}:
    delete:
      tags:
      - LinkedIn — Signals
      summary: Remove a watchlist entry
      operationId: linkedinSignalWatchlistRemove
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      - in: path
        name: entryId
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
        '404':
          description: Entry not found.
components:
  schemas:
    LinkedInSignal:
      type: object
      required:
      - id
      - kind
      - name
      - cadence
      - filterMinIntent
      - intentDescription
      - enabled
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
          - keyword
          - engagement
          - job_change_watchlist
          description: "Acquisition strategy for this signal:\n  - keyword: poll LinkedIn search for the saved query.\n  - engagement: poll a tracked profile/company's recent posts and fire on engagers.\n  - job_change_watchlist: poll a list of profiles weekly and fire on role/company changes.\n"
        name:
          type: string
        query:
          type: string
          nullable: true
          description: Required for kind=keyword; null otherwise.
        target:
          type: object
          nullable: true
          description: Engagement signals only — the resolved actor target.
          properties:
            kind:
              type: string
              enum:
              - profile
              - company
            url:
              type: string
            urn:
              type: string
              description: Resolved urn:li:fsd_profile:* or urn:li:fsd_company:*.
            label:
              type: string
              nullable: true
        lastSeenPostUrn:
          type: string
          nullable: true
          description: Engagement signals only — watermark of the most recent post processed. We only fire on engagers of posts newer than this.
        watchlistCount:
          type: integer
          nullable: true
          description: Watchlist signals only — number of profiles being tracked.
        sessionId:
          type: string
          nullable: true
          description: 'null = auto-distribute polling across all healthy LinkedIn

            sessions for this tenant (recommended). Specific id = pin

            the signal to that account so all polls come from it.

            '
        cadence:
          type: string
          enum:
          - daily
          - every_12h
          - every_6h
          - manual
        webhookUrl:
          type: string
          nullable: true
        webhookSecret:
          type: string
          nullable: true
          description: Returned only on creation. Verify with HMAC-SHA256.
        filterMinIntent:
          type: string
          enum:
          - low
          - medium
          - high
        intentDescription:
          type: string
          minLength: 1
          maxLength: 2000
          description: 'Plain-English firing rule the LLM classifier uses as the

            authoritative definition of what should fire. Example: "Flag as

            ready when the author is a founder/operator complaining about

            cold email — skip vendors, agencies, and content marketers."

            The keyword is only a coarse pre-filter; this rule decides

            engage=true/false and the cited reason on every match.

            '
        enabled:
          type: boolean
        lastPolledAt:
          type: string
          format: date-time
          nullable: true
        nextPollAt:
          type: string
          format: date-time
          nullable: true
        lastError:
          type: string
          nullable: true
        lastErrorCode:
          type: string
          nullable: true
        lastErrorAt:
          type: string
          format: date-time
          nullable: true
        matchesCount:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    LinkedInWatchlistEntry:
      type: object
      properties:
        id:
          type: string
        profileUrl:
          type: string
        profileSlug:
          type: string
        fullName:
          type: string
          nullable: true
        lastRole:
          type: string
          nullable: true
        lastCompany:
          type: string
          nullable: true
        lastPolledAt:
          type: string
          format: date-time
          nullable: true
        addedAt:
          type: string
          format: date-time
    LinkedInSignalMatch:
      type: object
      description: 'Discriminated by `kind`. Engagement matches include `engagement.commentText` (verbatim);

        job-change matches include `jobChange.{old,new}{Role,Company}`. Keyword matches leave both null.

        '
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
          - keyword
          - engagement
          - job_change
        postUrl:
          type: string
        postExcerpt:
          type: string
        postPostedAt:
          type: string
          format: date-time
          nullable: true
        engagement:
          type: object
          nullable: true
          properties:
            action:
              type: string
              enum:
              - commented
              - reacted
            commentText:
              type: string
              nullable: true
        jobChange:
          type: object
          nullable: true
          properties:
            oldRole:
              type: string
              nullable: true
            oldCompany:
              type: string
              nullable: true
            newRole:
              type: string
              nullable: true
            newCompany:
              type: string
              nullable: true
        author:
          type: object
          properties:
            name:
              type: string
            profileUrl:
              type: string
            headline:
              type: string
              nullable: true
              description: From the verified LinkedIn profile lookup.
            role:
              type: string
              nullable: true
              description: Parsed current role (e.g. "Founder & CEO"). null when profile lookup was deferred.
            company:
              type: string
              nullable: true
              description: Parsed current company. null when profile lookup was deferred.
        classification:
          type: object
          properties:
            engage:
              type: boolean
            intent:
              type: string
              enum:
              - low
              - medium
              - high
            reason:
              type: string
        triageScore:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
          description: Pass-1 triage classifier score (0-100). Posts below the threshold are not persisted.
        pendingEnrichment:
          type: boolean
          description: true when the post passed triage but the daily profile-lookup cap forced us to skip the full Pass-2 verification. Webhook delivery is held until the next poll fully verifies the match.
        foundAt:
          type: string
          format: date-time
  securitySchemes:
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'Any of `sa_`, `tk_`, `wk_`, `ak_`, or `mam_` keys. Scope is enforced

        per-endpoint — see individual operation descriptions.

        '