State Machines
Every Bikeep device has a state that tells you what the device is currently doing. Commands change the state. Understanding this state machine is the key to building a correct integration — it tells you which commands are valid, what to expect, and when to poll for updates.
Device States
| State | Intuitive Meaning | Description |
|---|---|---|
UNLOCKED |
Available / Ready | For docks — the lock arm is open and can be moved freely. For lockers — the locker is physically closed but accepts commands to initiate parking. For doors — the door lock is released (door can be opened). |
BOOKED |
Reserved | Reserved for a specific user; will revert to UNLOCKED on timeout |
LOCKING |
Securing | Transitioning to locked — command sent, waiting for hardware confirmation |
LOCKED |
In use / Secured | For docks — bike is secured by the lock arm. For lockers — locker is actively in use (session in progress). For doors — the door lock is engaged; this is the door’s ready/available state (inverted logic). |
UNLOCKING |
Releasing | Transitioning to unlocked — command sent, waiting for hardware confirmation |
ALLOCATED |
Command in flight | Device assigned to a user in the backend, awaiting hardware confirmation |
Tip: For availability display, prefer the
devices.availablefield from the location endpoint (GET /locationsorGET /public-areas/{id}/locations) — it counts devices that are both available AND online, so you don’t need to compute this yourself. If you need per-device logic: for most device types (LOCKER,BIKE_DOCK,SCOOTER_DOCK, etc.),state.value: "UNLOCKED"means available. Note:BIKE_HOUSE_DOORuses inverted logic — it is available whenLOCKED(normally-closed device).GUARDdevices are always considered available.
Note:
ALLOCATEDmeans the device has been assigned to a user in the backend, but the hardware hasn’t confirmed yet. It typically resolves toLOCKING/BOOKEDwithin seconds, or reverts toUNLOCKEDon timeout. The backend attempts automatic sync, but this can fail due to flaky connectivity.If
ALLOCATEDpersists: the device did not answer, but its latest heartbeat was still fresh enough that the backend allowed the command to be sent. Checkheartbeat_v2.health— ifDEADorLONG_DEAD, the device is offline and the command won’t complete. Retry the command once the device is back online, or contact Bikeep support if the state doesn’t resolve.
Note: Maintenance and out-of-service status is tracked at the location level via the
location_statusfield (e.g.,MAINTENANCE), not as a device state. See Location Types for location status values.
Command-to-State Transitions
This table shows every valid state transition triggered by a command:
| Current State | Command | Next State | Final State | Notes |
|---|---|---|---|---|
UNLOCKED |
book |
BOOKED |
BOOKED |
Remains booked until timeout_at or cancellation |
BOOKED |
cancel-booking |
UNLOCKED |
UNLOCKED |
|
BOOKED |
(timeout) | UNLOCKED |
UNLOCKED |
Automatic — booking expired |
BOOKED |
lock |
LOCKING |
LOCKED |
User checks in to their reserved device |
UNLOCKED |
lock |
LOCKING |
LOCKED |
Direct lock without prior booking |
LOCKING |
unlock |
UNLOCKING |
UNLOCKED |
User cancels before hardware confirms lock |
LOCKED |
unlock |
UNLOCKING |
UNLOCKED |
Ends the session (full lifecycle), device becomes available |
Important behaviors
-
lockandbookcommands initially set the device toALLOCATED— a brief intermediate state indicating the device has been assigned to a user but hardware hasn’t confirmed yet.ALLOCATEDresolves toLOCKING/BOOKEDwithin seconds, or reverts toUNLOCKEDon timeout. You do not need to act onALLOCATED— treat it as “command in progress.” -
Booking timeout defaults to 1 hour if you omit
timeout_atin thebookcommand. After timeout, the device automatically returns toUNLOCKED. -
LOCKINGandUNLOCKINGare transient states. These last only as long as it takes for the hardware to confirm (typically 1–5 seconds). You should never need to act on these states — just poll until the device reachesLOCKEDorUNLOCKED.
Locker State Diagram
stateDiagram-v2
[*] --> UNLOCKED
UNLOCKED --> BOOKED : book
UNLOCKED --> ALLOCATED : lock
BOOKED --> ALLOCATED : lock
BOOKED --> UNLOCKED : cancel-booking / timeout
ALLOCATED --> LOCKING
LOCKING --> LOCKED : hardware confirms
LOCKED --> UNLOCKING : unlock
UNLOCKING --> UNLOCKED : session ends
class UNLOCKED available
class BOOKED reserved
class ALLOCATED transient
class LOCKING,LOCKED active
class UNLOCKING active
Entrance Door (BIKE_HOUSE_DOOR) Behavior
BIKE_HOUSE_DOOR devices alternate between LOCKED and UNLOCKING. They support both RFID card access and the API unlock command:
stateDiagram-v2
[*] --> LOCKED
LOCKED --> UNLOCKING : RFID tap or unlock command
UNLOCKING --> LOCKED : door closes + timeout
class LOCKED available
class UNLOCKING active
Inverted logic: For BIKE_HOUSE_DOOR,
LOCKEDis the ready/idle state (door secured, accepting commands), andUNLOCKINGis the active state (door temporarily open). This is the opposite of lockers and docks, whereUNLOCKED= ready. When checking availability, a door inLOCKEDstate is operational and waiting for access requests.
Key difference from lockers: BIKE_HOUSE_DOOR devices only support the
unlockcommand (nolock,book, etc.). Each unlock is a stateless action — no session is created. The door’s RFID access keys are distributed by the parent GUARD (IoT gateway), which manages allowlists for all its children. See Bike House (Dockless) Integration or Bike House (with Docks/Lockers) Integration for details.
Command Lifecycle
Every command you send goes through its own state machine:
stateDiagram-v2
[*] --> NOT_SENT
NOT_SENT --> SENT
SENT --> RECEIVED
RECEIVED --> SUCCESS
RECEIVED --> ERROR
class NOT_SENT,SENT,RECEIVED transient
class SUCCESS success
class ERROR error
state.value |
Meaning |
|---|---|
NOT_SENT |
Command created but not yet dispatched to the device |
SENT |
Command dispatched to the device |
RECEIVED |
Hardware acknowledged receipt |
SUCCESS |
Command executed successfully |
ERROR |
Command failed — check the device state and retry if appropriate |
Note: The
NOT_SENT → SENTtransition is near-instant. The POST response that creates a command will typically already showSENT, so you will almost never observeNOT_SENTin practice. Your polling logic should still handle it — treat it the same asSENT(command in progress).
Note: The command’s
stateis an object — usestate.valueto read the current status (e.g.,"state": { "value": "SUCCESS" }).
Polling for command completion
After sending a command, poll for its status:
# Send a command
curl -X POST \
https://services.bikeep.com/device/v1/devices/{device_id}/commands \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"command": "unlock"}'
# Response includes command id — poll for completion
curl https://services.bikeep.com/device/v1/commands/{command_id} \
-H "Authorization: Bearer {token}"
Typical round-trip time: 1–5 seconds from SENT to SUCCESS. The backend automatically moves timed-out commands to ERROR, so poll until the command reaches a terminal state (SUCCESS or ERROR). If a command does end in ERROR, check the device’s heartbeat_v2.health to determine whether the device is offline. While the backend handles command timeouts, implementing your own client-side timeout logic is still recommended — it catches edge cases where the device state doesn’t transition as expected (e.g., due to connectivity issues between the devices and the API).
Sleep mode and command latency: Devices with
power_save_strategy: REMOTE_WAKEUPinLIGHT_SLEEPmay have longer round-trip times while the backend wakes the device — latency normalizes once the device is awake. Devices withpower_save_strategy: LOCAL_WAKEUPinDEEP_SLEEPwill not respond to commands at all — the command will time out toERROR. Checksleep_stateandpower_save_strategybefore sending commands to these devices. See Device Types — Power Save and Sleep for details.
Command state.timeout_at: While a command is in SENT state, state.timeout_at indicates the deadline for the command to reach the device. If the device doesn’t acknowledge the command by this time, the backend moves the command to ERROR. On terminal states (SUCCESS or ERROR), state.timeout_at is null.
RFID-Triggered Transitions
When a whitelisted RFID card is tapped on a device, the hardware triggers state transitions automatically without an API command:
| Device State | RFID Tap | Result |
|---|---|---|
UNLOCKED |
Whitelisted card | Device locks → LOCKED (session starts, tied to that card) |
LOCKED |
Same card that locked it | Device unlocks → UNLOCKED (session ends) |
LOCKED |
Card with is_master_key: true |
Device unlocks → UNLOCKED (session ends) |
LOCKED |
Different non-master card | No effect (access denied) |
These RFID-triggered transitions generate the same state changes you would see via the API. Monitor them by polling the device endpoint or the location endpoint for aggregated availability.
Note: RFID taps cannot trigger or consume bookings. The system cannot infer intent from a physical card tap — booking is designed for remote reservation of parking places via the API. A
BOOKEDdevice will not respond to RFID taps; the booking must be consumed or cancelled through the API.
Hardware States
Separate from the logical device state, each device reports a hardware_state:
| Hardware State | Meaning |
|---|---|
NORMAL |
Hardware operating correctly |
DISABLED |
Hardware administratively disabled |
DISABLED_LOCKING |
Device can only accept unlock commands (locking disabled) |
DISABLED_UNLOCKING |
Device can only accept lock commands (unlocking disabled) |
ERROR |
Hardware error detected |
NOT_RESPONDING |
Hardware not responding to commands (legacy G1 docks only) |
In practice, only
NORMALandNOT_RESPONDINGare actively used.NOT_RESPONDINGapplies to legacy G1 docks only. TheDISABLED_*andERRORstates exist in the API schema but are rarely encountered in production.
Heartbeat Health (heartbeat_v2.health)
Use heartbeat_v2.health to determine whether a device is online before sending commands:
| Health | Meaning |
|---|---|
HEALTHY |
Device is communicating normally |
UNHEALTHY |
Heartbeat delayed (~15 seconds without heartbeat) |
DEAD |
No recent heartbeat (~30 seconds) — device may be offline |
LONG_DEAD |
Device offline for 2+ hours |
Legacy field: The deprecated
heartbeat_healthfield still exists but mapsLONG_DEADtoDEAD(losing the distinction). Useheartbeat_v2.healthfor new integrations. See Device Types — Common Properties for the fullheartbeat_v2object.
Use
hardware_stateandheartbeat_v2.healthtogether to determine if a device is operational before sending commands.