# Get listing engagement

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

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

How people engaged with one listing across Ruuster sites, by day, week or month: views, visitors, favorites, tour requests, offers, shares, contact requests and push alerts. The seller-report call. With scope=any, your own leads' activity on any one listing.

- Authentication: `x-api-key` and `x-email` headers of an owner or admin
- Rate limit: 120 requests per minute per key

## 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 |
|---|---|---|---|
| `from` | date | 30 days before `to` | First day, `YYYY-MM-DD` in America/Chicago, included |
| `to` | date | today | Day after the last day, `YYYY-MM-DD`, excluded |
| `granularity` | string | `day` | `day`, `week` (Monday start) or `month` |
| `scope` | string | `brokerage` | `brokerage`: one of your listings, with every figure below. `any`: any single listing, by `propertyId`, with only your own leads' figures (see below) |

## Response

`data.series` has one entry per bucket (`bucket` is the bucket's first day, `partial` marks a bucket cut off by your range) and `data.totals` covers the whole range. Each contains:

| Field | Description |
|---|---|
| `views.total` | `public.pageViews` + `loggedIn.views`. Ruuster's best combined figure for views, not an exact count |
| `views.public.pageViews` | Views by visitors who weren't logged in, as Google Analytics measures them on this listing's pages across Ruuster sites. Reloads count |
| `views.public.users` | Distinct public visitors as Google Analytics counts them (roughly, distinct browsers). The closest available figure for "how many people" |
| `views.public.sessions` | Visits that included this listing |
| `views.public.fromMySites`, `fromOtherRuusterSites` | Public views on your hostnames vs other brokerages' Ruuster sites |
| `views.loggedIn.views` | Views by signed-in Ruuster users, from Ruuster's own records. Reloads count |
| `views.loggedIn.lead`, `agent` | Of those, by leads and by agents (yours or another brokerage's) |
| `views.loggedIn.uniqueLeads` | Distinct leads who viewed |
| `views.loggedIn.fromMySites`, `fromOtherRuusterSites` | Logged-in views by site |
| `views.gaSignedIn` | Google Analytics' own count of signed-in views and users. A cross-check on `loggedIn`, not added into `total` |
| `favorites` | Times the listing was saved. A tour request also saves the listing |
| `tourRequests` | Tour requests for the listing |
| `offers` | Offers submitted through Ruuster |
| `shares.sent`, `shares.opens` | Share links for this listing created on your Ruuster sites, and opens of those links |
| `contactAgentClicks` | Contact-agent requests sent from the listing page |
| `pushAlertsSent` | Mobile push alerts that linked directly to this listing. Most alerts link to a saved search instead, so this is usually small |

Visitor and lead counts (`users`, `uniqueLeads`) are distinct per day. In `week` and `month` buckets they are the sum of each day's count, so a person who came back on several days is counted on each of those days. They're `null` in `totals`, because people aren't additive across buckets.

Public views come from Google Analytics and logged-in views from Ruuster's own records, and `total` adds the two. The two sources measure differently, so a small number of visits can be counted in the wrong group. Google Analytics doesn't see visitors who block analytics, so treat public numbers as a floor. Buckets outside `meta.coverage` return `null`, never `0`.

To see which of your leads are behind the logged-in views, use [List a listing's viewers](https://help.ruuster.com/api/properties-viewers).

## Any listing: `scope=any`

With `scope=any` the listing can be one that isn't yours, for example a home one of your buyers keeps coming back to, identified by `propertyId` (resolve an MLS number with [Look up any listing by MLS number](https://help.ruuster.com/api/properties-lookup) first). The series then describes **your own leads only**: `views.loggedIn.lead` (their views, reloads count), `views.loggedIn.uniqueLeads` (how many of them), `favorites` (your leads who saved it) and `tourRequests` (your leads who requested a tour), per bucket and in `totals`, where the counts of leads are true distinct counts over the range. Everything that describes other people's activity is `null`: `views.total`, `views.public`, `views.gaSignedIn`, `views.loggedIn.views`, `agent`, the site split, `offers`, `shares`, `contactAgentClicks` and `pushAlertsSent`. The shape of the response is the same, so one parser serves both scopes, and `meta.scope` says which you got. It's always one listing per call; the ranked all-listings call covers your inventory only.

Not available per listing: how many Ruuster emails included the listing, and how many saved searches match it.

## Example request

cURL:

```bash
curl -s "https://api.ruuster.com/v1/properties/5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86/engagement?from=2026-09-14&to=2026-09-28&granularity=week" \
  -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/engagement?from=2026-09-14&to=2026-09-28&granularity=week", {
  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/engagement?from=2026-09-14&to=2026-09-28&granularity=week",
    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",
    "series": [
      {
        "bucket": "2026-09-14", "partial": false,
        "views": {
          "total": 3412,
          "public":   { "pageViews": 3346, "users": 1544, "sessions": 1569, "fromMySites": 3346, "fromOtherRuusterSites": 0 },
          "loggedIn": { "views": 66, "lead": 58, "agent": 8, "uniqueLeads": 21, "fromMySites": 63, "fromOtherRuusterSites": 3 },
          "gaSignedIn": { "pageViews": 52, "users": 19 }
        },
        "favorites": 4, "tourRequests": 1, "offers": 0,
        "shares": { "sent": 3, "opens": 9 },
        "contactAgentClicks": 2, "pushAlertsSent": 31
      },
      {
        "bucket": "2026-09-21", "partial": false,
        "views": {
          "total": 3849,
          "public":   { "pageViews": 3776, "users": 1770, "sessions": 1785, "fromMySites": 3776, "fromOtherRuusterSites": 0 },
          "loggedIn": { "views": 73, "lead": 64, "agent": 9, "uniqueLeads": 26, "fromMySites": 70, "fromOtherRuusterSites": 3 },
          "gaSignedIn": { "pageViews": 61, "users": 23 }
        },
        "favorites": 6, "tourRequests": 2, "offers": 0,
        "shares": { "sent": 4, "opens": 11 },
        "contactAgentClicks": 3, "pushAlertsSent": 38
      }
    ],
    "totals": {
      "views": {
        "total": 7261,
        "public":   { "pageViews": 7122, "users": null, "sessions": 3354, "fromMySites": 7122, "fromOtherRuusterSites": 0 },
        "loggedIn": { "views": 139, "lead": 122, "agent": 17, "uniqueLeads": null, "fromMySites": 133, "fromOtherRuusterSites": 6 },
        "gaSignedIn": { "pageViews": 113, "users": null }
      },
      "favorites": 10, "tourRequests": 3, "offers": 0,
      "shares": { "sent": 7, "opens": 20 },
      "contactAgentClicks": 5, "pushAlertsSent": 69
    }
  },
  "meta": {
    "brokerageId": "4f6c2a9e-8d31-4b57-9a0e-2c7d5e1b8f43",
    "range": { "from": "2026-09-14", "to": "2026-09-28", "tz": "America/Chicago" },
    "granularity": "week",
    "coverage": { "from": "2026-07-03", "to": "2026-09-30", "stableBefore": "2026-09-28",
                  "ga": { "from": "2023-06-16", "to": "2026-09-30" },
                  "bucketsRequested": 2, "bucketsCovered": 2 },
    "nextCursor": null
  }
}
```
