Docs
API guides

Manage PMS & CRM connections

Configure integrations, attach properties, and trigger syncs programmatically.

Who this is for: implementation teams onboarding many properties, and PMS/CRM vendors whose customers connect through Resi.

What you'll build: programmatic setup and operation of the connections that pull inventory into Resi and push leads out of it.

What a connection is

A connection is a configured integration between a Resi account and an external system: a PMS, a CRM, a tour provider, an analytics or accessibility tool. Connections are created at the account level and attached to properties; the attachment carries per-property settings such as the external system's property id.

This layer matters more than it looks. For any property with an active PMS connection, the PMS is the source of truth for inventory and pricing. When a client says unit data is stale or wrong, the answer is almost always here, not in the API.

Connection types

A connection's type is a registered type key. The category is derived from it. The main types:

CategoryType keys
pmsapp-folio-pms, door-loop-pms, encasa, engrain, entrata-pms, mri-pms, real-page-v1, real-page-v2-cross-fire, real-page-v2-ode, rent-cafe-basic, rent-cafe-v2-pms, rent-manager-pms
crmanyone-home, app-folio-crm, elise-ai, email-notification, entrata-crm, funnel-customer-crm, knock, lead2-lease, rent-cafe, yardi-iq
tourentrata-tour, funnel-customer-tour, hyly-tour, rent-cafe-v2-tour

Other categories cover analytics (google-analytics, heap), accessibility (accessibe, audio-eye, user-way), chatbots, multimedia, reviews, search and website tools, and zapier. An unknown type is a 422.

Listing connections

GET /api/v2/connections:

curl -G "https://v2.getresi.com/api/v2/connections" \
  --data-urlencode "category=pms" \
  --data-urlencode "enabled=true" \
  -H "Authorization: Bearer $RESI_TOKEN"

Filters: type, category, enabled, name (partial match), created_at and updated_at (ranges, such as updated_at[gte]), and property_id (resolves through the attachment).

Each connection carries latest_log, the most recent entry in its run log (event_type, status, message, created_at). Start here when debugging a data problem: a disabled connection or an Error status explains a great deal.

Creating a connection

POST /api/v2/connections:

curl -X POST https://v2.getresi.com/api/v2/connections \
  -H "Authorization: Bearer $RESI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Entrata — East region",
    "type": "entrata-pms",
    "enabled": true,
    "settings": {
      "connection": { "subdomain": "…", "username": "…", "password": "…" }
    }
  }'

type and name are required. pricing_includes_fees says whether the rents this connection imports already include fees.

settings is type-specific. Credentials and options sit under connection; the keys inside differ for every type, and the API accepts settings without checking them against the type. A wrong key is not rejected; the connection simply fails when it runs. Get the required shape from the Resi team, or from an existing connection of the same type, before writing one.

Credentials are write-only. V2 returns every credential in settings as ********: any key whose name contains password, passphrase, secret, token, api_key or private_key, at any depth. An empty credential is returned as stored, so you can tell a set credential from a missing one. You still send credentials in plain text, so do not log request bodies.

Updates merge into the stored settings. Keys you send replace their stored value, keys you leave out are kept, and a credential sent back as ******** keeps its stored value. You can read a connection, change one option and write the whole object back without re-sending its password. To rotate a credential, send the new value. To clear every setting, send "settings": null.

Attaching properties

A connection does nothing until properties are attached. Use POST /api/v2/connections/{connection}/properties:

curl -X POST "https://v2.getresi.com/api/v2/connections/$CONNECTION_ID/properties" \
  -H "Authorization: Bearer $RESI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "property_id": "019dd604-8468-7388-9a52-4c31a2e1209a",
    "settings": { "property": { "property_id": "1023848" } }
  }'

The per-property settings is where the external system's own property identifier goes, under property. The key name depends on the type: Entrata, RentCafe and AppFolio types use property_id, others use their own. This is the field that most often gets onboarding wrong. A wrong external id produces a connection that runs successfully and imports nothing, or worse, imports another property's units.

Attaching is idempotent. Re-attaching an already-linked property returns 200 and merges the settings you send into its stored settings, in the same way as a connection update. Omit settings to leave them as they are, or send null to clear them.

Detach with DELETE /api/v2/connections/{connection}/properties/{propertyId}:

curl -X DELETE "https://v2.getresi.com/api/v2/connections/$CONNECTION_ID/properties/$PROPERTY_ID" \
  -H "Authorization: Bearer $RESI_TOKEN"

Detaching stops future syncs for that property. It does not remove data already imported.

Running a connection

