Salto KS
Cloud control for Salto KS — Salto's cloud-managed commercial access-control platform — over the public Connect API. The driver performs online unlock / lock, lock-roster and battery reads, user enumeration, and audit-feed polling for last-event surfacing. Each online lock is exposed as a GEM zone you create by hand.
Online locks only. Offline-only hardware (classic XS4, or an IQ Door / Neo Cylinder without the online module) is programmed through the Salto Space client and is out of scope here. Salto SIP video stations are also out of scope — use a dedicated intercom driver (e.g.
aiphone_ix,butterflymx,comelit_vip) for door-station video.
Prerequisites
- A Salto KS subscription with the Connect API add-on enabled by Salto support. The API is not exposed on a stock KS account — submit a request through your Salto rep before commissioning.
- A client_id / client_secret pair minted in the KS portal under Account → API Credentials. Note the scopes granted; this driver assumes at minimum:
locks:readlocks:writeusers:readaudit:read
- Online locks (XS4 One, Neo Cylinder with online module, IQ Door on the online tier). Pure offline locks are still returned by
get_locks, butunlockagainst them fails with a Salto-side error.
Setup
- In the Salto KS portal → Account → API Credentials, create a new credential pair. Copy both the client_id and client_secret.
- Add the device. Go to System → Devices → Add Device and pick the Salto KS driver (key
salto_ks). - Enter credentials. Paste the client_id into the Client ID field and the client_secret into the Client Secret field. (Those two fields are the
username/passwordattributes under the hood; the dedicatedclient_id/client_secretattribute names also work if you prefer to enter them directly.) - Endpoint (usually leave default). Most tenants — EU and US included — share the default
clp.my-clay.comhost, so leave API Base URL and OAuth Token URL at their defaults. Override them only if Salto has placed your tenant on a different host, and then enter the exact base URL and token endpoint Salto gave you. - Resolve the site. Run
get_sites, copy the site UUID you want to manage, and paste it into the Site ID field. Single-site accounts are auto-detected — when only one site is visible the driver fillssite_iditself on first connect, so you can leave it blank. - Create one zone per lock. Run
get_locksto enumerate doors. For each online lock, create a Zone whose Address is the Salto lock UUID exactly as returned. Zones are not auto-created — the driver only writes state onto zones you create and address yourself (see the subsystem note below). - Verify. Run
get_statuswithaddress=<lock-uuid>for one zone to confirm the token flow and per-lock connectivity end to end.
Salto lock zones are not auto-created and the driver does not pre-select a subsystem for you. Its subsystem hints (access_control / door / lock) don't match any of GEM's default-seeded subsystems, so the New Zone editor leaves the Subsystem field empty — pick one by hand. Doors is the natural home (it's seeded by default and groups door/lock zones); Security is a reasonable alternative if you keep all access hardware together. The choice drives grouping and iconography only — lock state is written directly by the driver, not derived from relay-to-state mapping.
Device attributes
| Attribute (key) | Field label | Default | Notes |
|---|---|---|---|
username (or client_id) | Client ID | — | OAuth2 client_id from the KS portal. Required. |
password (or client_secret) | Client Secret | — | OAuth2 client_secret. Stored encrypted. Required. |
site_id | Site ID | — | Salto site UUID. Required for multi-site accounts; auto-filled on first connect when only one site is visible. |
api_base | API Base URL | https://clp.my-clay.com/v1.1 | Resource API base. Override only for a non-default regional host. |
auth_url | OAuth Token URL | https://clp.my-clay.com/oauth2/token | OAuth2 token endpoint. Override only for a non-default regional host. |
status_interval | Lock-roster Poll (ms) | 60000 | Lock-roster + battery sweep cadence. Minimum 15000. Mind tenant rate limits. |
event_poll_interval | Audit Poll (ms) | 30000 | Audit-feed cadence. Set 0 to disable the audit loop entirely. |
unlock_seconds | Default Unlock Duration (s) | 5 | Release duration when unlock is called without a seconds argument. Clamped 1–60. |
released_state_ms | Released State Hold (ms) | 5000 | How long zone.state stays released after an unlock before reverting to locked. Set 0 to leave it released (no auto-revert). |
The driver caches the OAuth bearer token, refreshes it about a minute before expiry, and on a 401 force-refreshes and retries the request once — so a credential rotation in the portal is picked up without a device reload.
Zone attributes
| Attribute | Field label | Required | Notes |
|---|---|---|---|
address | Address | yes | The Salto lock UUID, copied verbatim from get_locks. One zone per online lock; the driver matches a zone to its lock by this value alone. |
salto_type | Salto Object Type | no | Declared (lock / door) for forward compatibility; the current driver ignores it and keys zones purely by address. Leave it unset. |
As state arrives, the driver writes the following onto each lock zone:
online— boolean; whether the lock is currently reachable on the Salto cloud.battery_level— battery percentage (0–100).state—locked(default) orreleased(set the moment anunlockis accepted, reverting tolockedafterreleased_state_ms).
When the audit loop is enabled (event_poll_interval > 0) it additionally writes:
last_event— the most recent event type / action string for that lock.last_event_at— ISO timestamp of that event.last_event_user— user name or email, when the audit row carries one.
These six names are cataloged, so they autocomplete in the Attribute editor's name picker and can drive attribute triggers — for example, notify when a lock's battery_level drops below 20, when online flips to false, or when last_event records a denied/forced entry.
Commands
| Command | Arguments | Notes |
|---|---|---|
unlock | address, optional seconds (1–60) | Online release for unlock_seconds (or the seconds arg). Sets zone.state = released, reverting to locked after released_state_ms. |
lock | address | Programmatic lock. Not supported on every hardware revision — older XS4 strikes return 422. |
get_sites | — | Sites visible to this credential. |
get_locks | — | Lock roster for the configured site. |
get_users | — | User roster for the configured site. |
get_status | address | Per-lock detail: battery, online, last event. |
get_events | optional limit (default 50, max 200), since (ISO timestamp) | Tail the audit feed directly. |
refresh_token | — | Discard the cached bearer and re-authenticate. |
unlock and lock carry the lock UUID as their address argument; when you fire them against a zone (from a macro, trigger, scene, or a UI control) the zone's address fills it automatically.
Known limitations
- Offline-only locks cannot be unlocked over the API. They appear in
get_lockswithonline=false; callingunlockreturns a Salto-side error. lock(programmatic lock) is hardware-dependent. Older XS4 strikes do not implement it and return 422.- No event push. The Connect API does not push; both lock state and last-event surfacing rely on polling. Tune
status_intervalandevent_poll_intervalto your tenant's rate limit. - Audit endpoint variation. Some legacy KS tenants expose the audit feed at
/audit/loginstead of/audit-trails; the driver falls back automatically on a 404, so bothget_eventsand the audit loop keep working. - User management is read-only. Credential issuance, schedule writes, and access-rule changes are intentionally not exposed — they belong in the Salto KS portal, where the audit chain is preserved.
Troubleshooting
| Symptom | Check |
|---|---|
oauth token failed (status 401) | client_id or client_secret is wrong, or the credential was disabled in the KS portal. Re-mint and update the Client ID / Client Secret fields. |
oauth token failed (status 400) | The Connect API add-on is probably not enabled on this tenant. Contact Salto support. |
salto ks 403 | The credential is valid but lacks the scope for that endpoint. Re-mint with the scopes listed under Prerequisites. |
salto ks 422 on unlock | The lock is offline or the hardware doesn't support the operation. Confirm with get_status. |
| multi-site account, wrong/empty data | More than one site is visible, so nothing is auto-selected. Run get_sites and set Site ID explicitly. |
| no events appearing | Confirm Audit Poll (ms) is greater than 0 and the credential has audit:read. If get_events returns rows but zone.last_event never updates, confirm the zone Address matches the lockId in the audit payload exactly. |
Related documentation
- Zones — create one zone per online lock (place them in the Doors or Security subsystem).
- Subsystems — the subsystem these lock zones belong to.
- Devices — add and configure the Salto KS cloud connection.
- Commands — the command grid and Test runner for
unlock,lock, and theget_*queries.