# Find engaged leads your CRM hasn't called

Source: https://help.ruuster.com/api/leads-follow-up

```http
GET https://api.ruuster.com/v1/leads/follow-up
```

Leads who viewed listings (or engaged more) on your Ruuster sites in a period, whom your CRM shows no call, or no contact of any kind, for N days. Ruuster activity joined with your own CRM, live, in one call. Follow Up Boss.

- 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
- Your CRM: `x-crm-vendor` and `x-crm-key` headers with your CRM's API key, used for this request only. See [Connect your CRM](https://help.ruuster.com/api/crm)
- Rows count against the daily lead-row quota (50,000 by default)

The follow-up list: "which leads have been looking at homes on our site but haven't been called in the last month?" Ruuster takes every lead with activity in the range, keeps those who meet the `engagement` level, looks each one up in your CRM by the CRM id Ruuster's CRM sync stored on the lead (`crmExternalId`), and returns the ones whose last logged call (or, with `contact=any`, last call, text or email) is older than `quietDays`, or who were never contacted. Most engaged first.

This call reads your CRM live with your CRM's API key, sent in the `x-crm-vendor` and `x-crm-key` headers. The key is used for this request only and never stored. Your CRM answers with its own permissions: an agent's key may see only that agent's people. See [Connect your CRM](https://help.ruuster.com/api/crm).

Follow Up Boss only for now: Sierra keeps no calls, and Sure Send can't look many people up at once. Other CRMs answer `400 crm_not_supported`.

## Query parameters

