This is the plain-English picture of the Entrata connection: what we read, what we don't, and how a lead actually moves. It was rebuilt on 2026-08-05 against two rounds of live read-only probes run from the VPS, so the old "here's what we'd ask for someday" version is gone. The API source of truth is reference/entrata-touchpoints.md, which holds every call, required params, window caps, measured rate limits, and verified response shapes. The build source of truth is system-design.html, which holds the Constitution, component statuses, and the phased build order. Where either disagrees with this page, they win.
Every write is blocked today, and that is a choice we made. The key carries 28 services: 22 usable reads, 4 that always error, and 2 writes, sendLeads and updateLeads. Liberty enabled those two on 2026-07-31 on a live, occupied building. Brent declined to have them removed, so they stay enabled by choice and there is no vendor-side guard: the deny-by-default read-method allowlist in our code, enforced at the one function that issues requests and failing safe, is the only control that exists or ever will. sendWorkOrders and updateWorkOrders were never granted, and were still withheld on 2026-08-03 when Liberty put the whole maintenance group on the key with two reads on it, so their absence is a deliberate per-method decision on Liberty's side and not something to lean on. Anything this design would write (a guest card, a note on a guest card, a work order) goes into our own outbox and stays there. Nothing is issued to Entrata without Brent's written per-property sign-off naming the property and the methods. Constitution rule 2: read-only against any live production property. 📌 Decision, Brent, 2026-08-05, and it does not loosen anything today: the rule will be lifted on NPI-owned properties when Foothold goes live on them (Aspen, then Summerset), and only when all three conditions hold: NPI owns it, Foothold is live on it, and Brent has signed off in writing naming that property and those methods. Owyhee Park is not covered and is not expected to be, since it is Liberty's building, so everything on this page stays read-only for Owyhee permanently. The allowlist becomes per-property rather than going away, and an unrecognised property config still means read-only. sendWorkOrders became a live ask the same day, for owned properties only, and it has never been made.
Liberty Asset Group is the property manager and Entrata is their PMS. Access is live: one API key, read-only, in production since 2026-07-31, every call originating from the NPI VPS at 5.78.139.104. The key covers exactly one property: Owyhee Park, Liberty's own Boise building, 52 units across 4 buildings and 4 floor plans. Aspen and Summerset are not on it. Aspen closes 8/31 and isn't in Liberty's Entrata yet, Summerset either, so everything below is verified against Owyhee Park and is the shape our properties inherit the day they land on the key. North River's software is still being decided.
Pull = the 22 usable read methods, called serially from the VPS and cached, never on a page view. Outbox = every write intent is written to a queue we own and held there unsent. The arrow points at a box on our side, not at Entrata. It only ever reaches Entrata after Brent signs off in writing, per property.
| Data | Direction | What we use it for | Status |
|---|---|---|---|
Availability & pricinggetUnitsAvailabilityAndPricing | Pull | Live rent + available dates onto the Foothold site (/rentals, /map) so listings never go stale. Freshness is a ranking signal on Apartments.com. Entrata's availability is the one to trust, don't derive it from lease status, the two don't reconcile. Don't take square footage from this method, its numbers are broken. | Reads live |
Units, plans, unit typesgetPropertyUnits, getFloorPlans, getUnitTypes | Pull | All 52 units with building, floor, unit number and address; 4 floor plans with the authoritative square footage and CDN-hosted floorplan images. Rent comes back for all 24 lease terms but only the 12-month term is web-visible, so that's the one we quote. Display by floor plan, price by unit type. | Reads live |
Property basicsgetProperties | Pull | Office hours, lease terms offered, post months. Read the hours, never hardcode them. Owyhee reads M-F 9 to 5 with no weekend coverage, which is the number the whole after-hours argument rests on. | Reads live |
Amenities & specialsgetAmenities, getSpecials | Pull | Permissioned, and they return zero rows. HTTP 200 with no data. Amenity copy and concession display cannot be synced, they're our own content with a named owner and a staleness date, and concessions are an out-of-band conversation with Liberty. | 200, no data |
LeadsgetLeads, getLeadPickLists | Pull | The guest card CoStar already created, the speed-to-lead clock (createdOnDateTime), lead source mix, and the only working source of resident date of birth. Pick lists are the ID vocabulary to pin: 155 event types, 62 lead statuses, 15 named Liberty agents, 17 lead sources. Needs fromDate + toDate; takes a 730-day window, so 5 years of history is 3 calls. Highest-PII method on the key. | Reads live |
Guest cards + note loggingsendLeads, updateLeads | Outbox | The design writes our lead in as a guest card and mirrors the full word-for-word thread onto it as notes, so the PM works it in their normal queue. Both are writes. They queue in our outbox with real pick-list IDs and are never issued until Brent signs off for that property. | Blocked, rule 2 |
| Applications | Link | Never an API write and never was: the applications group isn't on the key at all. We hand the renter Entrata's own ProspectPortal apply link and follow up on stalled ones by watching lead status. Open item: the per-property apply URL from Liberty. Don't guess it. | No API |
Residents & leasesgetLeases, getLeaseDetails, getMitsLeases | Pull | The current rent roll (52 leases, 103 customer records, 88 current), scheduled charges, loss-to-lease against market rent, and renewal exposure by month. Best pet source on the key: 34 animals across 30 leases, with name, type, breed, color, age, weight. getMitsLeases gives household grain (100 people) and the customer IDs the consent read needs. Neither returns birth dates. ⚠️ Omit the status filter, filtering on it returns 190 cancelled leases instead of the roll. | Reads live |
Work ordersgetWorkOrders, getWorkOrderPickLists | Pull | Granted 2026-08-03 and verified returning real production tickets. Feeds the turn engine and the "How'd we do?" trigger. Filter out Entrata PaaS records, those are system-generated and must never fire a resident survey. Collapse parent/child before counting. Pass petInfo to whoever gets sent to the unit. The pick list carries the priority/status/problem vocabulary and Liberty's 12-person staff roster; cache it, it's the tightest daily bucket on the key. | Reads live |
Marketing consentgetMarketingPreferences, getMarketingPreferencePickList | Pull | Opt-in per customer per channel (email, phone, postal mail, SMS) with consent type. This is the gate in front of every outbound touch, including physical mail. Works for both the lead side and the resident side, and one person can hold both sets, so check both and fail closed. A missing preference means don't send. Cache the pick list, it's the tightest hourly bucket on the key. | Reads live |
Tour calendargetCalendarAvailability | Pull | Real, maintained tour configuration: 30-minute appointments, a hard 120-minute minimum lead time, agent-guided M-F 08:00 to 16:00 MST, self-guided all 7 days 06:00 to 18:00 MST at one at a time. ⚠️ 7-day maximum window, so a 60-day horizon costs 9 calls. ⚠️ Liberty books tours manually, so this is a template, not a booking ledger: offer windows, confirm with a human. | Read, human confirms |
FinancialsgetGlTransactions | Pull | GL actuals, 90 accounts, line items tagged to unit and building, which makes FlightDeck NOI and NPI's monthly reconciliation a drop-in. Budget comparison is not on the key, so NOI-vs-budget sources budget from our own model. ⚠️ Resident names sit in the AR payment memos, treat it as a PII feed, not "just numbers." | Reads live |
Not on the key, so don't design against it: the entire customers group, getLeadEvents, getPropertyMedia (property photo library), getExpiringLeases, lease documents, inspections, budgets and bank accounts, and the applications, arcodes, arpayments, artransactions, leasingcenter, pricing, queue and vendors groups in their entirety. Most have a workaround in the table above; none is worth an ask right now.
Build the integration behind a PMS adapter so North River can plug in a different source later without reworking the site or the agent.
This is the path from "renter clicks inquire on Apartments.com" to "Taylor's team sees it," confirmed from CoStar + Entrata docs:
feeds@apartments.com, linking the Listing ID to the Entrata Property ID), and (b) as an email to up to 6 lead destinations you choose.getLeads, which is how we dedupe against the card CoStar already created and time ourselves off createdOnDateTime, the real speed-to-lead clock. This half runs today. It's a read.updateLeads so the on-site team reads exactly what the AI said, not a summary. That call is a write, so under rule 2 it does not go out: the note queues locally and replays only once writes are signed off for that property. Which means that until sign-off, the guest card does not show the thread. That's a real gap, not a rounding error, and it's the price of the read-only rule.The authoritative full transcript sits in our own system (GHL / the agent's datastore), which we own, so the accountability trail never depended on Entrata in the first place. Every message the agent sends and every reply it gets is logged as its own note. Logging a note and sending a message are different operations, and the agent only ever writes notes, so the prospect never gets a duplicate.
Build the queued notes against the real ID vocabulary now. getLeadPickLists hands over 155 event types, 62 lead statuses, 15 named leasing agents and 17 lead sources, and it reads today. An outbox full of invented IDs has to be rewritten before it can ever be replayed, which turns a deferred write into a rebuild.
Entrata has guest SMS, but it's a dashboard tool with no API to send outbound programmatically. Every major AI leasing agent (EliseAI, Funnel, Knock) sends on its own number and logs back to Entrata. The only thing that sends natively through Entrata is Entrata's own ELI+ AI (because it is Entrata, a black box we'd rather not hand the conversation to, it costs us the accountability transcript + speed tuning + data).
Single point of contact, the right way: the agent owns one number, and we forward the property's published number to it (exactly what EliseAI does). The guest sees one consistent number and one thread; the on-site team works from the logged notes.
If LAG separately messages the same prospect through Entrata's own texting/email tools, that message is in Entrata but not our system, and the thread fragments. Fix is policy, not tech: the agent owns the conversation channel; LAG works from the logged notes and takes the tour. Route the Apartments.com lead email only to the agent for first-touch so two people never reply to the same renter. Note this bites harder while note logging is deferred, since there are no logged notes in Entrata yet to work from.
What the lead loop actually has: getLeads and getLeadPickLists read today. sendLeads and updateLeads are permissioned and banned. Everything else on the old wish list is off the key and stays off on purpose: getMitsLeads and sendMitsLeads were never granted, getLeadEvents was left out deliberately because getLeads takes eventDateFrom / eventDateTo / eventTypeIds and returns the matching events inline, and the whole customers group is absent, which costs us nothing: resident data comes from getLeases and the customer IDs come from getMitsLeases. One caveat still unconfirmed: the contact-form message body and unit-of-interest may not pass through the structured guest card, only name/email/phone/move-date are documented.
These solve different problems, and the research cleared up which does what:
getUnitsAvailabilityAndPricing, and that works today. The lead push half (sendLeads) is built as design and held in the outbox. So Path B is no longer "the path that needs access turned on," it's the path that needs the poller built and, separately and later, a written write sign-off.Net: the API question is settled. What's left with Liberty is the ILS syndication flip, the per-property application link, property scope for Aspen and Summerset, and two write asks added 2026-08-05 that have never been sent: sendWorkOrders on NPI-owned properties and sendLeaseActivities for logging Foothold's own resident touches. Those two go out in one email, and a grant on either still doesn't authorize a call.
The API user lives on Liberty's Entrata account (org libertyassetgroup, id 19530). The App Developer Interface Agreement was signed 2026-07-20 at $0 for Year 1, access cleared 2026-07-31, the key was widened again 2026-08-03, and every one of the 28 services on it was probed by 2026-08-05. No cost surprise ever surfaced.
Auth is an X-Api-Key header plus a matching block in the request body, both required, and everything is POST. Entrata whitelisted the NPI VPS static IP 5.78.139.104, and all traffic originates there. No laptop, no Worker, no exceptions. Cost is worth revisiting only if Liberty extends scope, since that's when a per-property line could show up on their side.
Neither is on the key, and neither is in Liberty's Entrata yet. Aspen closes 8/31. Until one of them lands there is no property we can legitimately run the resident-side engines against, because Owyhee Park is Liberty's building, not ours.
Extending the key is the same conversation that created it: Liberty asks their Entrata CSM to add the property to the existing API user. Two things ride along with it. Webhook scope is bound to the same property grant, so we don't build a second integration for it. And property scope is a security control on our side too: the key can already see 98 Liberty properties through getWebsites, including buildings other owners own, so the code enforces a property-ID allowlist next to the read-method allowlist, at the same choke point. We do not probe another owner's building.
Limits are per method, not one global cap, and they're generous where it matters: availability at 600/minute, leases at 1,072/minute. The three that actually bind: getStatus at 6 per minute (so it can't be a heartbeat), getWorkOrderPickLists at 1,000 per day, and getMarketingPreferencePickList at 60 per hour. The two pick lists get cached once a day per property, never called per lead or per ticket. Poll availability on a cadence and serve the site from cache, never call Entrata on a page view.
Four things that will bite the client: calls must be serial, never concurrent, one gate for the whole account. Set an explicit User-Agent, Cloudflare's edge 403s a default one with a non-JSON body reading error code: 1010, which reads exactly like a revoked key and isn't. Window caps are per method (730 days on leads, 7 on the calendar), so measure each one. And every date and time is Mountain, which is a one-day error on move-in dates near midnight if parsed lazily. Entrata's maintenance window is Tuesday late night into Wednesday, weekly: back off, serve last-good cache, alert only past 2 hours stale.