Cloud-managed multi-tenant video intercom. ButterflyMX is the dominant smart-intercom platform in modern apartment and mixed-use construction in North America; this driver brings their door-release and access-event API into GEM so a property's lobby / package-room / amenity doors can be triggered from macros, scenes, and access workflows.
What this integration covers
- Door release ("buzz in") from a GEM macro or zone command — drives the BMX cloud's
open endpoint for any door tied to your partner credentials.
- Door roster polling — pulls the list of doors visible to the API key on a configurable cadence and reflects them as GEM zones.
- Access event tail — walks the recent access-log feed and surfaces last-event metadata (event type, timestamp, who released) onto each door zone.
- Building scoping — if your partner credentials span multiple properties, the optional
building_id attribute scopes a single GEM device row to one building.
What this integration does NOT cover
- SIP audio / video preview of a panel call. ButterflyMX panels speak SIP through their own iOS/Android/web clients; integrating that path requires a SIP UA stack and is out of scope here.
- Resident / tenant provisioning (creating user accounts, mailing PINs, mailing physical keys). The BMX OS admin portal is the right tool for that.
- Door schedule editing (auto-unlock windows). The driver reads scheduling state via
get_doors but does not write to it.
Prerequisites
- A ButterflyMX partner-tier account with API access enabled. Resident / front-desk credentials do not have API scope — contact your ButterflyMX rep.
- A
client_id and client_secret pair minted in the BMX partner portal.
- Outbound HTTPS reachability from the GEM controller to:
accounts.butterflymx.com (OAuth 2.0 token endpoint)
api.butterflymx.com (REST API)
Setup steps
- Have your ButterflyMX partner contact provision API access for the property and mint a
client_id + client_secret pair.
- In GEM admin → Devices → New Device, pick ButterflyMX Intercom as the driver.
- Enter the
client_id and client_secret on the device row (the secret is encrypted at rest).
- If your credentials span multiple buildings and you want this device row scoped to one, set
building_id to the BMX building UUID. Leave blank to include every visible building.
- Save the device row. After ~10 seconds the device should show as connected.
- From the Commands tab, run
get_doors to enumerate available doors. Each entry's id is what you'll use as the zone address.
- Create a zone per door you want to control (zone subsystem
gate or door makes the most sense; door is the default mapping). zone.address = the door's BMX UUID.
Attribute reference
Device attributes
| Name | Type | Required | Description |
|---|
client_id | string | yes | OAuth 2.0 client_id from the BMX partner portal. |
client_secret | string | yes | OAuth 2.0 client_secret. Stored encrypted. |
api_base | string | no | Override the BMX API root. Default https://api.butterflymx.com. Only set if BMX has directed you to a regional or staging endpoint. |
token_url | string | no | Override the OAuth2 token endpoint. Default https://accounts.butterflymx.com/oauth/token. |
building_id | string | no | Building UUID filter when the credentials span multiple properties. |
status_interval | int | no | Door roster poll cadence in milliseconds (default 60000, minimum 15000). |
events_interval | int | no | Access-event feed poll cadence in milliseconds (default 30000, 0 disables). |
request_timeout | int | no | Per-request timeout (default 15000 ms). |
Zone attributes
| Name | Type | Description |
|---|
address | string | The BMX door UUID (required). |
released_state_ms | int | How long zone.state stays "released" after an unlock before clearing back to "idle". Does NOT change the strike duration — that's set on the door in the BMX portal. Default 5000 ms. |
state | string | Maintained by the driver. "idle" normally, "released" for released_state_ms after an unlock. |
last_event | string | Most recent access event type for this door. |
last_event_at | string | ISO timestamp of the most recent access event. |
last_event_user | string | Display name of the actor on the most recent access event, if BMX surfaced one. |
Plain BMX door UUID, e.g. 5f3a1c2e-8b9d-4f7a-b6e2-1a2b3c4d5e6f. Get the list with the get_doors command after first connect.
Commands
| Command | Args | Notes |
|---|
unlock | address | Release / open the door. Strike duration is set on the BMX side. |
open | address | Alias for unlock. |
get_buildings | — | List buildings visible to the credentials. |
get_doors | — | List doors. Use this to populate zone addresses. |
get_panels | — | List intercom panels (the hardware door stations). |
get_status | — | Force a status tick and return the cached door roster. |
get_events | optional since | Walk the recent access-log feed. |
refresh_token | — | Force a fresh OAuth2 token (debug). |
Known limitations
- Strike duration is not configurable from GEM. ButterflyMX owns that — set it on the BMX side per door.
- No long-held / always-unlocked state. Most BMX doors are momentary; if a door is configured for an extended hold via BMX's own schedule, the driver does not currently surface that as a distinct zone state (it'll just report
idle between release events).
- Unlock endpoint is partner-tier. ButterflyMX has versioned this path in the past. If
unlock returns a 404 on a brand-new partner contract, confirm the current path with your ButterflyMX rep — the driver will continue to function for read-only polling regardless.
- No SIP audio/video (see "What this integration does NOT cover").
Troubleshooting
token request failed: HTTP 401 — client_id or client_secret is wrong, or the partner account doesn't have API scope enabled. Re-mint the credentials in the BMX portal.
HTTP 403 on /v3/doors — credentials are valid but the partner account doesn't have door-read scope. Contact your BMX rep to widen the scope.
HTTP 404 on unlock — the BMX unlock endpoint changed shape for your contract tier. Open a ticket with BMX to confirm the current path; read-only polling will keep working in the meantime.
HTTP 429 — the cloud is rate-limiting. Increase status_interval and events_interval.
- Zones never transition past
idle — make sure zone.address matches a UUID from the get_doors output verbatim. Address fields in the BMX API are case-sensitive.
- Device shows disconnected after a few hours — token refresh failed. Check that
accounts.butterflymx.com is reachable from the GEM controller; the driver auto-refreshes ~5 minutes before token expiry.