| Name | Type | Default | Description |
|---|---|---|---|
| `from` | date | 30 days before `to` | First day of Ruuster activity, `YYYY-MM-DD` in America/Chicago, included. At most 93 days |
| `to` | date | tomorrow | Day after the last day, `YYYY-MM-DD`, excluded. The default includes today |
| `engagement` | `any` \| `viewed` \| `meaningful` \| `strong` | `viewed` | Who counts as engaged on Ruuster: `viewed` viewed at least one listing; `meaningful` 3 or more listing views, or any favorite, tour request, contact-agent click or share; `strong` a tour request or contact-agent click, 10 or more views, or 2 or more favorites; `any` any recorded activity |
| `contact` | `call` \| `any` | `call` | What counts as contact in your CRM: `call` a logged call; `any` a logged call, text or email |
| `quietDays` | integer | `30` | No contact of that kind in this many days, 1 to 365. Leads never contacted are always included |
| `stage` | string | | Only people in this CRM stage (comma-separate several), e.g. `Lead,Nurture` |
| `pond` | string | | Only people in these CRM ponds: a pond id, or a name to match (comma-separate several), e.g. `inactive` matches every pond whose name contains it |
| `assignedUserId` | string | | Only people assigned to this CRM user (an id from [Get your CRM's vocabulary](https://help.ruuster.com/api/crm-overview)) |
| `limit` | integer | `50` | Most leads returned, 1 to 500 |
| `countOnly` | `0` \| `1` | `0` | `1` returns `{ "total": n, "summary": … }` and no rows |

## Response

`data.leads` is ordered most engaged first (tour and contact requests, then favorites and shares, then views). Each row:

| Field | Description |
|---|---|
| `lead` | The short lead form, as on [List active leads](https://help.ruuster.com/api/leads-active) |
| `ruuster.level` | The highest engagement level the lead reached in the range |
| `ruuster.totals` | Their Ruuster activity in the range: `actions`, `propertyViews`, `favorites`, `searches`, `tourRequests`, `contactAgentClicks`, `shares`, `logins`, `savedSearchEvents` |
| `ruuster.lastActivityAt` | Their latest recorded activity in the range |
| `crm.personId`, `name`, `stage`, `pond`, `assignedTo`, `assignedUserId`, `tags` | The person in your CRM |
| `crm.lastCallAt`, `daysSinceCall` | The latest logged call, or null if none |
| `crm.lastContactAt`, `lastContactKind` | The latest contact of the kind `contact` asked about (`call`, `text`, `email` or `communication`) |

`data.summary` counts the funnel: `engagedOnRuuster` (leads at the level), `linkedToCrm` (CRM people behind them: leads whose `crmExternalId` is a CRM person id, one per person), `notLinkedToCrm` (engaged leads with no CRM id), `checkedInCrm`, `foundInCrm`, `notFoundInCrm` (ids this key doesn't return: deleted, in the trash, merged, or not visible to this key), `matchingFilters` (with `stage`, `pond` or `assignedUserId`), `notContacted` and `neverContacted`. `data.criteria` restates the rules in words. `meta.crm` names the CRM, says how many CRM calls were made and carries any caution, such as a key that found few of your leads. Up to the 2,000 most engaged leads linked to your CRM are checked per call.

## Example request

cURL:

```bash
curl -s "https://api.ruuster.com/v1/leads/follow-up?engagement=viewed&quietDays=30&limit=2" \
  -H "x-api-key: $RUUSTER_API_KEY" \
  -H "x-email: $RUUSTER_EMAIL" \
  -H "x-crm-vendor: fub" \
  -H "x-crm-key: $CRM_API_KEY"
```

Node.js:

```javascript
const res = await fetch("https://api.ruuster.com/v1/leads/follow-up?engagement=viewed&quietDays=30&limit=2", {
  headers: {
    "x-api-key": process.env.RUUSTER_API_KEY,
    "x-email": process.env.RUUSTER_EMAIL,
    "x-crm-vendor": "fub",
    "x-crm-key": process.env.CRM_API_KEY,
  },
});
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/leads/follow-up?engagement=viewed&quietDays=30&limit=2",
    headers={"x-api-key": os.environ["RUUSTER_API_KEY"], "x-email": os.environ["RUUSTER_EMAIL"], "x-crm-vendor": "fub", "x-crm-key": os.environ["CRM_API_KEY"]},
    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": {
    "leads": [
      {
        "lead": { "leadId": "user:c0ff3e21-8a4b-4c7d-9e15-2b6f8d3a1c04", "firstName": "Sam", "lastName": "Lee",
                  "email": "sam.lee@example.com", "phone": "+15205550142", "crmExternalId": "48213",
                  "crmStatus": "Nurture", "crmTags": ["buyer"], "agentId": "7c1e9b42-3d85-4a6f-9e07-1b2c8d4f5a69",
                  "createdAt": "2026-05-21T18:20:05Z", "lastLoginAt": "2026-10-20T02:11:40Z" },
        "ruuster": { "level": "strong",
                     "totals": { "actions": 64, "propertyViews": 31, "favorites": 4, "searches": 12, "tourRequests": 1,
                                 "contactAgentClicks": 0, "shares": 1, "logins": 5, "savedSearchEvents": 0 },
                     "lastActivityAt": "2026-10-20T23:58:44Z" },
        "crm": { "personId": "48213", "name": "Sam Lee", "stage": "Nurture", "pond": "Inactive Buyers",
                 "assignedTo": null, "assignedUserId": null, "tags": ["buyer"],
                 "lastCallAt": "2026-07-02T16:10:00.000Z", "daysSinceCall": 110,
                 "lastContactAt": "2026-07-02T16:10:00.000Z", "lastContactKind": "call" }
      },
      {
        "lead": { "leadId": "unregistered:4a9e2c71-0b58-4d3f-8e26-5c1d7b9f3a40", "firstName": "Dana", "lastName": "Okafor",
                  "email": "dana.okafor@example.com", "phone": "+15205550166", "crmExternalId": "50322",
                  "crmStatus": "Lead", "crmTags": [], "agentId": "e5a7d3c1-9b46-4f28-8c0e-6d2a1f7b3e95",
                  "createdAt": "2026-08-30T15:12:41Z", "lastLoginAt": null },
        "ruuster": { "level": "meaningful",
                     "totals": { "actions": 9, "propertyViews": 6, "favorites": 0, "searches": 2, "tourRequests": 0,
                                 "contactAgentClicks": 0, "shares": 0, "logins": 0, "savedSearchEvents": 0 },
                     "lastActivityAt": "2026-10-19T14:05:51Z" },
        "crm": { "personId": "50322", "name": "Dana Okafor", "stage": "Lead", "pond": null,
                 "assignedTo": "Jordan Price", "assignedUserId": "12", "tags": [],
                 "lastCallAt": null, "daysSinceCall": null, "lastContactAt": null, "lastContactKind": null }
      }
    ],
    "summary": { "engagedOnRuuster": 812, "linkedToCrm": 640, "notLinkedToCrm": 172, "checkedInCrm": 640,
                 "foundInCrm": 598, "notFoundInCrm": 42, "notContacted": 233, "neverContacted": 120 },
    "criteria": { "engagement": "viewed", "engagementRule": "viewed at least one listing on your Ruuster sites",
                  "contact": "call", "contactRule": "no call logged in Follow Up Boss in the last 30 days", "quietDays": 30 }
  },
  "meta": {
    "range": { "from": "2026-09-22", "to": "2026-10-22", "tz": "America/Chicago" },
    "coverage": { "from": "2026-07-03", "to": "2026-10-22", "stableBefore": "2026-10-20" },
    "crm": { "vendor": "fub", "label": "Follow Up Boss", "source": "Follow Up Boss, live, with your API key", "calls": 7,
             "note": "notFoundInCrm are leads whose CRM id this key doesn't return: deleted, in the trash, merged, or not visible to this key." }
  }
}
```
