Kisi (Cloud)
Kisi is a commercial cloud access-control platform commonly deployed in offices, coworking, gyms, apartment lobbies, and mixed-use buildings. This driver talks to the Kisi HTTP API at api.kisi.io to unlock doors, enumerate places and members, and surface the event feed to GEM zones. Credential provisioning, group and role CRUD, and floor-plan editing stay in the Kisi Web app — GEM only reads the roster and issues unlock requests.
Prerequisites
- A Kisi account with admin (or per-place manager) rights on every door you plan to control from GEM. The driver inherits the account's place and role scope — an API key minted by a receptionist-tier user will only see the doors that account already sees.
- An API key generated in Kisi Web → your name → Account → API Keys → New API Key. The secret is shown once at creation. Treat it like a password; store it in a password manager before pasting into GEM.
- Doors already provisioned in Kisi (reader hardware paired to a Place and Group). Only online doors can be unlocked via the API; wireless-only Kisi Card locks and offline hardware are not part of Kisi's REST surface and are out of scope for this driver.
Setup
- Mint the API key. In the Kisi Web app (
https://web.kisi.io), click your name in the top-right corner → Account → API Keys → New API Key. Give it a descriptive label like "GEM Controller —". Copy the secret. - Add the device in GEM. Go to Admin → System → Devices, click New Device, and pick the driver Kisi. Give the device a friendly name (e.g. "Kisi — HQ").
- Paste the credential. Fill in the Kisi API Key attribute. It is stored encrypted at rest via GEM's secure-attribute mechanism.
- (Optional) If the API key spans multiple places and you only want GEM to see one, set the Place ID (filter) attribute to that place's integer id. You can find place ids by running
get_places. - Save the device. Watch the log for
kisi cloud connected: <id> <name> N doors reachable. If it says0 doors reachable, the API key's underlying account is not on any place — go back to Kisi Web and grant the required group membership. - Enumerate doors. Run the
get_doorscommand (Devices → the Kisi row → Send Command →get_doors). The response lists each door'sid,name,place, andlockreference. - Create a zone per door. In Admin → System → Zones, add one zone per door you want to control. Set:
- Device to the Kisi row you just created.
- Subsystem to
access_control(ordoor). - Address to the Kisi door id (integer, e.g.
12345).
- Verify unlock. From the zone tile in a UI, or Devices → the Kisi row → Send Command → unlock with
address = <door_id>, trigger a live unlock. The door's strike should chirp within a second.
Attribute Reference
Device attributes
| Attribute | Required | Description |
|---|---|---|
api_key | yes | Kisi Static Secret / API key. Secure — stored encrypted. |
place_id | no | Optional Kisi place id to scope roster and event polls. Leave empty to see everything the key can reach. |
api_base | no | Override the base URL. Almost always https://api.kisi.io. |
status_interval | no | Door-roster poll cadence in ms. Default 60000. Kisi enforces per-account rate limits; keep at 60s+ on large orgs. |
event_poll_interval | no | Event-feed poll cadence in ms. Default 30000. Set to 0 to disable event polling entirely. |
unlock_seconds | no | Advisory duration passed with the unlock request. Kisi controllers ultimately honor the strike-relay hold configured on the reader hardware. Default 5. |
released_state_ms | no | How long zone.state stays released after an unlock before reverting to locked. Default 5000. |
Zone attributes (populated by the driver)
| Attribute | Description |
|---|---|
state | locked (default) or released for the strike-hold window immediately after an unlock. |
online | Boolean — whether the reader is reporting to Kisi cloud. Sourced from the door roster. |
place_name | Friendly place label from Kisi. Handy for admin UI disambiguation. |
last_event | Most recent event type on this door (e.g. unlock_granted, door_forced, access_denied). |
last_event_at | ISO timestamp of last_event. |
last_event_user | Name or email of the actor associated with last_event, when Kisi reports one. |
Zone Address Format
zone.address is the Kisi door id — always an integer. You can find it either by:
- Selecting a door in the Kisi Web app and reading the URL:
.../places/<pid>/doors/<door_id>. - Running the
get_doorscommand from GEM and copying theidfield.
Do not use the lock id here. The driver resolves lock ids from the door id on connect (and refreshes on cache miss), so all commands are addressed by door.
Commands
| Command | Args | Description |
|---|---|---|
unlock | address (door id) | Issues an unlock request. Zone state transitions locked → released → locked for released_state_ms. |
get_doors | optional limit | Enumerates doors reachable to the API key. Respects place_id. |
get_places | optional limit | Lists Kisi places (locations). |
get_members | optional limit | Lists members visible to the API key. Respects place_id. |
get_events | optional limit, since_id | Tails the event feed. Watermarked by highest seen id. |
get_status | address | Fetches a single door's Kisi record (name, place, lock reference, online status). |
refresh | none | Force-refresh the door → lock mapping cache. Rarely needed; the driver refreshes on cache miss automatically. |
Known Limitations
- No programmatic lock. Kisi's model is auto-relock on strike-hold expiry. GEM cannot force a door back to
lockedbefore the hardware timer expires — this driver only exposesunlock. - No door-position sensor. Kisi's REST API does not report a physical open/closed contact by default. The driver seeds
zone.state = lockedand toggles it toreleasedon unlock, but does not know if a person actually opened the door. For true door-open telemetry, wire the reader's DPS contact into a relay/BACnet/Modbus point and mirror that into a separate GEM zone. - Polled events, not pushed. Kisi does not expose a webhook or push channel accessible without a paid Enterprise contract. The event loop polls; tune
event_poll_intervalper your rate-limit envelope. - Credential and group management out of scope. Adding, removing, or role-scoping members should be done in Kisi Web. GEM only reads
get_membersfor visibility. - Rate limit ~120 req/min per key. Combined default polls (60s roster + 30s events) use well under this. On very large sites with many doors, raise the intervals.
Troubleshooting
Symptom: 401 unauthorized at connect.
- The API key is stale or was truncated on paste. Regenerate it in Kisi Web → Account → API Keys and re-paste. Confirm the underlying account has not been suspended.
Symptom: get_doors returns zero entries.
- The API key's user has no place membership. In Kisi Web → Places → Groups, add the user to the group that includes the doors, or generate the key from an admin-tier account instead.
Symptom: unlock returns kisi 403.
- The API key's user is not on the door's access group. Grant access in Places →
→ Groups → .→ Members
Symptom: unlock returns door <id> not found or has no lock associated.
- The address does not match any door in the roster, or the door has been removed from Kisi. Run
get_doorsto list current door ids. If the door is a wireless Card lock without an online controller, it is not accessible via the REST API.
Symptom: zone.state sticks at released.
released_state_mswas set to0or a very large value, so the timer never fires. Raise it to5000(5 seconds), the physical strike default. Alternatively, verify that the event loop is running and that the door's follow-up event (unlock_granted) is being observed.
Symptom: Event feed is delayed by minutes.
- Kisi does not push. Lower
event_poll_intervalto 10000 (10 seconds) if your rate-limit envelope allows it. Rate-limited responses return a429; the driver surfaces the raw error in that case.
Also see
- Menu-level protocol notes: Access Control
- Sibling access-control drivers: Salto KS, UniFi Access, Verkada Command, Brivo