# Find prospects for a listing

Source: https://help.ruuster.com/api/properties-prospects

```http
GET https://api.ruuster.com/v1/properties/{propertyId}/prospects
```

Reverse prospecting in one call. Your brokerage's leads whose saved searches this listing fits, by area (zip, city, or a county, school or neighborhood that contains it) and by price within a tolerance, with the matching searches. Use it to pick who to email about a new listing, a price change or an open house.

- Authentication: `x-api-key` and `x-email` headers of an owner or admin
- Rate limit: *lookup* call, 20 per minute per key, within the 120 per minute
- Rows count against the daily lead-row quota (50,000 by default)

Answers "who should hear about this home?" from what your leads told Ruuster they want. A lead is a prospect when one of their saved searches covers the listing's **area** and its **price**:

- **Area**: the search names the listing's zip code, or its city, or a county, school zone, neighborhood or drawn area whose boundary contains the listing.
- **Price**: the search's price range overlaps the listing price widened by `priceTolerance` percent each way. A search with no maximum is open-ended; one with no minimum starts at 0. With `bounded=1`, only searches with a maximum price count (a budget rather than "any price").

Brokerage-wide default searches (the fallback search every contact gets) are skipped: they say nothing about what a person wants. Rental searches are skipped too. Other criteria on the search (property types, beds, square feet, year built…) are returned in `criteria` but not applied, so you can judge fit, for example leaving out a condo-only search for a house.

Any listing works by `propertyId`, yours or not; your own listings also by `mls:<number>`. The leads are always your brokerage's. To see which of your leads have already looked at the listing, use [List a listing's viewers](https://help.ruuster.com/api/properties-viewers) with `scope=any`. This is a lookup call: it searches saved searches live, except that a search a lead saved in the last 15 minutes may not be included yet.

## Path parameters

| Name | Type | Description |
|---|---|---|
| `propertyId` | string | The listing's `propertyId` (uuid), or `mls:<number>` to find it by MLS number. See [MLS numbers](https://help.ruuster.com/api/mls-numbers) |

## Query parameters

| Name | Type | Default | Description |
|---|---|---|---|
| `priceTolerance` | integer | `5` | Percent the listing price is widened each way before comparing with a search's range, 0 to 25 |
| `bounded` | `0` \| `1` | `0` | `1` counts only searches with a maximum price |
| `limit` | integer | `100` | Prospects returned, 1 to 200, best matches first |
| `countOnly` | `0` \| `1` | `0` | `1` returns `{ "total": n, "priceBounded": n }` and no rows |
| `agent` | string | your default agent | Whose page the listing links (`url`) open under: an `agentId`, email or landing-page slug from [List agents](https://help.ruuster.com/api/agents-list). Whoever signs up from such a link becomes that agent's lead. Without it, links credit your brokerage's default agent (where unassigned leads go). `meta.agent` names the agent credited |

## Response

`data.property` identifies the listing: `propertyId`, `mlsRecordId`, `mlsName`, `address`, `city`, `state`, `postalCode`, `price`, `status`, `url` (on your Ruuster site) and `ownedByYou`. `data.total` is the number of prospects found; `meta.truncated` is true when it is more than `limit`.

`data.prospects` has one row per lead, leads whose fitting search has a maximum price first, then the tightest price range:

| Field | Description |
|---|---|
| `lead` | The short lead form: `leadId`, `firstName`, `lastName`, `email` and `phone` when lead contact details are on, `crmExternalId`, `crmStatus`, `crmTags`, `agentId`, `createdAt`, `lastLoginAt` |
| `priceBounded` | Whether at least one fitting search has a maximum price |
| `searches` | The lead's fitting saved searches: `searchId`, `name`, `matchedOn` (`zip`, `city`, or `area:<kind>` such as `area:county` or `area:school`), `priceMin`, `priceMax` (`null` when not set), `criteria` (the search's other filters as saved) and `updatedAt` |

`meta.priceRange` is the price window used (`null` when the listing has no price, in which case price isn't checked), `meta.priceTolerance` and `meta.bounded` echo the parameters, and `meta.searchesConsidered` counts your leads' searches whose area covers the listing before the price check. `meta.areaShapes` says whether searches drawn as shapes (county, school district, neighborhood or a drawn area) were checked: `checked`, `no_location` (the listing has no map point, so only zip and city areas apply), or `timed_out` (the shape check ran out of time; the prospects shown are the zip and city matches, so try again for the full list). An unknown `propertyId` returns `404 not_found`. Rows count against your daily lead-row quota.

## Example request

cURL:

```bash
curl -s "https://api.ruuster.com/v1/properties/5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86/prospects?priceTolerance=5&limit=2" \
  -H "x-api-key: $RUUSTER_API_KEY" \
  -H "x-email: $RUUSTER_EMAIL"
```

Node.js:

```javascript
const res = await fetch("https://api.ruuster.com/v1/properties/5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86/prospects?priceTolerance=5&limit=2", {
  headers: {
    "x-api-key": process.env.RUUSTER_API_KEY,
    "x-email": process.env.RUUSTER_EMAIL,
  },
});
const body = await res.json();
if (!body.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.requestId})`);
console.log(body.data);
```

Python:

```python
import os
import requests

res = requests.get(
    "https://api.ruuster.com/v1/properties/5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86/prospects?priceTolerance=5&limit=2",
    headers={"x-api-key": os.environ["RUUSTER_API_KEY"], "x-email": os.environ["RUUSTER_EMAIL"]},
    timeout=30,
)
body = res.json()
if not body["ok"]:
    raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]} ({body.get("requestId")})')
