listing.details
ChatGPTRetrieve detailed information about a specific rental listing. Requires the encrypted listingKey from search.listings results. Returns full property details including floor plans, amenities, photos, contact info, and neighborhood data. The response includes a numeric rating (renter score, 0–5) but does NOT include individual review text. If the user asks to read or see reviews, ALWAYS present listingPageUrl as a markdown link to the property's Apartments.com listing page — whether or not rating is present. Describe the link as the listing page (NOT "the reviews") — the reviews section lives on that page and lazy-loads as the user scrolls. If rating is present, state it; if missing or zero, note no reviews yet. Then add one short statement-form sentence presenting the link. Do NOT offer to open, fetch, summarize, or pull up the reviews on the user's behalf — the link IS the action; let the user click it themselves. After presenting listing details, ALWAYS proactively present the next action based on tourEligibility: If tourEligibility.state is "ScheduleATour", present "Schedule a Tour" and call tour.availability with tourEligibility.selectedTourType; the host will confirm via tour.confirm. If tourEligibility.state is "RequestATour", present "Request a Tour" and call tour.availability with tourEligibility.selectedTourType to fetch dates/times (the property supports online scheduling but must confirm the booking). When the renter picks a date+time, call tour.confirm with isRequestTour=true so the regulatory verificationNotice is rendered. If tourEligibility.state starts with "NotEligible_", inform the user that online tour scheduling is not available and offer listing.lead to contact the leasing office. If tourEligibility is null (eligibility check failed), do not offer a tour; offer listing.lead to contact the leasing office instead. The response also includes a requiredFollowUpQuestion field — ask it verbatim as the final sentence.
listing.lead
ChatGPTSubmit user contact information to express interest in a property. This tool powers the "Message" action shown on the listing card — it sends the renter's contact info to the leasing office. Use this tool when the user indicates interest in a specific listing. Treat both explicit requests and softer positive intent as interest signals when they refer to one listing, for example: asks to apply, asks to contact leasing, says they want this property, says this listing looks good, says they like this one, says this seems like a strong option, or asks about next steps for that listing. EXPLICIT MESSAGE INTENT — skip the consent question: When the user explicitly asks to message or contact the property (e.g., "send message to <property>", "message <property>", "message the leasing office", "contact leasing at <property>", "email them", "reach out to <property>"), treat this AS the consent — do NOT ask "Want me to connect you with the leasing manager?" again. Go directly to collecting full name, email, and phone (reusing anything already shared), then call this tool. SOFT INTEREST — ask consent first: If the user expresses positive interest in a specific listing but has NOT explicitly asked to message/contact (e.g., "I like this one", "this looks good", "what are next steps"), ask one consent question: "Want me to connect you with the leasing manager?" If yes, collect full name, email, and phone, then call this tool once. If the user declines, do not call this tool and continue helping with listings. Guardrail: if the user already shared contact info earlier in the conversation, do not ask for it again; reuse it for additional interested listings and only ask for missing fields. Search restriction: do NOT call listing.search or any search tool before, during, or after this tool call. The user has already identified the listing — searching will disrupt their experience. On success, you MUST display the Reference Number (leadId from the tool result) to the renter — never omit it; it is their proof of submission.
local.guide
ChatGPTGet neighborhood context for a geographic area — walk score, city demographics, rent trends, and top schools. Requires matchType and encrypted matchId from prior search or listing detail results. For non-City geographies, provide cityId for demographics. Returns structuredContent for conversational presentation.
media.listing
ChatGPTRetrieve all community-level media for a property: photos, videos, 3-D virtual tours, and floor-plan images. WHEN TO CALL: (1) user explicitly asks to see photos, videos, virtual tours, 3D tours, Matterport, or floor plans for a property; (2) user asks "how many photos/videos/tours does this listing have?" — use categoryCounts in the response to answer without scanning items[]. INPUT: listingKey from listing.details or search.listings results. Set preferredTab based on the user's intent: "Photos" for photos/images, "Videos" for videos, "Virtual Tours" for virtual tours / 3D tours / Matterport, "Floor Plans" for floor plans. The response selectedTab field tells the MediaCard which tab to pre-select — pass it directly to the component. AFTER RESPONSE: render the MediaCard component using selectedTab as the active tab. If hasUnitLevelMedia is true and user is interested in a specific unit → also call media.unit with that unit's unitKey. If hasModelLevelMedia is true and user is interested in a specific floor-plan type → also call media.model with that model's modelKey.
media.model
ChatGPTRetrieve media for a specific floor-plan model: floor plan images, representative interior photos, and model-level 3-D tours. WHEN TO CALL: (1) user asks for photos, floor plans, or tours for a specific floor-plan type (e.g. "show me the 2-bedroom floor plan"); (2) hasModelLevelMedia is true in a media.listing response and user is interested in a specific model. INPUT: modelKey from a model-level rental in listing.details (rentals[].modelKey). Set preferredTab based on the user's intent: "Photos", "Videos", "Virtual Tours" (also for 3D tours / Matterport), or "Floor Plans". The response selectedTab field tells the MediaCard which tab to pre-select — pass it directly to the component. Do NOT call if modelKey is absent on a rental — not all rentals have model-level media. AFTER RESPONSE: render the MediaCard component using selectedTab as the active tab.
media.unit
ChatGPTRetrieve media specific to an individual apartment unit: interior photos, unit-specific floor plan, and virtual tour for that exact unit. WHEN TO CALL: (1) user asks to see photos or tours for a specific unit they are considering; (2) hasUnitLevelMedia is true in a media.listing response and user is focused on a specific unit. INPUT: unitKey from a unit-level rental in listing.details (rentals[].unitKey). Set preferredTab based on the user's intent: "Photos", "Videos", "Virtual Tours" (also for 3D tours / Matterport), or "Floor Plans". The response selectedTab field tells the MediaCard which tab to pre-select — pass it directly to the component. Do NOT call if unitKey is absent on a rental — not all rentals are unit-level. AFTER RESPONSE: render the MediaCard component using selectedTab as the active tab.
model.fees-and-policies
ChatGPTRetrieve transparent fees and policies for a specific floorplan/model within a listing. Returns model-level pricing details such as application fees, pet fees, parking fees, and storage fees. Use the listingKey from listing.details and the modelKey from any rental entry belonging to the floorplan the user is asking about. WHEN TO CALL: Call this tool when the user asks about fees for a floorplan, bedroom/bathroom count, or model type — i.e. the user has NOT selected a specific unit but is asking about a unit type (e.g. "2 bed 2 bath fees", "what are the fees for the A1 floorplan", "how much is the application fee for a 1 bedroom"). Do NOT call this tool if the user is asking about a specific individual unit by unit number — use unit.fees-and-policies instead. FALLBACK RULES: If this tool returns no fee data (empty fees array) or returns an error, do NOT surface the failure to the user. Instead, silently fall back to any fee information already present in the listing.details context (e.g. application fee, pet fee, deposit amounts) and present those listing-level figures as the cost estimate. Note naturally that exact amounts should be confirmed with the leasing office. RESPONSE RULES: Never mention tool calls, tool results, or data retrieval to the user. Never say phrases like "I tried to pull", "the tool returned", "data was unavailable", "fee projection was unavailable", or any language that exposes internal execution. If fee data is available, present it as a clean cost breakdown (up-front and monthly) in natural language. If fee data is missing or partial, silently omit those line items and note only that specific figures may vary — never attribute it to a system or data issue.
rent.affordability
ChatGPTAssess whether a rent amount fits a renter's budget using the 30% gross income guideline. WHEN TO CALL: call proactively when the user mentions their income, asks if they can afford a listing, asks what rent range they should look for, or asks how a specific rent compares to the market. Do not wait for an explicit request — treat any affordability signal as a trigger. Two modes: (1) Pure calculation — provide income and optionally a rent amount to get budget, ratio, and tier. (2) Market comparison — additionally provide a cityId (from search.listings or listing.details) to compare budget against average rents in that area. Income must be a raw number (no $ or K suffixes — convert before calling, e.g. '80K' → 80000). incomePeriod is required: 'annual' or 'monthly'. If both rentAmount and cityId are provided, both modes run simultaneously. AFTER RESPONSE: present the recommended monthly budget, the affordability tier (e.g. affordable, stretched, over budget), and if market data is available, how the budget compares to average rents in the area.
search.listings
ChatGPTSearch for rental property listings (apartments, houses, condos, townhouses) by location, price, bedrooms, amenities, and other criteria. Returns a paginated list of matching listings with pricing, photos, and coordinates for map display. LOCATION PRECONDITION: This tool REQUIRES a concrete location named by the user — a city, neighborhood, postal code, street address, or named landmark/area. When the user uses proximity words like "near", "nearby", "near by", "here", "around", "close", "local" without naming a specific place — even when combined with filters like price, beds, or amenities (e.g. "nearby rentals under $2000", "near me 2 bed") — call search.near_me instead of this tool. When the user mentions price/beds/amenities but no place and no proximity word (e.g., "find apartments under $3000"), ask: "Which city, neighborhood, or ZIP code should I search?" and wait. When the user names a place alongside a self-relative phrase (e.g., "near me in Atlanta", "apartments near the airport in Boston"), use the NAMED place directly and call as normal. PAGE SIZE RULES: Always use pageSize=50 (and pageNumber=1 for the first page). Applied uniformly across all UI modes so the result ordering is identical whether the user is in list, inline-map, or fullscreen-map view. Increment pageNumber to retrieve subsequent pages of 50. RESPONSE RULES: (1) For pageNumber=1, the opener, bullet count, and CTA BRANCH on listings.length. The "narrow down with more filters" option appears ONLY when listings.length >= 10 — below that, results are too few to narrow further; offer "adjust your search" instead: • 1 result → render 1 bullet. Opener: "<Property Name> matched your search criteria of <filters>." CTA: "Want details on it, or adjust your search?" No "see more", no "narrow". • 2 results → render both bullets. Opener: "<Property A> and <Property B> matched your search criteria of <filters>." CTA: "Want details on one, to compare them, or adjust your search?" No "see more", no "narrow". • 3 results → render all 3 bullets. Opener: "There are various places with <filters> — here are the top 3." CTA: "Want details on one, to compare a few, or adjust your search?" No "see more" (the top 3 ARE all results), no "narrow". • 4–9 results → render the top 3 bullets. Opener: "There are various places with <filters> — here are the top 3." CTA: "Want to see more rentals, get details on one, compare a few, or adjust your search?" No "narrow". After the user asks for "more", render the remaining bullets (4–listings.length) in a single chunk, then close with: "Want details on one, to compare a few, or adjust your search?" • 10+ results → render the top 3 bullets. Opener: "There are various places with <filters> — here are the top 3." CTA: "Want to see more rentals, get details on one, compare a few, or narrow down with more filters?" In all five cases, wait for the user before doing anything else. (2) PAGINATION WITHIN THE CURRENT RESULT — no new tool call (applies ONLY when listings.length >= 4 on page 1, or on any page>=2): on every 'see more rentals' reply, render the NEXT CHUNK of up to 10 bullets from the SAME result object already in context. Track how many bullets you have shown so far for this page (call it N). The next 'more' response renders bullets at 1-based indices (N+1)..min(N+10, listings.length). Page-1 chunk sequence (listings.length >= 10): top 3 → 4–13 → 14–23 → 24–33 → 34–43 → 44–50 (or fewer if listings.length < 50). For 4–9 results, there is just one follow-up chunk: 4–listings.length. DO NOT call search.listings while any listing in the current result is still unshown — they are already in the structuredContent of the previous tool result that is still in context. End each chunk response with: "Want details on one, to compare a few, see more rentals, or narrow down with more filters?" — but DROP "narrow down with more filters" (replace with "adjust your search") when listings.length < 10, and DROP "see more rentals" once every bullet on the current page has been rendered. Wait after each response. (2a) For pageNumber>=2, the FIRST re…
search.near_me
ChatGPTGPS-based apartment search. Call this tool — with no arguments — whenever the user wants rentals near their current location and has NOT named a specific city, ZIP, or address. Trigger phrases (call immediately, no questions asked): "search nearby", "search near me", "apartments near me", "rentals near me", "nearby apartments", "nearby rentals", "near by rentals", "close by rentals", "rentals around here", "local apartments", "apartments in my area". Also triggers when filters are combined with proximity: "nearby under $2000", "2-bed near me". This tool takes NO arguments. The app retrieves GPS automatically and calls search.listings. After calling this tool, reply only: "Getting your location now…" and wait. When the updateModelContext message with GPS coordinates arrives, call search.listings immediately. If the user specified filters, include them. If no filters were specified, call search.listings with ONLY the GPS coordinates — do NOT ask the user for filters.
tour.availability
ChatGPTFetch available tour dates and timeslots for a property. No personal data is submitted. Turn 1: call with listingKey + tourTypeId (no date) to get available dates with pre-fetched timeslots. Each entry in openDates has a displayDate, rawDate, and times array (each with display and raw fields). Show dates and times together and ask the renter to pick BOTH a date AND a time in one response. If the renter picks a date+time from the Turn 1 list, skip Turn 2 and go directly to tour.confirm using rawDate (YYYY-MM-DD) and raw time (HH:MM 24h). Turn 2 (fallback only): call with date + isIntegrated if the renter asks for a date not in the Turn 1 list. Pass isIntegrated back on all subsequent calls — it is returned in the Turn 1 response. Search restriction: do NOT call listing.search or any search tool before, during, or after this tool call. The user has already identified the listing — searching will disrupt their experience.
tour.confirm
ChatGPTSubmit the tour booking with the renter's chosen date, time, and contact info. ALWAYS pass propertyName (from listing.details name field) and tourTypeName (10 = "In Person", 20 = "Self-Guided", 30 = "Personal Online") so the consent dialog shows meaningful labels. Before calling, you MUST have collected from the renter: first name, last name, email, and phone number. Do NOT call until all four fields are confirmed — the tool will reject the call without them. NAME PARSING: when the renter provides their name as one string (e.g. "Junhong xue", "Maria Garcia Lopez", "wei li"), split on whitespace — the FIRST token is firstName, the REMAINING tokens (joined with spaces) are lastName. Accept ANY non-empty token as a valid last name regardless of length, case, or origin — short names like "xue", "li", "ng", "wu", "ho" are valid. Do NOT re-ask for a last name based on how it looks. Reuse contact info already shared earlier in the conversation; only ask for missing fields. Pass isIntegrated from the tour.availability Turn 1 response. Pass isRequestTour=true when the listing.details tourEligibility.state was "RequestATour" — the booking is a request that the property must confirm, and the tool result will include a regulatory verificationNotice that MUST be rendered verbatim. Omit or set false for "ScheduleATour" properties. On success, you MUST display the Confirmation Number (appointmentConfirmationId from the tool result) — never omit it. It is the single most important field for the renter.
typeahead
ChatGPTResolves a search term to a property or location. Call this in two scenarios: 1. User mentions a specific property/apartment name → typeahead identifies the property → leads to residentialForRent → listing.details (full property detail) 2. User searches by neighborhood, POI, zip code, or address (anything that is NOT a plain city name) → typeahead resolves it, then routes based on MatchType: - Property address → if resolved to a property (MatchType 1, 4, 13, or 20) → residentialForRent → listing.details - Neighborhood / POI / zip / street → resolves to city+state → search.listings Best suited for queries containing a specific property name (e.g. "Enzo", "Avalon", "Camden"), street address, neighborhood, or POI — even when a city is also mentioned, like "in Irvine". The city is location context for the property search, not the search target itself. For generic city-wide rental searches without a property name, search.listings is preferred. Recommended queries for this tool: - find me Enzo apartment in Irvine (property name → leads to listing.details) - show me Avalon apartments near Austin (property name → leads to listing.details) - 123 Main St, Irvine CA (property address → MatchType 1/4/13/20 → residentialForRent → listing.details) - apartments in Turtle Rock (neighborhood → resolves to city+state → search.listings) - near Irvine Spectrum (POI → resolves to city+state → search.listings) - apartments near 92612 (zip code → resolves to city+state → search.listings) - near UCI (POI → resolves to city+state → search.listings) Examples that should NOT call this tool (use search.listings instead): - apartments in Irvine (city name only — no specific property name) - 2BR in Newport Beach (city name only — no specific property name) When calling this tool: - Set keyword to the property name, neighborhood, POI, zip code, or address only. Do NOT include words like "apartment", "near", "in" in keyword. Examples: "Enzo apartment in Irvine" → keyword: "Enzo"; "near UCI" → keyword: "UCI" - Include stateCode when you can infer it, in this order: 1. Explicitly stated in the current prompt ("in Irvine" → "CA", "in Austin" → "TX") 2. User's current location — if the host provides the user's location, use that state 3. Prior conversation context — state from the most recent search or location mentioned 4. No state context available → omit stateCode and call the tool anyway. If multiple properties are returned across states, the user will pick from the disambiguation list. - Include centerPoint when you have coordinates from a prior search result. After receiving the result, route based on count and MatchType: - If count=0 (no results found): If the user's original prompt contains a city name: call search.listings with { location: { city, administrativeArea1, countryCode: "US" } } extracted from the prompt, plus all user-specified filters (bedrooms, price range, amenities — do NOT drop them). If no city is available in the prompt: tell the user no results were found for their search term and ask them to refine it (check spelling, try a different name, or search by city instead). - If MatchType is a Property type (1, 4, 13, or 20): If count=1: call residentialForRent immediately with the returned matchKey. If count>1: the result has status="awaiting_selection" and a disambiguationId. Present the numbered displayOptions list to the user and wait. Do NOT call residentialForRent or any other tool — there is no matchKey. After the user picks, call typeahead.resolve with the disambiguationId and the user's selection. That returns the matchKey and MatchType. Then call residentialForRent with those values. - If MatchType is any other value (neighborhood, POI, zip code, etc.): typeahead has resolved the search term to a city+state — now call search.listings: If city and state are present: call search.listings with { location: { city, administrativeArea1, countryCode: "US" } } AND all user-specified filters from the original prompt (bedrooms, price range, amenities — do NOT drop them) If city or state is missing: ask the user which city they want t…
typeahead.resolve
ChatGPTResolves a user's selection from a typeahead disambiguation list to a listingKey. Call this after typeahead returns multiple results and the user has picked a property. Pass the disambiguationId from the typeahead result and the user's selection (e.g. "1", "2", "the Irvine one", "Volar Irvine"). AFTER THIS TOOL RETURNS: call listing.details immediately with the returned listingKey. Do NOT call residentialForRent, search.listings, or any other tool.
unit.fees-and-policies
ChatGPTRetrieve transparent fees and policies for a specific individual unit within a listing. Returns unit-level pricing details such as application fees, pet fees, parking fees, and storage fees. Use the listingKey from listing.details and the unitKey from the specific rental entry in listing.details. WHEN TO CALL: Call this tool only when the user is asking about fees for a specific individual unit — i.e. the user has selected or is viewing a particular unit identified by a unit number or unitKey (e.g. "unit 301", "this unit", "the one on the 3rd floor"). Do NOT call this tool if the user is asking about a floorplan, bedroom count, or model type — use model.fees-and-policies instead. FALLBACK RULES: If this tool returns no fee data (empty fees array) or returns an error, do NOT surface the failure to the user. Instead, silently fall back to any fee information already present in the listing.details context (e.g. application fee, pet fee, deposit amounts) and present those listing-level figures as the cost estimate. Note naturally that exact amounts should be confirmed with the leasing office. RESPONSE RULES: Never mention tool calls, tool results, or data retrieval to the user. Never say phrases like "I tried to pull", "the tool returned", "data was unavailable", "fee projection was unavailable", or any language that exposes internal execution. If fee data is available, present it as a clean cost breakdown (up-front and monthly) in natural language. If fee data is missing or partial, silently omit those line items and note only that specific figures may vary — never attribute it to a system or data issue.
unit.rent-matrix
ChatGPTRetrieves RentMatrix promotional pricing for a specific unit. Returns special pricing offers set by the PMC, in one of two shapes depending on the listing's RentInfoType: - rentInfoType = 1 (Term): a list of prices per lease term, each term has a single rent price (no move-in date selection) example for rentInfoType = 1 (Term): { "rentInfoType": 1, "leaseTerms": [ { "leaseTerm": 12, "options": [{ "rent": 2261, "currency": "USD" }] }, { "leaseTerm": 13, "options": [{ "rent": 1739, "currency": "USD" }] } ] } - rentInfoType = 2 (MoveInDate): a list of lease terms, each with multiple rent options per available move-in date (full price grid) example for rentInfoType = 2 (MoveInDate): { "rentInfoType": 2, "leaseTerms": [ { "leaseTerm": 12, "options": [ { "rent": 2261, "currency": "USD", "startDate": "2026-04-21T00:00:00-04:00" }, { "rent": 2266, "currency": "USD", "startDate": "2026-04-22T00:00:00-04:00" } ] } ] } ONLY call this tool automatically when BOTH are true: 1. hasRentMatrix is true in the unit context. 2. The user is viewing or showing interest in a specific unit (a specific unitId is in focus). Do not wait for the user to ask about pricing — call proactively as part of presenting the unit. Do not call during listing or unit-list browsing. If hasRentMatrix is false or missing, do NOT call this tool. Do not mention this tool, RentMatrix, or missing pricing to the user.