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

# Top buyers in a place

> Who bought most often in one ZIP in the last 90 days: one call, in-place counts beside market-wide facts, the operator rule, the contact facts.

One call to [`GET /v1/investors`](/api-reference/endpoints/investors-list) answers the question "who buys in this ZIP
now". Give the ZIP as the location. A ZIP or a postal city is a location like a point or a viewport. Set `bought_after`
to the market's data end minus 90 days. Set `sort=events_desc`, the default order. It ranks by distinct purchase days
inside the place.

Each row carries that trailing count beside the investor's market-wide facts. The same row says if a skip trace has
anyone to look for.

## 1. The call

Scottsdale's 85257, the purchases of the last 90 days of data, most purchase days first:

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.investorlift.com/v1/investors?zip=85257&bought_after=2026-05-29&sort=events_desc" \
    -H "Authorization: Bearer $GM_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({ zip: "85257", bought_after: "2026-05-29", sort: "events_desc" });
  const res = await fetch(`https://api.investorlift.com/v1/investors?${params}`, {
    headers: { Authorization: `Bearer ${process.env.GM_API_KEY}` },
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const body = await res.json();
  ```

  ```python Python theme={null}
  import os

  import requests

  r = requests.get(
      "https://api.investorlift.com/v1/investors",
      params={"zip": "85257", "bought_after": "2026-05-29", "sort": "events_desc"},
      headers={"Authorization": f"Bearer {os.environ['GM_API_KEY']}"},
      timeout=30,
  )
  r.raise_for_status()
  body = r.json()
  ```
</CodeGroup>

`bought_after` is an absolute date. Nothing bounds the purchases counted inside the place relative to today or to
`data_end`. `active_within_months` keeps investors by their last purchase anywhere in the market, in months before
`data_end`. It moves no count.

The anchor is the market's own clock, `meta.coverage[].data_end`
([Coverage and freshness](/guides/concepts/coverage#as-of-dates)), not today. The data end runs weeks behind the day
you ask. On the day of writing, the lag was between about three and five weeks across the loaded markets. So today
minus 90 days shortens the window by that lag with no warning, and counts days the data does not hold yet.

Read `data_end` first, from [`GET /v1/dataset`](/api-reference/endpoints/dataset) (not metered) or from the
`meta.coverage[]` of any earlier response. Subtract 90 days, and write the date. A market whose `data_end` is the last
day of August opens its window at the start of June. Recompute it after every refresh. `data_end` moves, so a
`bought_after` that you wrote once covers a longer window after each refresh. Rows with no purchase date leave the
count once you set `bought_after`, and `include_undated=true` keeps them.

## 2. Or around a point

The place form measures no distance. When the question is "within a mile of this house", not "in this ZIP", give a
point and a radius instead. Use the same filter and the same sort:

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.investorlift.com/v1/investors?lat=33.476917&lng=-111.920385&radius_miles=1&bought_after=2026-05-29&sort=events_desc" \
    -H "Authorization: Bearer $GM_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    lat: "33.476917",
    lng: "-111.920385",
    radius_miles: "1",
    bought_after: "2026-05-29",
    sort: "events_desc",
  });
  const res = await fetch(`https://api.investorlift.com/v1/investors?${params}`, {
    headers: { Authorization: `Bearer ${process.env.GM_API_KEY}` },
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const body = await res.json();
  ```

  ```python Python theme={null}
  import os

  import requests

  r = requests.get(
      "https://api.investorlift.com/v1/investors",
      params={
          "lat": 33.476917,
          "lng": -111.920385,
          "radius_miles": 1,
          "bought_after": "2026-05-29",
          "sort": "events_desc",
      },
      headers={"Authorization": f"Bearer {os.environ['GM_API_KEY']}"},
      timeout=30,
  )
  r.raise_for_status()
  body = r.json()
  ```
</CodeGroup>

Each row then carries `in_radius.nearest_distance_miles` from the point. With a bare ZIP it is null. `lat` and `lng`
beside `zip` are allowed. They are the reference point for that distance alone, and the ZIP stays the geometry.
`radius_miles` beside `zip` is [`400 geometry_conflict`](/guides/concepts/errors#geometry_conflict): one location per
request.

Over MCP the same question is [`godmode_investors_nearby`](/mcp/tools#godmode_investors_nearby) with the same inputs:
`zip: ["85257"]`, `bought_after: "2026-05-29"` and `sort: "events_desc"`, or `latitude`, `longitude` and `radius_miles`
for the point form. Its concise format, the default, keeps the identity, `operator_id`, the contact counts and the
geometry's `n_events`, `n_parcels`, `volume` and `last_bought_on`. It leaves out `in_radius.n_purchase_events`,
`investor.n_deals`, `investor.n_purchase_events_12m`, `investor.days_since_last_purchase` and
`investor.n_operator_members`. Ask for `response_format: "detailed"` to read those.

## 3. Read the row

Each row is an [investor list row](/api-reference/objects/investor): `investor`, `in_radius`, `contact` and the
per-market blocks. The fields this ranking is about:

<ResponseField name="in_radius.n_purchase_events" type="integer">
  Distinct purchase days inside the ZIP on or after `bought_after`: the trailing count, and what `sort=events_desc`
  ranks by. A bulk buy of four parcels on one day is one event. `in_radius.n_parcels` counts the parcels.
  `in_radius.volume` sums the prices the deeds state, or is null when no deed here carries a price.
  `in_radius.last_bought_on` is the latest of those days. The count is purchases alone: `in_radius.last_deal_on` also
  moves on a sale or a transfer inside the ZIP, and is for display.
</ResponseField>

<ResponseField name="investor.n_deals, investor.n_purchase_events_12m, investor.days_since_last_purchase" type="mixed">
  The same investor across the whole market, untouched by the ZIP and the window. The first is every investment deal
  as buyer inside the area loaded for the market, all kinds and years. The second is the distinct purchase days in the
  12 months before `data_end`. The third is the days from their last purchase anywhere to `data_end`, or null when
  they never bought.

  Read the pair together. Six purchase days here beside an `n_deals` in the hundreds is a large operator that works
  this neighbourhood among others. The same six beside `n_deals: 6` is a buyer who works only here. The API counts
  every "days since" to `data_end`, never to today.
</ResponseField>

<ResponseField name="investor.operator_id, investor.n_operator_members" type="mixed">
  Rows that share an `operator_id` are one buying operation, and a fresh LLC per deal is common. Collapse them and
  contact them once. The API ranks and pages each id on its own. So an operator with three ids can take three places
  in this ranking and fall across two pages. Keep the row whose `id` equals `operator_id` as the visible one, and never
  sum the counts across the members.

  Group by it within one response. Store the investor ids, not the operator id, which can move to another member after
  a refresh. [Same buyer, cash right now](/guides/walkthroughs/same-buyer) has the whole rule.
</ResponseField>

<ResponseField name="contact.n_skip_trace_targets, contact.has_person_member, contact.has_home_address" type="mixed">
  The contact facts every key sees. The first is how many person-and-address pairings a skip trace can start from. The
  second says if a natural person appears among the deed names. The third says if one of them has a non-generic house
  address on a deed. The two flags also appear on `investor`.

  The API does not serve the identifying fields of the block (`primary_address`, `mailing_addresses`, `person_members`,
  `skip_trace_targets`) on this host. They are null with `contact_redacted: true` while the counts stand, so the block
  itself is never null. A row with `has_person_member: false` and `n_skip_trace_targets: 0` is an entity with no person
  on any deed. The next step is the corporate registry, not a skip trace.
</ResponseField>

<ResponseField name="summary.n_investors_ranked" type="integer">
  The whole ZIP after the filters, not the page: the total the pages walk. `summary.by_buyer_status` counts the deal
  rows behind it by buyer status. The `unclassified`, `excluded` and `unknown` buyers have no investor id and never
  appear as rows here.
</ResponseField>

## Narrow it

* `investor_deals_min=2` keeps the repeat buyers: at least two purchase days inside the ZIP in the window. The API
  applies it after the other filters.
* `kind=flip,wholesale` counts only the purchases that ended in a flip or a double close. So the ranking becomes the
  flippers and the wholesalers of the ZIP. `investor_kind=FLIPPER` keeps the investors the registry classes as
  flippers, whatever they bought here. [Filters in plain words](/guides/concepts/filters) lists the rest.
* By default the list hides institutional investors (iBuyers, production builders, bulk landlords). They are often a
  ZIP's largest buyers by volume. `scale=individual,small,mid,large,institutional` puts them in the ranking.
  `scale=institutional` shows them alone.

## What this list is not

* **A relative window on the count.** `bought_after` is a date you compute from `data_end`, because the API does not
  know today. The one relative window you set, `active_within_months`, keeps or drops whole investors by their
  market-wide recency. The flag filters (`buys_wholesale`, `buys_at_auction`, `buys_reo`, `uses_private_lender`) count
  back from `data_end` the same way. None of them touches `in_radius.n_purchase_events`. A window computed from today
  is shorter than it looks by the data end's lag.
* **A ring count.** The counts are for the one geometry you asked for, and the API computes no concentric rings. To
  count an investor's purchases within a quarter mile and within a mile, send two requests with two radii. No field on
  a row says how the ZIP's count divides by distance.
* **Market-wide beside in-place.** `investor.n_deals`, `investor.n_purchase_events_12m` and
  `investor.days_since_last_purchase` ignore the ZIP and the window. The trailing count for this place is
  `in_radius.n_purchase_events` and nothing else on the row.
* **One ZIP on Free and Starter.** On those plans a ZIP list or a city is [`403 plan_limit`](/guides/concepts/errors#plan_limit),
  which names the limit ([Plans and limits](/guides/plans-and-limits)). From Growth a request takes up to 50 ZIPs or a
  city. A request with more than six ZIPs or a city waits behind other large queries. A ZIP no loaded market carries is
  [`422 outside_coverage`](/guides/concepts/errors#outside_coverage), which names it. The API refuses the whole list
  when one ZIP is unknown.
* **Per id, not per operator.** The counts are the id's. An operation spread over several LLCs appears once per id,
  and you fold it with `operator_id`.
* **Registered investors only.** A household, a custodian trust or an unnamed buyer never appears as a row.
  `summary.by_buyer_status` counts them, and `GET /v1/deals?zip=85257&buyer_status=unclassified,excluded,unknown`
  lists the deals behind them (`buyer_status=unclassified` alone is the households).
* **Charged per investor.** On the public tier each investor new to your account is one credit. The same investor
  again is free while the plan has credit ([Plans and limits](/guides/plans-and-limits)).

## Partners and staff

On the internal host a key with the contact scope gets the identifying fields the counts describe, on the same row.
These are `contact.skip_trace_targets[]`, `contact.person_members[]` and the mailing addresses. A skip-trace target is
a person paired with an address, and the array lists the best first. So the ranking and the reach-out come from one
call. Everything above this heading reads the same on both hosts.


## Related topics

- [List investors in an area](/api-reference/endpoints/investors-list.md)
- [Changelog](/changelog.md)
- [Rank one lender by place or by month](/api-reference/endpoints/lenders-rankings.md)
- [The twenty-six tools](/mcp/tools.md)
- [Rank buyers for a property](/api-reference/endpoints/buyers-match.md)