POST /api/v2/connections/{connection}/run:

curl -X POST "https://v2.getresi.com/api/v2/connections/$CONNECTION_ID/run" \
  -H "Authorization: Bearer $RESI_TOKEN"
{ "message": "Connection run has been queued." }

Returns 202: the run is queued, not complete. It runs every attached property and takes no body. Two failure responses are worth handling explicitly:

ResponseMeaning
422 "This connection type cannot be run."Not every type is runnable. Most PMS types are; door-loop-pms, mri-pms and rent-manager-pms are not, and nor are most CRM, tour, analytics and accessibility types, which act on events such as a lead rather than on a pull
422 "This connection is disabled and cannot be run."Enable it first

Confirming a run finished

There is no run-status endpoint and no completion webhook. Two signals, used together:

  • The connection's logs. GET /api/v2/connections/{connection}/logs lists every sync, import and webhook newest first, with its status, message (on a failure, why), duration and run details in metadata. Filter by property_id, status or created_at[gt] (the time you triggered the run). A run over several properties writes several entries, so check one per property. latest_log on the connection is the newest of them. Logs are kept for 14 days.
  • The data itself. Watch the attached property's units change:
async function waitForSync(propertyId, before, { timeoutMs = 300_000 } = {}) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    await new Promise((r) => setTimeout(r, 15_000));
    const units = await collect("/units", { property_id: propertyId });
    const latest = Math.max(...units.map((u) => Date.parse(u.updated_at)));
    if (latest > before) return { changed: true, units: units.length };
  }
  return { changed: false };
}

collect() is the paginating helper from Build an ILS / syndication feed. Poll on a sane interval (15 seconds, not 1) with a hard timeout. Connections run against third-party systems and can legitimately take minutes.

Do not trigger runs on a tight schedule. Connections run on the schedule configured for them in the Resi app. Manual runs are for onboarding and for recovering from a specific failure; hammering run puts load on the client's PMS and can get Resi rate-limited by the provider.

Onboarding sequence

The order that avoids the most rework:

1. Create the properties in Resi          (is_enabled: false)
2. Create the connection                   (correct type + credentials)
3. Attach each property                    (correct external property id — verify!)
4. Run the connection                      (202)
5. Wait, then verify unit counts against the PMS
6. Fix any mis-mapped external ids and re-run
7. Enable the properties

Step 5 is not optional. Verify counts per property before enabling: a mis-mapped external id is invisible until someone notices a property is showing another community's units. See Onboard a portfolio in bulk.

Who owns which fields

Once a PMS connection is live, its imports overwrite the fields it maps on every sync. Which fields those are depends on the connection type and which of its import steps are enabled. Availability, pricing, unit numbers, floor plan mapping, available_at and vacate_at are the usual PMS-owned fields; some types also import amenities, images and property details.

Writing to a field the connection syncs is refused. A PATCH or POST that sets one returns 422, with an errors entry naming the field and the connection, because the next sync would overwrite it. These are the same fields the Resi app locks with a shield icon. If a client needs a field to differ from the PMS, that is a connection configuration change: turn off sync for that field in the connection's settings, and the API accepts it.

This is the most common source of "the API isn't saving my change" reports. Check for an active PMS connection first.

Lead flow in the other direction

CRM connections receive leads through forms, not through the connection endpoints. When a lead is submitted via POST /api/v1/leads, it is pushed through the form's configured connections. Forms and their routing are managed in the Resi app; V2 has no forms endpoints.

Read a property's forms and the connections each one routes to with GET /api/v1/property/{property}/forms:

curl "https://v2.getresi.com/api/v1/property/$PROPERTY_ID/forms"

Troubleshooting

SymptomFirst thing to check
Units are staleIs the connection enabled? What does latest_log say? When did unit updated_at last move?
A property has no unitsIs it attached? Is the external property id in its attachment settings correct?
Wrong units on a propertyAlmost certainly a wrong external property id
A write is refused with "synced from the … connection"The connection syncs that field; turn its sync off in the connection's settings, or leave it to the PMS
run returns 422Type is not runnable, or the connection is disabled
Pricing differs from the PMSCheck pricing_includes_fees and the property's fee setup before assuming a sync bug: Resi's min_rent is base rent plus mandatory fees

Checklist

  • Connection type key and settings shape confirmed before creating
  • External property id verified per property attachment
  • Unit counts reconciled against the PMS before enabling properties
  • Run treated as async, polled on a sane interval with a timeout
  • No scheduled manual runs
  • No writes to PMS-owned fields
  • Connection payloads never logged

Last updated on

On this page