Skip to main content

Seam API

Cloud-to-cloud integration with Seam, which fronts a wide roster of smart-lock vendors (August, Yale, Schlage, Salto, SmartThings, etc.) behind a single REST + events API. Use this when the integrator already pays for Seam, or when one workspace controls a mixed fleet that GEM would otherwise need several vendor drivers for.

What's controlled​

  • Remote lock / unlock — the SDK auto-polls Seam's action_attempts until the lock physically confirms, so commands return real success or a descriptive failure, not fire-and-forget.
  • refresh — pull fresh lock state for every zone immediately.
  • clear_tamper — clear the zone's tampered flag (Seam does not always emit a "tamper resolved" event).
  • Access-code workflows — create_access_code, list_access_codes, update_access_code, delete_access_code, generate_code. Supports ongoing codes, time-bound codes (starts_at + ends_at), and one-time codes.
  • pull_events — fetch recent Seam events for the managed locks. Does not advance the internal auto-poll cursor.

Prerequisites​

  • A Seam workspace at console.seam.co and an API key with permission to read/manage your locks.
  • The target locks already enrolled in the manufacturer app and visible in the Seam dashboard with non-empty device_id UUIDs.
  • Outbound HTTPS to seam.co. No inbound ports.

Setup​

  1. Mint a Seam API key in the Seam console.
  2. Add a device with driver seamapi and paste the key into api_key.
  3. Create one zone per lock under a lock / security subsystem. Set zone.address to the Seam device_id (UUID, case-sensitive).
  4. Reload the device. Zone attributes populate on the next poll.
  5. For access-code workflows, drive create_access_code / list_access_codes etc. from a macro or the Script Console.

Attributes​

Device — required​

NameTypeDescription
api_keystring (secure)Seam workspace API key.

Device — optional​

NameTypeDefaultDescription
status_intervalint (ms)5000How often to poll Seam for lock state. Floor is 2000 ms.
poll_eventsboolfalseWhether to also poll Seam's events stream for state-change events between snapshots.
low_battery_thresholdint (%)20low_battery is set true when battery_level falls below this. Re-evaluated every poll, so threshold edits take effect on the next tick.

Zone — address​

zone.address is the Seam device_id UUID exactly as shown in the Seam dashboard, e.g. 1a2b3c4d-5678-90ab-cdef-1234567890ab.

Zone — read-only attributes​

AttributeTypeDescription
statestringlocked or unlocked.
battery_levelintBattery percentage 0–100 (Seam reports a 0.0–1.0 float; the driver scales it).
battery_statusstringManufacturer category: full, good, low, critical.
low_batteryboolTrue when battery_level is below low_battery_threshold.
onlineboolLock currently reachable by Seam.
tamperedboolSet true by Seam's device.tampered event. Cleared via clear_tamper.
device_errorsstringComma-separated Seam error_codes currently active on the lock (e.g. device_offline,hub_disconnected).
supported_code_lengthsstringComma-separated digit counts the lock accepts for access codes (e.g. 4,5,6,7,8). Used by create_access_code for pre-validation.
max_active_codesintMax simultaneous access codes the lock supports.

Commands​

NameArgsNotes
lockaddressReturns {success, state, action_attempt_id} on confirm, or {error, failure_type} / {error, timeout: true} on a Seam failure / timeout.
unlockaddressSame shape as lock.
refresh—Re-pull every managed lock and (if enabled) drain the event queue.
clear_tamperaddressSet tampered back to false on a zone.
create_access_codeaddress, name, code, starts_at, ends_at, is_one_time_use, preferred_code_lengthOmit code to let Seam auto-generate a compatible value. If code is provided, the driver pre-validates its length against the zone's supported_code_lengths.
list_access_codesaddressReturns all codes currently provisioned on the lock.
update_access_codeaccess_code_id, name, code, starts_at, ends_atOnly pass the fields you want to change.
delete_access_codeaccess_code_idRemove by Seam access-code id.
generate_codeaddressAsk Seam for a code that matches the lock's constraints, without provisioning it.
pull_eventssince_minutes (default 60)Fetch recent events for the managed locks. Does not advance the auto-poll cursor — safe to call ad-hoc for debugging. Returns {error} if Seam could not be reached.

How polling and events fit together​

The driver maintains its own poll loop (no webhooks). Each tick lists every lock in the workspace, applies the snapshot to the matching zone, and — if poll_events is enabled — drains Seam's events stream since the last cursor. Events are deduped against a 500-entry in-memory ring so overlapping windows don't double-fire, and are applied oldest-first so the zone ends on the latest state. If the events fetch fails, the lock snapshot still counts and the cursor stays where it was, so the missed window is read on the next tick.

On error the driver backs off exponentially up to 60 s (never faster than status_interval), then resumes the normal cadence once a poll succeeds. When Seam rejects the API key (401/403) it retries every 5 minutes instead. The device's connected flag follows the latest poll result. A repeating failure is logged once, not on every retry.

Every request to Seam has a timeout (30 s; 60 s for lock / unlock, which wait for the lock to confirm). A request that never answers fails the poll or the command rather than stalling it.

Subscribed event types​

The events poller scopes itself to the event types the driver actually reacts to, to keep Seam quota usage low:

lock.locked, lock.unlocked, lock.access_denied, device.connected, device.disconnected, device.connection_became_flaky, device.connection_stabilized, device.tampered, device.low_battery, device.battery_status_changed, device.removed, device.deleted, action_attempt.lock_door.failed, action_attempt.unlock_door.failed, access_code.set_on_device, access_code.removed_from_device, access_code.failed_to_set_on_device, access_code.failed_to_remove_from_device.

Known quirks​

  • Cloud round-trip adds 1–5 s latency on lock/unlock. The SDK auto-polls Seam until the action attempt resolves, so a successful return means the lock truly moved.
  • Seam's battery_level is a 0.0–1.0 float; GEM scales it to a 0–100 integer.
  • tampered is set by device.tampered but Seam does not emit a corresponding "tamper resolved" event for every vendor — clear with clear_tamper.
  • The driver does not use webhooks; everything is pull-based.

Troubleshooting​

SymptomCheck
Zone state never updatesConfirm zone.address matches the Seam device_id exactly (UUID, case-sensitive). Locks with no matching zone are skipped silently, so an address typo produces no warning.
Commands return invalid zone for addressThe zone address and Seam device_id must match. If you changed the address, reload the device.
Lock shows offline but works from the Seam appRun the refresh command. If persistent, inspect the zone's device_errors attribute for Seam's reason.
create_access_code rejects the code lengthSome locks only accept specific lengths (4–8 digits typical). Omit code to let Seam pick one, or read the zone's supported_code_lengths attribute.
tampered stays true after you cleared the alarmRun clear_tamper on that zone.
lock/unlock returns {error, timeout: true}The Seam SDK waited for confirmation and never got one, or Seam itself did not answer within 60 s. The lock is likely offline or the hub is unreachable. The lock may still move later.
Device offline, log says seam poll error: UnauthorizedSeam rejected the API key. Replace api_key (or re-enable the workspace) and reload the device. Until then the driver retries every 5 minutes.

See also​