Verkada Command
Verkada Command cloud REST API — surfaces the camera and access-door rosters for a Verkada org, polls door lock state, follows the events feed for access activity, and offers remote admin-unlock for doors plus on-demand HLS and thumbnail links for cameras.
One GEM device row = one Verkada org (one API key). One zone per Verkada device you want to surface — a door, a camera, or both.
Prerequisites
- Verkada Command admin access for the org you want to integrate.
- An API key minted in Admin → API with the scopes you intend to use:
Cameras: Read— forget_cameras,get_camera_link,get_thumbnail.Access: Read— forget_doors, door state polling, the events feed.Access: Write— forunlock(admin pulse) andlock.
- Outbound HTTPS to
api.verkada.com(or your tenant's API base for GovCloud installs). No inbound firewall changes are required.
Setup
- Mint an API key. In Command go to Admin → API → "+ Create API Key". Choose only the scopes you need. Copy the key immediately — Verkada does not show it again.
- Create the GEM device. Admin → Devices → "+ Device". Driver
verkada_command. Paste the key intoapi_key. Subsystemaccessorsecurityis typical when control is in scope;camerais fine for read-only camera surfaces. - Save and reload. The driver smoke-tests both rosters and logs e.g.
verkada command connected: <id> <name> 12 cameras 4 doors. A(no access scope)/(no camera scope)note means the key is missing the corresponding scope — fix in Command, no GEM change needed. - Discover devices.
await GemApp.getInstance().command({device: <id>, action: 'get_doors'});await GemApp.getInstance().command({device: <id>, action: 'get_cameras'});
- Create one zone per device you care about:
- Access door:
zone.address= the door UUID fromget_doors. Subsystemaccessorsecurity. - Camera:
zone.address= the 16-char hexcamera_idfromget_cameras. Subsystemcamera.
- Access door:
- If you ever hit a collision (the same id appearing in both rosters), set the zone-level
verkada_typeattribute todoororcamerato lock in the routing.
Attribute Reference
Device
| Attribute | Required | Description |
|---|---|---|
api_key | yes | The Verkada Command API key. Stored encrypted. |
api_base | no | Override the API base URL — set this only for GovCloud / dedicated tenants. Default https://api.verkada.com. |
status_interval | no | Door-roster poll interval in ms. Default 60000. |
event_poll_interval | no | Access-events poll interval in ms. Default 30000. Set to 0 to disable event polling entirely. |
enable_event_polling | no | When false, skips the events loop even if event_poll_interval > 0. Default true. |
unlock_seconds | no | Default unlock pulse duration when the unlock command is called without an explicit duration. Verkada caps at 60 s. Default 5. |
request_timeout | no | HTTPS request timeout in ms. Default 15000. |
Zone
| Attribute | Description |
|---|---|
verkada_type | Optional hint — door or camera. Only needed when auto-detection fails. |
Zone State Attributes (written by the driver for door zones)
| Attribute | Description |
|---|---|
state | Coarse state from lock_state — locked, unlocked, open, closed, or the raw string when Verkada returns a value the driver doesn't yet map. |
lock_state | Raw lock state from the API. |
lock_status | Raw lock status string when present (some firmware exposes this separately from lock_state). |
online | Boolean — is the access controller reporting in. |
site_name | The Verkada site this door belongs to. |
schedule_active | Boolean — is the door currently following a Command schedule. |
last_event | Most recent event type seen by the events loop (e.g. door.access_granted, door.forced_open). |
last_event_at | Unix timestamp of the most recent event. |
last_event_user | User name from the most recent event, if present. |
Zone Address Format
- Door zones: the Verkada door UUID (looks like
12345678-90ab-cdef-1234-567890abcdef). - Camera zones: the 16-char hex
camera_id(e.g.a1b2c3d4e5f60718).
Commands
| Command | Args | Notes |
|---|---|---|
get_cameras | — | List all cameras visible to the API key. |
get_doors | — | List all access doors with lock state and online status. |
get_status | address | Refresh and return current state for one zone. Auto-detects whether it's a door or camera. |
unlock | address, duration | Admin pulse-unlock a door. duration is 1-60 seconds (default 5). Logged in Command audit as "Admin Unlock". |
lock | address | Send a lock command to a door. Doors under a Command schedule will reject this — adjust the schedule in Command instead. |
get_events | since | Fetch access events newer than the supplied unix timestamp. Defaults to the last 5 minutes. |
get_camera_link | address | Return a short-lived HLS playback URL for one camera (Verkada expires these in ~15 min). |
get_thumbnail | address | Return a JPEG thumbnail URL for one camera. |
refresh | — | Force an immediate door-roster + events sweep. |
Known Limitations
- Camera control surface is read-only. PTZ, recording controls, and audio talk-down are not exposed by the public API and so are not implemented here.
- Lock semantics depend on the door's Command schedule.
lockwill return a vendor error on doors that are under aLockedorUnlockedschedule block; in those cases adjust the schedule directly in Command. - Admin-unlock attribution. Verkada audit logs show the unlock as "Admin Unlock" performed by the API key name, not by an end user. If end-user attribution matters, the call needs to go through the user-mode unlock endpoint, which requires a per-user OAuth token rather than the org-level API key — out of scope for this driver.
- Events watermark drift. The driver tracks the last event timestamp it consumed and only asks for newer events on subsequent polls. A long disconnect will leave the watermark stale; on reconnect, the first sweep may pull a large batch.
- Camera footage links are ephemeral.
get_camera_linkreturns signed URLs that expire (~15 min). Don't cache them; re-fetch when needed. - GovCloud / FedRAMP tenants. Set
api_baseto the correct hostname for those deployments. The defaultapi.verkada.comwill return 401/404 against the wrong tenant. - API key scopes are silent. A key missing a scope returns 403 from the call that needs it, not at key creation. The driver logs the HTTP status and the body so missing-scope errors are easy to identify.
Troubleshooting
http 401on every call —api_keyis wrong, revoked, or expired. Re-mint in Command.http 403on a specific endpoint — the key lacks the scope needed for that endpoint. Edit the key in Command and add the scope.http 429— You're being rate-limited. Raisestatus_intervalandevent_poll_interval, and avoid hammeringrefresh.unknown address; run get_doors or get_cameras— The zone address isn't in either cached roster, and auto-detection failed. Either runget_doors/get_camerasto refresh the cache, or setverkada_typeon the zone explicitly.lock not supported by this api key / door — adjust schedule in command— Either the API key lacksAccess: Write, or the door is on a schedule that owns lock state. Both are configured in Command.- No events appear after enabling event polling — Either the API key lacks
Access: Read, or the org has no recent activity. Bumpsincefurther back viaget_events {since: <unix_ts>}to confirm.
Verification Status
This driver was authored against Verkada's public developer documentation (apidocs.verkada.com) and community references. The following call shapes are documented and used as-is; live verification on a Verkada Command tenant is recommended before production deployment:
GET /cameras/v1/devices,GET /cameras/v1/footage/link,GET /cameras/v1/footage/thumbnails.GET /access/v1/doors,POST /access/v1/door/admin_unlock(body{door_id, duration_seconds}).GET /events/v1?start_time=<unix>&limit=100.
POST /access/v1/door/lock is documented inconsistently across Command builds; the driver detects a 404 and surfaces a friendly error so you can switch to a Command schedule instead.