Leads & tours
Submit leads, check tour availability, book tours, and attribute demand — from a public front end.
These are V1 endpoints, designed to be called directly from a renter-facing surface (a property website, a chatbot, a landing page, a listing site), so they require no credential in your front end. If you are building lead capture or tour scheduling for a WordPress site, a widget or your own front end, this is your API.
Base URL: https://v2.getresi.com/api/v1
Building a Next.js property or leasing site on the Sites API? Use its own form, tour and analytics endpoints from your server instead; see Forms, tours & analytics. They accept an Idempotency-Key, which the V1 endpoints below do not.
Submitting a lead
A lead is bound to a form, not directly to a property. The form determines email notifications and which CRM connections the lead flows into, so the form_id is the single most important field in the payload.
curl -X POST https://v2.getresi.com/api/v1/leads \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"form_id": "019dd607-1a2b-73c4-8d5e-6f7a8b9c0d1e",
"property_id": "019dd604-8468-7388-9a52-4c31a2e1209a",
"email": "renter@example.com",
"first_name": "Alex",
"last_name": "Nguyen",
"phone": "(765) 555-0134",
"move_in_date": "2026-10-01",
"message": "Interested in a 2 bed with a balcony.",
"resi_source_key": "acme-ils"
}'{ "message": "Thanks! A leasing agent will be in touch shortly." }Returns 201. The message is the form's configured success message. Render it to the renter rather than your own hardcoded string; clients customize it.
Fields
| Field | Required | Notes |
|---|---|---|
form_id | Yes | Which form this submission belongs to. Get it from GET /property/{property}/forms. An unknown id is a 404 |
email | Yes | Must be a valid email |
property_id | No | Defaults to the form's first property. Send it when a form spans multiple properties |
move_in_date | No | Any parseable date |
tour_date | No | Any parseable date |
| anything else | No | Passed through and stored on the lead |
The payload is intentionally open. Fields beyond the validated ones are stored on the lead and forwarded to the client's CRM. Send whatever the form collects (first_name, bedrooms, pets, lease_term) as flat top-level keys. Do not nest.
If property_id names a property outside the form's account, it is ignored and the form's default property is used instead. That is a quiet failure mode worth testing during integration. If there is no default either, the response is 404.
Discovering forms for a property
curl "https://v2.getresi.com/api/v1/property/$PROPERTY_ID/forms"Returns the property's enabled forms with their field definitions, settings (including success_message) and the CRM destinations each routes to. Build your submission against the form definition rather than hardcoding fields: clients change forms without telling vendors. After a 422 from lead submission, re-fetch the form; the failure usually means it changed.
Attribution
Resi attributes a lead to a lead source in this order:
resi_source_key: an exact match against a lead sourcecodeconfigured on the property. This is the reliable path. Ask your client to create a lead source for you and give you its code.resi_source_name(or its older aliaslead_source_name): a display name, normalized against known source names. Fuzzier; use only as a fallback.
{ "resi_source_key": "acme-ils", "resi_source_name": "Acme Listings" }If neither resolves, the lead is recorded without a source and your traffic is invisible in the client's reporting, which becomes a renewal conversation. Confirm the code with the client and verify attribution on your first live lead.
The codes configured on a property are public at GET /api/v1/property/{property}/lead-sources. With a V2 token you can also list them, and create them, through /api/v2/lead-sources:
curl "https://v2.getresi.com/api/v2/lead-sources?property_id=$PROPERTY_ID" \
-H "Authorization: Bearer $RESI_TOKEN"Submit each lead exactly once. The endpoint has no idempotency: a repeated request creates a second lead. Queue submissions on your side, retry only on 5xx, and de-duplicate against your own record of what you have already sent before retrying.
Reading leads back
Leads are written through V1 and the Sites API, and read through V2. GET /api/v2/leads lists the account's leads newest first, with the contact fields Resi recognises (name, email, phone, message, move_in_date), the tour for a tour booking, the lead_source_id it was attributed to, and everything submitted in submission. Filter by property_id, form_id, lead_source_id or created_at, and poll for new leads with created_at[gt] set to the newest one you hold:
curl "https://v2.getresi.com/api/v2/leads?created_at[gt]=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer $RESI_TOKEN"It needs a V2 token whose user may view leads; a user restricted to some groups sees the leads of the properties in those groups.
Tours
Tour booking is a two-call flow against the property's configured tour provider. Resi proxies to that provider live, so both calls depend on a third party being up.
0. Find the connection and its booking fields
Get the connection_id, and the fields the provider needs at booking, from GET /api/v1/property/{property}/integrations:
curl "https://v2.getresi.com/api/v1/property/$PROPERTY_ID/integrations"A tour connection has category: "tour", and its id is the connection_id. Its tour_types[] lists each enabled tour type with a label and a schema.fields array: the booking fields that provider requires for that tour type. Each field has a type, a name (the key to submit it under) and data with its label, required flag and validation rules. Fields vary by provider and tour type. Read them; do not assume name, email and phone are sufficient.
1. Check availability
POST /api/v1/tour/get-availability
curl -X POST https://v2.getresi.com/api/v1/tour/get-availability \
-H "Content-Type: application/json" \
-d '{
"property_id": "019dd604-8468-7388-9a52-4c31a2e1209a",
"connection_id": "019dd608-2b3c-74d5-9e6f-7a8b9c0d1e2f",
"tour_type": "in_person_tour"
}'Tour types: in_person_tour (agent-led), virtual_tour, self_guided_tour.
{
"data": {
"availability": [
{
"date": "2026-08-14",
"timezone": "America/Indiana/Indianapolis",
"slots": [
{ "start": "14:30", "end": "15:00" },
{ "start": "15:00", "end": "15:30" }
]
}
]
}
}Slot times are 24-hour HH:MM in that day's timezone.
2. Reserve
curl -X POST https://v2.getresi.com/api/v1/tour/reserve \
-H "Content-Type: application/json" \
-d '{
"property_id": "019dd604-8468-7388-9a52-4c31a2e1209a",
"connection_id": "019dd608-2b3c-74d5-9e6f-7a8b9c0d1e2f",
"tour_type": "in_person_tour",
"tour_date": "2026-08-14",
"tour_time": "14:30",
"first_name": "Alex",
"last_name": "Nguyen",
"email": "renter@example.com",
"phone": "(765) 555-0134"
}'{ "message": "Tour reserved successfully" }Returns 201. Formats are strict: tour_date must be YYYY-MM-DD, and tour_time must be HH:MM in 24-hour time. Anything else is a 422.
Beyond the five base fields (property_id, connection_id, tour_type, tour_date, tour_time), validation is dynamic, driven by the schema.fields published for that connection and tour type. This is why you must render the form from the schema rather than a fixed field list: a provider that requires number_of_guests will reject a booking without it.
A successful reservation creates a lead in Resi as well as the booking with the provider. Pass resi_source_key here too to attribute it.
Tour error cases
Errors on these endpoints come back as { "error": "…" }, or as message and errors for a validation failure.
| Response | Meaning |
|---|---|
422 "No valid property found." | Bad property_id |
422 "No tour connection is configured for this property." | No enabled tour provider for this property, or the connection_id does not match one |
422 "This tour type is not available for this property." | The provider is configured but this tour type is disabled |
422 with errors | A provider-required field is missing or malformed |
422 on reserve | The provider rejected the booking |
409 on reserve | The selected slot is no longer available. Offer the renter another time |
502 | The provider was unreachable or failed. Retryable, but not immediately |
Handle these in the UI, not just in logs. "This tour type is not available" should offer the renter a different tour type or the lead form, and a 502 on availability should fall back to the lead form. A raw error toast loses the lead.
Demand events
POST /api/v1/demand-events records renter behavior into Resi's demand funnel. If you render Resi data in your own UI, sending these gives the client attribution for the engagement you are driving, which is a strong argument at renewal.
curl -X POST https://v2.getresi.com/api/v1/demand-events \
-H "Content-Type: application/json" \
-d '{
"event_type": "unit_viewed",
"property_id": "019dd604-8468-7388-9a52-4c31a2e1209a",
"unit_id": "019dd605-61bc-71bf-acf9-1f1dd97ac1ca",
"session_id": "sess_9f8e7d6c",
"anonymous_id": "anon_1a2b3c4d",
"resi_source_key": "acme-ils",
"occurred_at": "2026-08-06T14:22:31Z"
}'{ "id": "019dd609-3c4d-75e6-8f7a-8b9c0d1e2f3a", "event_type": "unit_viewed", "status": "recorded" }Returns 201. event_type and property_id are required.
Accepted event types (browser-ingestible only):
| Funnel stage | Events |
|---|---|
| Awareness | unit_viewed, floor_plan_viewed, price_matrix_loaded |
| Consideration | lease_term_selected, move_in_date_changed, fee_breakdown_viewed, fee_calculator_opened |
| Intent | apply_clicked, tour_modal_opened, form_started |
| Conversion | form_submitted |
The other four types are recorded by Resi itself: lead_submitted when you submit a lead, tour_availability_checked and tour_reserved when you call the tour endpoints, and apply_intent_clicked when a renter follows a Resi application link. The endpoint rejects them with 422; sending your own equivalents would double-count the client's funnel.
Optional context worth including when you have it: unit_id, floor_plan_id, form_id, move_in_date, lease_term_months (1 to 36), displayed_base_rent, displayed_tmlp and displayed_fee_total (whole numbers), destination_url, and a freeform metadata object. Rate limit: 120 requests a minute per IP.
POST /api/v1/source-observations is its companion: it records the traffic-source signals on a page view (landing URL, referrer, query parameters, click ids) so Resi can resolve them to a lead source. It is usually sent once per session by the Resi pixel. Rate limit: 240 requests a minute per IP.
Requirements for public-facing forms
These endpoints are designed to be called from a renter-facing surface, which puts a few obligations on your integration:
- Never expose a V2 token in the same front end. These endpoints exist precisely so you don't have to.
- Bot protection on any public form is required.
POST /api/v1/leadshas no rate limit of its own. Put a CAPTCHA, honeypot or equivalent in front of lead submission, and rate limit at your own edge. Submissions reach a client's leasing team and CRM directly, so the quality bar is theirs: a leasing office fielding junk inquiries will hold the integration responsible. - Validate on your side before submitting. A
422from Resi after the renter has hit submit is a worse experience than inline validation. - Treat
property_idandform_idas configuration, not secrets, but do not publish them beyond the form that needs them.
Last updated on