Inventory & pricing
Floor plans, units, the availability feed, price matrices, unit types and search facets — and how pricing display settings shape every one of them.
Inventory endpoints run across the website's properties. Add property={slug} to narrow to one. All are tagged inventory:{property_id} for each property on the page, with a TTL of 300 (60 for /availability).
Floor plans
GET /floor-plans · GET /floor-plans/{floorPlan}
Default order: order, then name.
| Parameter | |
|---|---|
property | A property slug. Unknown is a 404, not an empty list |
group | A group id; includes descendants |
q | Name contains |
beds, baths | Counts, comma-separated |
sqft[gte] … | Range: gte, lte, gt, lt |
min_rent, max_rent | Starting rent at or above / at or below |
featured | Boolean |
available | Has (true) or lacks (false) an available unit |
available_by | YYYY-MM-DD; has a unit available on or before this date |
include | units to embed each plan's units |
sort | order, name, beds, rent, sqft |
page, per_page | Default 50, ceiling 200 |
{
"id": "01000000-0000-4000-8000-000000000005",
"slug": "the-contract",
"path": "/floor-plan/the-contract",
"name": "The Contract",
"marketing_name": null,
"code": "A1",
"description": "One bedroom.",
"unit_type": { "slug": "1-bedroom", "name": "1 Bedroom" },
"bedrooms": 1,
"bathrooms": 1,
"bedrooms_max": 1,
"bathrooms_max": 1,
"sqft": { "min": 700, "max": 750 },
"rent": { "min": 1500, "max": 1800, "base_min": 1500, "base_max": 1800 },
"fees": { "lines": [], "monthly_total": 0, "monthly_total_max": 0, "upfront_total": 0 },
"deposit": { "min": null, "max": null },
"available_on": "2026-02-01",
"availability": { "available_now": 0, "coming_soon": 1 },
"has_specials": false,
"is_featured": false,
"sort_order": 0,
"tags": [],
"image_2d": null, "image_3d": null, "pdf_url": null, "pdfs": [],
"images": [], "videos": [], "virtual_tours": [],
"buildings": [{ "id": "0100…0004", "number": "1", "code": null, "name": "North Building" }],
"groups": [],
"inherited_groups": [],
"property": { "id": "0100…0002", "slug": "contract-property", "name": "Contract Property" },
"units": null
}Grouped floor plans. When Resi groups several floor plans as one, only the primary is listed, and it speaks for the whole group: its availability, rent bounds and embedded units cover every sibling. A sibling's own slug is a 404.
units is null unless include=units; meta.includes echoes what was embedded. At most 100 units per plan are embedded, by unit number; a plan with more is paged through GET /units?floor_plan={slug}.
Units
GET /units · GET /units/{unit}
Enabled, unhidden units, ordered by unit number unless sorted. A unit Resi marks hidden is never served, by list or by slug.
| Parameter | |
|---|---|
property, group | As above |
q | Unit number contains |
floor_plan | A floor plan slug. A primary matches its whole group's units |
building | Building ids, comma-separated |
amenity | Amenity ids, comma-separated. A unit must have all of them |
beds, baths | Comma-separated. Falls back to the floor plan's counts when the unit holds none |
sqft[gte] … | Range |
min_rent, max_rent | |
featured, available | Boolean |
available_by | YYYY-MM-DD |
sort | number, rent, sqft, beds, available |
page, per_page | Default 50, ceiling 200 |
{
"id": "01000000-0000-4000-8000-00000000000a",
"slug": "101",
"path": "/unit/101",
"number": "101",
"floor": "1",
"bedrooms": 1,
"bathrooms": 1,
"sqft": { "interior": 720, "exterior": null },
"description": null,
"view_direction": null,
"is_available": true,
"available_on": "2026-02-01",
"rent": { "min": 1500, "max": 1500, "base_min": 1500, "base_max": 1500 },
"fees": { "lines": [], "monthly_total": 0, "monthly_total_max": 0, "upfront_total": 0 },
"deposit": null,
"specials": null,
"flags": { "featured": false, "accessible": false, "furnished": false, "affordable": false, "model": false, "guest_suite": false, "penthouse": false },
"links": { "tour": null, "quote": null, "application": null },
"sightmap_unit_id": "258702",
"tags": [], "pdf_url": null, "pdfs": [],
"images": [], "videos": [], "virtual_tours": [], "amenities": [],
"building": { "id": "0100…0004", "number": "1", "code": null, "name": "North Building" },
"floor_plan": { "id": "0100…0005", "slug": "the-contract", "name": "The Contract", "path": "/floor-plan/the-contract", "unit_type": { "slug": "1-bedroom", "name": "1 Bedroom" }, "image_2d": null, "image_3d": null },
"groups": [],
"inherited_groups": [
{ "id": "0100…0018", "label": "North Tower", "slug": "north-tower", "via": "building", "set": { "id": "0100…0017", "key": "towers", "name": "Towers", "object_type": "building" } }
],
"property": { "id": "0100…0002", "slug": "contract-property", "name": "Contract Property" }
}Groups: direct and inherited
groups lists the groups the unit itself was put in. inherited_groups lists the groups it belongs to through what contains it, each with via — property, building or floor_plan. The group filter matches on both, so every unit a group filter returns carries that group in one list or the other. Floor plans carry inherited_groups too, from their property.
pdfs[] is every PDF attached (id, url, title); pdf_url is the first one's URL. fees.upfront_total is the one-time mandatory fees due up front, hidden with the other fee totals. On a floor plan, bedrooms_max and bathrooms_max equal bedrooms and bathrooms unless the plan spans a range, and marketing_name is the name the management system markets the plan under, when it has one.
sightmap_unit_id is the unit's id in SightMap, for deep-linking the SightMap embed to a unit. It is null when Resi holds no mapping.
Pricing, and what is hidden
Every rent block has the same four keys:
| Key | Means |
|---|---|
min, max | The total monthly price: base rent plus mandatory monthly fees |
base_min, base_max | Base rent alone |
rent.min always equals base_min + fees.monthly_total. Values are stored when pricing is synced, not computed per request, so the identity holds in every payload.
Each property chooses how its prices may be shown, reported as pricing_display.mode on the property card:
| Mode | min / max | base_min / base_max |
|---|---|---|
BASE_AND_TOTAL | shown | shown |
TOTAL_ONLY | shown | null |
BASE_ONLY | null | shown |
A property can also hide all pricing, or hide it when nothing is available. In every case:
- a hidden value is
null, never absent. Render a label frompricing_displayin its place:hidden_price_labelwhen all pricing is hidden,no_availability_price_labelwhen the price is hidden because nothing is available, andno_price_labelfor any othernullprice, such as one the PMS never sent; fees.linesis[]when the fee breakdown is off, and the totals arenullwhen fee totals are hidden;- the API enforces this. There is no parameter that returns a hidden price, and a rent filter leaves hidden-price records out of its results.
Sorting by rent still orders records whose price is hidden by their stored value. Do not present a rent-sorted list as a price ranking on a site that mixes properties with hidden pricing.
The availability feed
GET /availability — only the part of a unit that moves.
{
"id": "01000000-0000-4000-8000-00000000000a",
"slug": "101",
"property_id": "01000000-0000-4000-8000-000000000002",
"floor_plan_id": "01000000-0000-4000-8000-000000000005",
"is_available": true,
"available_on": "2026-02-01",
"rent": { "min": 1500, "max": 1500, "base_min": 1500, "base_max": 1500 },
"updated_at": "2026-01-15T12:00:00+00:00"
}Parameters: property, available, page, per_page (default 500, ceiling 2000). meta.cache.ttl is 60.
The intended pattern: cache the heavy /units and /floor-plans payloads for as long as their tags allow, re-read /availability often, and join on id or slug.
Price matrix
GET /units/{unit}/price-matrix?from=2026-02-01&to=2026-04-30 — rent by move-in date and lease term.
{
"data": [
{ "start_date": "2026-02-01", "best_term": { "months": 12, "rent": 1500 }, "terms": [{ "months": 6, "rent": 1650 }, { "months": 12, "rent": 1500 }, { "months": 15, "rent": 1525 }] }
],
"meta": { "unit": { "id": "0100…000a", "slug": "101", "number": "101" }, "cache": { "tags": ["inventory:0100…0002"], "ttl": 300 } }
}fromdefaults to today. At most 120 rows.- Served from stored data only. The endpoint never calls the property's management system, so it is always fast; a unit with no stored matrix returns
[]. []too when the property's pricing display settings hide the price.- Counted against the tighter write rate limit (60 / minute). Fetch it when a visitor opens a unit, not for every unit in a list.
Unit types
GET /unit-types (optionally ?property=) — studio, one bedroom and so on, for "browse by size" navigation.
{ "slug": "1-bedroom", "name": "1 Bedroom", "bedrooms": 1, "units_count": 1, "available_units_count": 1, "rent": { "min": 1500, "max": 1500, "base_min": 1500, "base_max": 1500 }, "sqft": { "min": 720, "max": 720 }, "fees": { "monthly_total": 0, "monthly_total_max": 0 } }fees is the range of mandatory monthly fees across the type's available units; both values are null when fee totals are hidden.
Search facets
GET /facets (optionally ?property=) — what a search UI's filters can offer, with counts. Tags: site, inventory:{property_id}.
{
"properties_count": 1,
"units_count": 1,
"available_units_count": 1,
"rent": { "min": 1500, "max": 1500, "base_min": 1500, "base_max": 1500 },
"sqft": { "min": 720, "max": 720 },
"bedrooms": [{ "value": 1, "units_count": 1, "available_units_count": 1 }],
"bathrooms": [{ "value": 1, "units_count": 1, "available_units_count": 1 }],
"cities": [{ "value": "Austin", "state": "TX", "properties_count": 1 }],
"states": [{ "value": "TX", "properties_count": 1 }]
}Each value is exactly what the matching filter accepts (beds, baths, city, state), so a facet can be turned into a query string without translation. Use rent and sqft as slider bounds.
Last updated on