Conventions
Response envelope, dates and time zone, ranges, granularity, pagination, coverage, identifiers, versioning and the example data.
Response envelope. Every success is:
{ "ok": true, "data": { … }, "meta": { … } }
meta includes brokerageId, and where relevant range, granularity, coverage, total and
nextCursor. Every error is:
{ "ok": false, "error": { "code": "invalid_range", "message": "…" }, "requestId": "req_…" }
Every response has an X-Request-Id header. Quote it when you contact support.
Dates and time zone. Timestamps are ISO 8601 in UTC (2026-09-30T02:19:03Z). Days, weeks and
months are calendar periods in America/Chicago, the clock Ruuster's weekly analytics email uses.
Ranges. from and to are dates (YYYY-MM-DD). The range includes from and excludes to, so
from=2026-09-21&to=2026-09-28 is Monday September 21 through Sunday September 27. Without them a call
covers the last 30 complete days, unless the call's own page says otherwise. A reversed or oversized
range returns 400 invalid_range.
Granularity. Series calls take granularity=day|week|month. Weeks start on Monday. Each bucket is
labelled with its first day ("bucket": "2026-09-21"). A bucket cut off by your range is marked
"partial": true.
Which date a list filters on. List calls take dateField to choose what from/to apply to:
| Call | dateField values (default first) |
|---|---|
/properties |
updated, onMarket, closed |
/leads |
created, active, updated |
Pagination. Lists return up to limit rows and meta.nextCursor. Pass the cursor back as
?cursor= for the next page; null means you're done. Cursors stay valid for at least 24 hours and
work for any owner or admin key in your brokerage. There are no page numbers.
Coverage. meta.coverage says which days are available for your brokerage:
from/to for Ruuster's own activity data, ga for Google Analytics data, ownershipAsOf for the
last listing-attribution refresh, and stableBefore, the day before which numbers no longer change.
Buckets outside coverage return null metrics, never 0. Totals are computed over covered buckets,
and bucketsRequested / bucketsCovered show how many that was.
Comparing periods. Planned: compare=previous will add a previous object with the same shape
for the range immediately before yours. Until then, request two periods and compare the buckets (see
Guides).
Incremental sync. Planned: bulk calls will accept changedSince=<timestamp> and return only
records whose numbers changed since then. Until then, re-pull the last few days on each sync.
Caching. Planned: responses for completed periods will carry an ETag. A matching
If-None-Match will return 304 Not Modified with no body, which uses none of your daily row quota.
Methods. Every data call is GET. Other methods return 405 method_not_allowed with an Allow
header. Webhook management uses POST, PATCH and DELETE as documented.
Identifiers
| Id | Format | Notes |
|---|---|---|
propertyId |
uuid | The id in …/listings/<propertyId> on your site. Stable for the life of the listing on Ruuster |
mlsRecordId |
MLS number as the MLS publishes it | See MLS numbers |
leadId |
user:<uuid> or unregistered:<uuid> |
user has a Ruuster account; unregistered is a contact synced from your CRM. Use the whole value and treat it as opaque |
crmExternalId |
your CRM's id | For example the Follow Up Boss person id. Your join key to CRM exports |
emailId |
em_… |
One Ruuster email to one recipient. Treat it as opaque |
id on events |
evt_… |
Same in the API and in webhooks |
Versioning. Breaking changes ship as /v2; /v1 keeps working. Removing a field, changing what
null means, or changing bucket boundaries counts as breaking. Adding fields, optional parameters,
calls or event kinds doesn't; ignore fields you don't use. Ruuster announces deprecations at least 90
days ahead in the changelog and with Deprecation and
Sunset headers.
About the examples
Fixture. Every example in these docs uses one fictional brokerage, "Saguaro Realty Group"
(saguaro.ruuster.com), observed on Thursday 2026-10-01. All people, addresses, ids and numbers are
fictional. Weeks are Monday–Sunday America/Chicago (2026-09-21 is a Monday).
| Thing | Id |
|---|---|
| Brokerage | 4f6c2a9e-8d31-4b57-9a0e-2c7d5e1b8f43 |
| Jane Doe (admin, the key holder in every example) | user 7c1e9b42-3d85-4a6f-9e07-1b2c8d4f5a69 |
Marco Ruiz (agent; in the directory and as a lead's assigned agentId) |
user e5a7d3c1-9b46-4f28-8c0e-6d2a1f7b3e95 |
| Listing A: 4182 E Saguaro Bloom Dr, Tucson AZ 85718 | 5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86, MLS 22418765 |
| Listing B: 1920 W Mesquite Ridge Ln, Oro Valley AZ 85737 | a03c8f61-7e2b-4d95-8a14-6f0b3e9c2d57, MLS 22419302 |
| Lead Sam Lee (registered) | user:c0ff3e21-8a4b-4c7d-9e15-2b6f8d3a1c04 |
| Lead Priya Shah (CRM-only) | unregistered:8d2b6f19-4e37-4a85-b0c9-7f1e3d5a2b68 |
| MLS (MLS of Southern Arizona) | 3f4a7b12-6c85-4d93-a1e0-8b2f5c9d7e31 |
The recipes in Guides use this shell setup:
B=https://api.ruuster.com/v1
H=(-H "x-api-key: $RUUSTER_API_KEY" -H "x-email: jane@saguarorealty.com")
Response headers on every authenticated call (shown once):
HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: no-store
x-request-id: req_01J9ZB4M8Q2C7V5T3K8P6W1N0X
x-ratelimit-limit: 120
x-ratelimit-remaining: 117
x-ratelimit-reset: 1790863260
x-daily-rows-limit: 250000
x-daily-rows-remaining: 248310
Lead email/phone appear in these examples because the fixture brokerage has lead contact
details enabled. With them disabled, those two fields are omitted from every lead object.
Property objects also carry mlsName and mlsRecordIdCore; they're left out of the examples for length.