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

# List Signal Leads

> Fetch the leads your Datamagnet engagement signals have found. Filter by signal, lead score, keyword, engagement type, name or headline, and date, and page through your full history.

# List Signal Leads

Returns the leads your engagement signals ([`keyword_engagement`](/api-reference/endpoints/signal-keyword-engagement), [`industry_engagement`](/api-reference/endpoints/signal-industry-engagement), [`company_engagement`](/api-reference/endpoints/signal-company-engagement), and [`person_engagement`](/api-reference/endpoints/signal-person-engagement)) have stored, newest first. Use it to read past leads: to backfill after a missed [webhook](/api-reference/webhooks), to rebuild a report, or to pull only what is new since your last call.

The leads are always your own. They are found from your API key, so a request cannot name another account. This request costs no credits.

***

## Request

Every attribute is optional. With none, you get your 50 most recent leads across all your signals. Filters combine: a lead has to match all of the ones you send.

### Filters

<ParamField body="signal_id" type="string">
  Only leads found by this signal. Get the ID from [/signal/create](/api-reference/endpoints/signal-create) or [/signal/list](/api-reference/endpoints/signal-list). A signal ID that is not yours returns an empty page.
</ParamField>

<ParamField body="min_score" type="integer">
  Only leads with a [lead score](/api-reference/endpoints/signal-keyword-engagement#lead-scoring) of at least this, from `1` to `3`.
</ParamField>

<ParamField body="max_score" type="integer">
  Only leads with a lead score of at most this, from `1` to `3`. Must not be lower than `min_score`.
</ParamField>

<ParamField body="keyword" type="string">
  Only leads that this keyword, industry, company, or person label surfaced (the lead's `signal_keyword`). Matches the whole value, in any letter case. Up to 200 characters.
</ParamField>

<ParamField body="engagement_type" type="string">
  How the person engaged: `comment`, `like`, or another reaction such as `celebrate` or `support`. Any letter case. Up to 50 characters.
</ParamField>

<ParamField body="search" type="string">
  Only leads whose name or headline contains this text, in any letter case. `%` and `_` are matched as themselves. Up to 100 characters.
</ParamField>

<ParamField body="created_from" type="string">
  Only leads found at or after this moment, as an ISO 8601 date or time, for example `2026-09-01` or `2026-09-01T09:00:00Z`. A date alone means the start of that day, and a time without a zone is read as UTC.
</ParamField>

<ParamField body="created_to" type="string">
  Only leads found before this moment, in the same format. Leads found exactly at this moment are not included. Must be later than `created_from`.
</ParamField>

### Sorting and paging

<ParamField body="sort" type="string" default="created_at_desc">
  The order of the leads:

  * `created_at_desc`: newest first (default)
  * `created_at_asc`: oldest first
  * `lead_score_desc`: highest lead score first, newest first among equal scores
  * `icp_score_desc`: best ICP fit first, newest first among equal scores
</ParamField>

<ParamField body="limit" type="integer" default="50">
  Leads per page, from `1` to `200`.
</ParamField>

<ParamField body="offset" type="integer" default="0">
  Leads to skip, from `0` to `1000000`. To go deeper than that, narrow the range with `created_from` and `created_to`.
</ParamField>

Text values are trimmed of surrounding spaces. Any other attribute, including one that names a user or an email, is rejected with `400`, so a misspelled filter cannot quietly return unfiltered leads.

<RequestExample>
  ```json Request theme={null}
  {
    "signal_id": "e7f1c2a4-9b3d-4c8e-a1f5-6d2b0c9e8a7f",
    "min_score": 2,
    "engagement_type": "comment",
    "created_from": "2026-09-01",
    "sort": "lead_score_desc",
    "limit": 50
  }
  ```
</RequestExample>

***

## Response

<ResponseExample>
  ```json Response theme={null}
  {
    "message": {
      "total": 137,
      "limit": 50,
      "offset": 0,
      "has_more": true,
      "leads": [
        {
          "id": "0b9c6d5e-3f2a-4b1c-8d7e-6a5f4e3d2c1b",
          "signal_id": "e7f1c2a4-9b3d-4c8e-a1f5-6d2b0c9e8a7f",
          "linkedin_url": "https://linkedin.com/in/johndoe",
          "profile_name": "John Doe",
          "headline": "VP Sales at Acme Corp",
          "signal_keyword": "sales intelligence",
          "engagement_type": "comment",
          "engagement_post_url": "https://linkedin.com/posts/...",
          "comment_text": "Great point on outbound",
          "post_excerpt": "We just shipped a new way to prioritise outbound...",
          "post_author": "Jane Roe",
          "lead_score": 3,
          "icp_score": 0.87,
          "signal_score": 0.91,
          "icp_match_details": {
            "title": { "matched_target": "VP Sales", "seniority": "vp", "confidence": 0.95 },
            "industry": { "status": "match", "matched_target": "SaaS" },
            "company_size": "match",
            "location": "match"
          },
          "detected_at": "2026-09-24T09:15:00+00:00"
        }
      ]
    },
    "credit_used": 0
  }
  ```

  ```json No leads theme={null}
  {
    "message": {
      "total": 0,
      "limit": 50,
      "offset": 0,
      "has_more": false,
      "leads": []
    },
    "credit_used": 0
  }
  ```
</ResponseExample>

## Response fields

<ResponseField name="message" type="object">
  <Expandable title="Page">
    <ResponseField name="total" type="integer">
      How many leads match your filters in all, not just on this page.
    </ResponseField>

    <ResponseField name="limit" type="integer">
      The page size used.
    </ResponseField>

    <ResponseField name="offset" type="integer">
      The number of leads skipped.
    </ResponseField>

    <ResponseField name="has_more" type="boolean">
      `true` when there are more leads after this page. Send `offset` plus `limit` to get them.
    </ResponseField>

    <ResponseField name="leads" type="array">
      The leads on this page.

      <Expandable title="Lead">
        <ResponseField name="id" type="string">
          The lead's unique ID. Stable, so you can use it to skip leads you already have.
        </ResponseField>

        <ResponseField name="signal_id" type="string">
          The signal that found the lead.
        </ResponseField>

        <ResponseField name="linkedin_url" type="string">
          The LinkedIn profile URL of the person who engaged.
        </ResponseField>

        <ResponseField name="profile_name" type="string">
          Full name of the person.
        </ResponseField>

        <ResponseField name="headline" type="string">
          Their LinkedIn headline when the lead was found.
        </ResponseField>

        <ResponseField name="signal_keyword" type="string">
          The keyword, industry, company, or person label that surfaced the lead.
        </ResponseField>

        <ResponseField name="engagement_type" type="string">
          `comment`, or a reaction such as `like`, `celebrate`, or `support`.
        </ResponseField>

        <ResponseField name="engagement_post_url" type="string">
          The LinkedIn post the person engaged with.
        </ResponseField>

        <ResponseField name="comment_text" type="string | null">
          The comment they left. `null` for reactions.
        </ResponseField>

        <ResponseField name="post_excerpt" type="string | null">
          The start of the post they engaged with. `null` if the post text could not be retrieved.
        </ResponseField>

        <ResponseField name="post_author" type="string | null">
          Who wrote the post. `null` if it could not be retrieved.
        </ResponseField>

        <ResponseField name="lead_score" type="integer">
          Overall lead quality, from `1` to `3`. See [lead scoring](/api-reference/endpoints/signal-keyword-engagement#lead-scoring).
        </ResponseField>

        <ResponseField name="icp_score" type="number">
          How well the lead fits your ICP, from `0.0` to `1.0`.
        </ResponseField>

        <ResponseField name="signal_score" type="number">
          Intent signal strength, from `0.0` to `1.0`.
        </ResponseField>

        <ResponseField name="icp_match_details" type="object">
          How the lead matched the ICP filters on the signal. Which keys appear depends on the filters you set, for example `title`, `industry`, `company_size`, `location`, and `seniority`. Empty when the signal has no ICP.
        </ResponseField>

        <ResponseField name="detected_at" type="string">
          When the lead was **first** found, as an ISO 8601 time in UTC.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="credit_used" type="integer">
  Always `0`.
</ResponseField>

***

## Getting all your leads

**Your whole history.** Page with `limit` and `offset` until `has_more` is `false`. Set `created_to` to the current time before your first request and keep it the same for the whole walk. Otherwise leads that arrive while you are paging shift the pages.

**Only what is new.** Send `sort` as `created_at_asc` and `created_from` set a minute before the newest `detected_at` you already have, then skip any `id` you already hold. Leads from one run are stored together and share a `detected_at`, and the minute of overlap covers a run that finishes just after another.

<Note>
  `detected_at` is when a lead was first found. If a later run finds the same person for the same keyword, it updates that lead's scores and post in place and keeps its `detected_at`, so a lead you already have can change without showing up again in a `created_from` request.
</Note>

## Notes

* The lead fields are the same as in a [webhook](/api-reference/webhooks#engagement-signal-payload-fields), plus `id`, `signal_id`, and `icp_match_details`. `engagement_link` is not included, because the link to the exact comment is not stored: use `engagement_post_url`.
* Leads outlive their signal. Deleting a signal does not delete the leads it found, and they stay listed under its old `signal_id`.
* Returns `400` for an invalid filter, with a message that names the attribute, for example `min_score must be an integer from 1 to 3`.
* Returns `401` for an invalid API key, or when the account has no credits left, even though this request costs none.
* Returns `403` if your account does not have access to Signals. Contact [signals@datamagnet.co](mailto:signals@datamagnet.co).
* Returns `500` with `Failed to fetch leads` if the request could not be completed. Try again.
