Docs
Sites API

Conventions: envelope, filters, errors, limits

The rules every Sites API endpoint follows, so you learn them once.

The envelope

Every success is { "data": …, "meta": … }. Keys are snake_case. data is an object for one record and an array for a list.

{
  "data": [ { "slug": "101", "…": "…" } ],
  "meta": {
    "pagination": { "total": 1, "page": 1, "per_page": 50, "last_page": 1 },
    "cache": { "tags": ["inventory:01000000-0000-4000-8000-000000000002"], "ttl": 300 }
  }
}

Three promises about shape:

  • Every documented key is always present. A value you may not see is null, never missing. A hidden price is null; a section you did not include is null.
  • Empty maps are {} and empty lists are []. They are never swapped.
  • Dates are ISO 8601 with an offset (2026-01-15T12:00:00+00:00); date-only values are YYYY-MM-DD. Money is a plain number (1500), not a formatted string.

Pagination

List endpoints take page (from 1) and per_page. Defaults and ceilings differ per endpoint and are listed in the endpoint reference: most lists default to 50 with a ceiling of 200; /paths and /availability default to 500 with a ceiling of 2000, because a build reads them whole.

meta.pagination always carries total, page, per_page and last_page. Read until page == last_page.

Filtering

Filters are plain query parameters, documented per endpoint. Four forms:

FormExampleMeaning
Exactcity=AustinCase-insensitive match
Any ofbeds=1,2Comma-separated; matches any
Booleanavailable=truetrue, false, 1, 0; anything else is a 422
Rangesqft[gte]=700&sqft[lte]=1000Operators gte, lte, gt, lt

property={slug} narrows a website-wide list to one property. An unknown property slug is a 404, not an empty list, so a typo cannot pass for "no results". group={id} takes a group id from GET /groups and cascades to the group's descendants.

A filter that cannot be applied is a 422, never silently ignored. beds=abc or include=nonsense fails loudly:

{ "message": "Must be a number.", "errors": { "beds": ["Must be a number."] } }

Sorting

sort takes one or more keys, comma-separated, with a - prefix for descending: sort=-rent,number. Each endpoint allows only the keys it lists; anything else is a 422. When paging a sorted list, add a second unique key such as number or name so ties land in the same order on every page.

Includes

include embeds optional sections (GET /properties/{property}, GET /floor-plans). An unrequested section's key is still present, as null. meta.includes echoes what was applied.

Errors

StatusMeaningBody
401Missing or revoked token{message}
403Token is for another website{message}
404Not found, or outside this website{message} — /resolve and /entries add type: "not_found"
409An Idempotency-Key still being processed, or a tour slot already taken{message}
422Validation failed{message, errors} — errors keyed by parameter
429Rate limited{message}, with Retry-After
502A tour provider failed; safe to retry{message}

Rate limits

Limits are per website token, not per IP, because a build server shares its address with many sites.

LimiterLimitApplies to
Read1,200 / minuteEvery GET, plus POST /events and POST /source-observations
Write60 / minutePOST /forms/{form}/submissions, POST /tours/*, and GET /units/{unit}/price-matrix

Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; a 429 carries Retry-After.

Idempotency

POST /forms/{form}/submissions and POST /tours/reservations accept an Idempotency-Key header: any unique string, up to 255 characters, one per submission. A repeat within 24 hours returns the original result with 200 and meta.duplicate: true, and creates nothing. A repeat while the first is still running is a 409. Always send one: it is what makes a retry after a timeout safe.

Compatibility

Additive changes ship continuously: new endpoints, new optional parameters, new keys. Build clients to ignore keys they do not know. Removing or retyping a key is a breaking change and is gated in CI by an OpenAPI diff.

Last updated on

On this page