print(body["data"])
```

## Example response

```json
{
  "ok": true,
  "data": {
    "propertyId": "5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86",
    "property": {
      "propertyId": "5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86",
      "mlsRecordId": "22418765",
      "mlsName": "MLS of Southern Arizona",
      "address": "4182 E Saguaro Bloom Dr, Tucson, AZ 85718",
      "ownedByYou": true,
      "city": "Tucson", "state": "AZ", "postalCode": "85718", "price": 875000, "status": "Active",
      "url": "https://saguaro.ruuster.com/listings/5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86"
    },
    "total": 14,
    "prospects": [
      {
        "lead": { "leadId": "user:c0ff3e21-8a4b-4c7d-9e15-2b6f8d3a1c04", "firstName": "Sam", "lastName": "Lee",
                  "email": "sam.lee@example.com", "phone": "+15205550142", "crmExternalId": "48213",
                  "crmStatus": "Showing Homes Nurture", "crmTags": ["buyer", "tucson"],
                  "agentId": "7c1e9b42-3d85-4a6f-9e07-1b2c8d4f5a69",
                  "createdAt": "2026-09-21T18:20:05Z", "lastLoginAt": "2026-09-30T02:11:40Z" },
        "priceBounded": true,
        "searches": [
          { "searchId": "3e73962d-b3a1-4a51-ac1d-4d25f1f17c0f", "name": "Catalina Foothills, 4+ beds",
            "matchedOn": "zip", "priceMin": 800000, "priceMax": 950000,
            "criteria": { "type": ["house"], "status": ["Active", "ComingSoon"], "bedsMin": "4" },
            "updatedAt": "2026-09-18T15:02:44Z" }
        ]
      },
      {
        "lead": { "leadId": "unregistered:8d2b6f19-4e37-4a85-b0c9-7f1e3d5a2b68", "firstName": "Priya", "lastName": "Shah",
                  "email": "priya.shah@example.com", "phone": "+15205550187", "crmExternalId": "51907",
                  "crmStatus": null, "crmTags": ["buyer"],
                  "agentId": "e5a7d3c1-9b46-4f28-8c0e-6d2a1f7b3e95",
                  "createdAt": "2026-09-23T15:44:12Z", "lastLoginAt": null },
        "priceBounded": true,
        "searches": [
          { "searchId": "0bda2c4f-00c5-482a-8f2b-ab3c71b137d3", "name": "Pima County",
            "matchedOn": "area:county", "priceMin": 600000, "priceMax": 1200000,
            "criteria": {}, "updatedAt": "2026-08-30T21:40:10Z" }
        ]
      }
    ]
  },
  "meta": {
    "priceTolerance": 5,
    "priceRange": { "from": 831250, "to": 918750 },
    "bounded": false,
    "searchesConsidered": 61,
    "areaShapes": "checked",
    "nextCursor": null,
    "truncated": true
  }
}
```
