Website & content data
The public V1 endpoints that deliver everything a property website or widget renders.
V1 is the public delivery API. Where V2 gives you normalized resources for managing data, V1 gives you pre-composed, render-ready payloads for one property: the same payloads that power Resi's WordPress property websites and widgets.
Base URL: https://v2.getresi.com/api/v1 · No authentication.
Reach for V1 when you are rendering one property's public presence in a browser, a WordPress site or a widget. Reach for V2 when you are managing data across an account.
Building a new property or leasing website? Use the Sites API instead. It is the delivery API for Next.js property and centralized leasing sites: one token per website, called from your server, with records addressed by slug and every response scoped to that website's properties. V1 stays in place for existing WordPress sites and widgets. See Which API should I use?.
The endpoint map
All endpoints are GET and take a property UUID. An unknown property is a 404.
| Endpoint | Returns |
|---|---|
/property/{property} | Property profile: address, coordinates, contact, urls, availability and rent rollups, and pricing display settings |
/property/{property}/units | Every unit, with building and floor-plan context and a per-fee pricing breakdown |
/property/{property}/unit-types | Floor plans grouped by bedroom count, with square-footage and pricing ranges |
/property/{property}/floor-plans | Floor plans with media, pricing rollups and availability counts |
/property/{property}/buildings | Buildings, each with a count of its available units |
/property/{property}/amenities | Enabled amenities on the property |
/property/{property}/fees | The property's effective fee schedule |
/property/{property}/galleries | Enabled galleries with images, videos and virtual tours |
/property/{property}/media | Older unwrapped media payload, built from the first gallery of each media type |
/property/{property}/faqs | FAQs |
/property/{property}/reviews | Published reviews |
/property/{property}/announcements | Enabled announcements active right now |
/property/{property}/neighborhood | The property's address plus neighborhood categories and places, shaped for a map |
/property/{property}/content-groups | Content blocks with their content items |
/property/{property}/content-conditionals | Flags for which content sections have something to render |
/property/{property}/panels | Configured page panels/sections |
/property/{property}/lists | Configured lists |
/property/{property}/instagram | Synced Instagram posts, if an Instagram connection is set up |
/property/{property}/website | Website configuration |
/property/{property}/widget | Widget configuration |
/property/{property}/groups | Group sets available to the property, with their group trees |
/property/{property}/forms | Enabled forms with fields and CRM destinations |
/property/{property}/integrations | Connections on the property, secrets stripped, incl. tour connection_ids and booking fields |
/property/{property}/lead-sources | Lead sources configured on the property |
/property/{property}/unit/{unit}/price-matrix | Lease-term pricing matrix for one unit |
/units, /floor-plans and /buildings accept ?group= (a group UUID; its descendants match too). /content-groups accepts ?website_connection_keys= to narrow content blocks to named website connections.
On /units, filter before display: a unit is publicly marketable only when unitAvailable is true and unitModel and unitGuestSuite are false.
What V1 gives you that V2 does not
This is the reason many integrations end up touching both:
curl "https://v2.getresi.com/api/v1/property/019dd604-8468-7388-9a52-4c31a2e1209a"{
"data": {
"id": "019dd604-8468-7388-9a52-4c31a2e1209a",
"name": "Pawnee Place",
"slug": "pawnee-place",
"description": "Pawnee Place is a vibrant, community-centric multifamily property…",
"image": "https://dam.getresi.co/18349/conversions/DJI_0316-thumb.jpg",
"imageFull": "https://dam.getresi.co/18349/conversions/DJI_0316-full.jpg",
"website": "https://pawneeplace.com",
"residentPortalUrl": "https://pawneeplace.com/resident-portal",
"applicationUrl": "https://pawneeplace.com/apply",
"tourBookingUrl": "https://pawneeplace.com/schedule-tour",
"phone": "(765) 555-4321",
"email": "leasing@pawneeplace.com",
"address": "123 Main St, Pawnee, IN 47302",
"street": "123 Main St",
"city": "Pawnee",
"state": "IN",
"zipcode": "47302",
"latitude": 40.19,
"longitude": -85.38,
"tags": ["Muncie", "Market Rate", "Luxury"],
"unitsCount": 240,
"availableUnitsCount": 17,
"hasAvailability": true,
"minRent": 1195,
"maxRent": 2450,
"minBaseRent": 1150,
"maxBaseRent": 2395
}
}The response is abridged here; the reference lists every key.
The V2 property carries the address and coordinates too (as an address object). What V1 adds:
unitsCount,availableUnitsCountandhasAvailabilityrollupsminRent/maxRentandminBaseRent/maxBaseRentrollups for the property- the itemized fee breakdown behind every total
Pricing detail lives here. Resi works out prices whenever the underlying data changes, and V1 serves the result with its fee lines. Every priced payload carries both base rent and the total monthly leasing price (TMLP): base rent plus the monthly equivalent of the mandatory fees.
| Endpoint | Fields |
|---|---|
/units | unitPrice, unitMinBaseRent/Max, unitMinTmlp/Max, unitFees, unitFeeTotal, unitFeeTotalMax |
/floor-plans | floorPlanRentMin/Max, floorPlanBaseRentMin/Max, floorPlanTmlpMin/Max, floorPlanFees, floorPlanFeeTotal, floorPlanFeeTotalMax |
/unit-types | minBaseRent, maxBaseRent, minTmlp, maxTmlp, fees, feeTotal, feeTotalMax |
/property/{property} | minRent, maxRent, minBaseRent, maxBaseRent, pricingDisplaySettings |
/fees | The effective fee schedule, with monthlyFeeTotal, monthlyFeeTotalMax and upfrontFeeTotal |
If you need to show a renter what makes up a price (base rent plus each contributing fee), this is where it comes from.
V1 also applies the property's pricing display settings. Under TOTAL_ONLY the base-rent fields are null; under BASE_ONLY the totals and fee fields are null; BASE_AND_TOTAL shows both. A client can also hide every price, or hide prices on units with nothing available. Hidden fields are set to null, never removed. Where a price is null, render hidden_price_label when all pricing is hidden, no_availability_price_label when it is hidden because nothing is available, and no_price_label otherwise. This enforcement is being rolled out account by account; until it reaches an account, its payloads carry every field and your front end should apply pricingDisplaySettings itself. Pricing and fees covers the fee rules and each setting in full.
V1 uses camelCase; V2 uses snake_case. They are different payload families, not two spellings of one schema. Normalize at your boundary rather than passing raw payloads around your codebase.
Lease-term pricing
curl "https://v2.getresi.com/api/v1/property/$PROPERTY_ID/unit/$UNIT_ID/price-matrix"
curl "https://v2.getresi.com/api/v1/property/$PROPERTY_ID/unit/$UNIT_ID/price-matrix?start_date=2026-09-01"Returns one row per lease start date, ordered by start_date. Each row carries rents for the available term lengths (1 to 24 months), plus best_rent and next_best_rent. start_date narrows the result to that one date. A unit that is not on the property is a 404.
Two behaviors to design around:
- Live PMS fallback. When Resi holds no stored matrix for the unit, it queries the property's PMS connection in real time and normalizes the result. That call is much slower than a database read and depends on the PMS being up. Set a generous client timeout, and never call it in a loop over a whole portfolio.
- An empty array is a legitimate answer. No PMS connection, no matrix from the provider, a PMS that fails, or a unit with no term pricing all return
[]. Fall back to the unit's rent range rather than showing nothing.
Cache the response per unit per day, and call it lazily: on unit-detail view, not on listing render.
Building a property page
A reasonable fetch plan for a full property page:
const base = `https://v2.getresi.com/api/v1/property/${propertyId}`;
const [property, floorPlans, units, amenities, galleries, faqs, reviews, neighborhood] =
await Promise.all([
fetch(base).then((r) => r.json()),
fetch(`${base}/floor-plans`).then((r) => r.json()),
fetch(`${base}/units`).then((r) => r.json()),
fetch(`${base}/amenities`).then((r) => r.json()),
fetch(`${base}/galleries`).then((r) => r.json()),
fetch(`${base}/faqs`).then((r) => r.json()),
fetch(`${base}/reviews`).then((r) => r.json()),
fetch(`${base}/neighborhood`).then((r) => r.json()),
]);These are independent, so fire them concurrently. Then fetch /forms and /integrations only on the pages that need a lead form or a tour widget.
Caching
Cache V1 responses on your side. Suggested TTLs:
| Endpoint group | TTL |
|---|---|
/units, /unit-types, /floor-plans | 5–15 minutes |
/property, /amenities, /fees, /buildings | 1 hour |
/galleries, /faqs, /reviews, /neighborhood, /content-groups, /panels | 6–24 hours |
/price-matrix | 24 hours, per unit |
/forms, /integrations | 1 hour, but re-fetch on a form validation failure |
Purging a website's cache
POST /api/v1/cache/clear is a public purge webhook:
curl -X POST https://v2.getresi.com/api/v1/cache/clear \
-H "Content-Type: application/json" \
-d '{"property_id": "019dd604-8468-7388-9a52-4c31a2e1209a"}'Send exactly one of property_id or portfolio_id; sending both is a 422. It purges the page cache of the website host connected to that property in Resi (a Kinsta or WP Engine website connection), and of any merged parent site that also shows the property. It does not reach a CDN or cache you run yourself, so wire your own purge alongside it.
It returns 202 whether or not the id exists or has anything to purge, so the response does not confirm that a purge happened. Rate limit: 30 requests a minute per target id.
What belongs in V1 content
V1 is the published layer: it delivers the marketing content a property website shows to renters. Two things follow.
- Treat every V1 field as public content. These payloads are built for browser delivery, so anything a client stores in a rendered field (notes, staging URLs, work-in-progress copy) should be treated as published. Worth raising once during onboarding so clients keep internal notes in internal fields.
- V1 reads are read-only. Property data is managed through the V2 API and the Resi app. The only V1 writes are lead submission, tour booking, demand events, source observations and cache purge; see Leads & tours.
Last updated on