Locker & Dock Integration
This smart locker API guide covers both smart lockers (BIKE_LOCKERS locations) and bike docks (BIKE_DOCKS locations). Lockers are individual enclosed storage compartments; bike docks are mechanical arms that grip a bicycle wheel or frame. While the underlying hardware state machines differ between device types, the Bikeep API abstracts this — both use the same API commands (lock, unlock, book, cancel-booking) and the same API-level state machine. Everything in this guide applies to both unless noted otherwise.
Lockers vs. Bike Docks: Lockers (
LOCKERdevices atBIKE_LOCKERSlocations) are enclosed compartments — the user opens a door, places an item inside, and locks it. Bike docks (BIKE_DOCKdevices atBIKE_DOCKSlocations) are open-air mechanical arms — the user slides a bike wheel into the slot, and the arm locks around it. The hardware operates differently, but the API presents a unified interface for both — the difference affects your UI (icons, labels, user instructions) but not your backend integration.
Prerequisites:
- API credentials (client ID and secret) from Bikeep
- Familiarity with Device Types and State Machines
Step 1: Authenticate
Obtain an OAuth2 access token using the client credentials flow:
curl -X POST https://auth.bikeep.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
Use this token in the Authorization header for all subsequent requests:
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Note: S2S tokens expire after 1 hour. Refresh by requesting a new token. See Authentication for all auth methods.
Step 2: Discover Locations
List all locations your operator has access to:
curl https://services.bikeep.com/location/v1/locations \
-H "Authorization: Bearer {token}"
Response (abbreviated):
{
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "BIKE_LOCKERS",
"label": "#1234",
"name": "#1234 Office Building A",
"status": "LAUNCHED",
"connection": "online",
"address": "123 Main Street",
"devices": {
"total": 10,
"available": 7,
"online": 10
}
},
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "BIKE_DOCKS",
"label": "#5678",
"name": "#5678 Train Station B",
"status": "LAUNCHED",
"connection": "online",
"address": "456 Station Road",
"devices": {
"total": 20,
"available": 15,
"online": 20
}
}
]
}
Filter by type — "BIKE_LOCKERS" for locker locations, "BIKE_DOCKS" for bike dock locations. The devices.available count tells you how many devices are currently usable (available AND online) — this is the number to show end users.
Step 3: List Devices at a Location
Get all lockers at a specific location:
curl https://services.bikeep.com/device/v1/locations/{location_id}/devices \
-H "Authorization: Bearer {token}"
Response (abbreviated):
{
"data": [
{
"id": "guard-uuid-0000-0000-000000000001",
"type": "GUARD",
"alias": "C1",
"code": "123455",
"state": {
"value": "LOCKED",
"changed_at": "2025-12-15T09:00:00Z"
},
"hardware_state": "NORMAL",
"heartbeat_health": "HEALTHY"
},
{
"id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"type": "LOCKER",
"alias": "1",
"code": "123456",
"state": {
"value": "UNLOCKED",
"changed_at": "2025-12-15T10:30:00Z"
},
"hardware_state": "NORMAL",
"heartbeat_health": "HEALTHY"
},
{
"id": "a9b8c7d6-e5f4-3210-abcd-ef1234567890",
"type": "LOCKER",
"alias": "2",
"code": "123457",
"state": {
"value": "LOCKED",
"changed_at": "2025-12-15T08:00:00Z"
},
"hardware_state": "NORMAL",
"heartbeat_health": "HEALTHY"
}
]
}
Note: The
GUARDdevice is the IoT gateway (parent controller) for the location. Itsstatefield is not operationally meaningful — monitorheartbeat_v2.healthfor gateway connectivity. The GUARD manages cloud communication and allowlist distribution for all child devices.
Note: Some locations have more than one GUARD — for example, when there are too many devices for a single controller or sections are too far apart for wiring. When listing devices, filter for
type: "GUARD"rather than assuming exactly one per location.
Bike dock locations return type: "BIKE_DOCK" instead of "LOCKER". Fields and states are the same.
A device with state.value: "UNLOCKED" is available. A device with state.value: "LOCKED" is currently in use.
Note: For lockers,
UNLOCKEDmeans “available for use” — not physically open. Lockers are normally-locked: the electronic latch is armed at all times, and the door locks automatically when closed. The API stateUNLOCKEDindicates the device is ready to accept a newlockcommand. For bike docks,UNLOCKEDmeans the mechanical arm is physically open.
Step 4: Set Up RFID Access (Optional — Whitelists)
Lockers and bike docks support two access models, and you can use either or both on the same devices:
- API commands — your app sends
lock,unlock,bookcommands directly (e.g., from a mobile app). No allowlist setup needed. - RFID card access — users tap physical RFID cards on the device reader. You manage cards via the Whitelist API (this step).
Many deployments combine both: regular commuters use RFID cards for quick tap-and-go access, while occasional users or visitors use the mobile app.
Skip this step if your integration uses only API commands to control devices. Allowlists are only needed when users will operate devices by tapping physical RFID cards on the device reader.
Whitelists and their targets (which locations/devices accept the RFID cards) are configured by Bikeep during station setup. Your integration manages the entities — adding and removing RFID cards on existing whitelists.
4a. Find existing whitelists for the location
curl https://services.bikeep.com/whitelist/v1/locations/{location_id}/whitelists \
-H "Authorization: Bearer {token}"
You can also list the entities (RFID cards) and targets on an existing whitelist:
# List RFID cards in a whitelist
curl https://services.bikeep.com/whitelist/v1/whitelists/{whitelist_id}/entities \
-H "Authorization: Bearer {token}"
# List targets in a whitelist (optionally filter by type)
curl "https://services.bikeep.com/whitelist/v1/whitelists/{whitelist_id}/targets?type=device" \
-H "Authorization: Bearer {token}"
4b. Add RFID cards (entities) to the whitelist
curl -X POST \
https://services.bikeep.com/whitelist/v1/whitelists/{whitelist_id}/entities \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"operation": "ADD",
"entities": [
{
"id": "04a2b3c4d5e6f7",
"id_type": "rfid",
"status": "ACTIVE",
"comment": "Employee badge - John"
},
{
"id": "04b3c4d5e6f7a8",
"id_type": "rfid",
"status": "ACTIVE",
"expires_at": "2026-06-30T23:59:59Z",
"comment": "Contractor badge - Jane (temp)"
}
]
}'
4c. Remove RFID cards from the whitelist
To remove RFID cards from a whitelist, use the REMOVE operation:
curl -X POST \
https://services.bikeep.com/whitelist/v1/whitelists/{whitelist_id}/entities \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"operation": "REMOVE",
"entities": [
{
"id": "04a2b3c4d5e6f7",
"id_type": "rfid"
}
]
}'
Step 5: Reserve a Locker (Book)
Commands are asynchronous. When you send a command, you get back a command ID immediately. The command then travels to the device over MQTT. You poll GET /device/v1/commands/{command_id} until the state reaches SUCCESS (or ERROR). Typical command round-trip (API ↔ device): 1–5 seconds.
Reserve a specific device for a user remotely — for example, when they want to claim a device from a distance before arriving at the location. When physically at the device, booking is unnecessary — the lock command internally sets the device to ALLOCATED state immediately, which prevents other users from claiming it:
curl -X POST \
https://services.bikeep.com/device/v1/devices/{device_id}/commands \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"command": "book",
"timeout_at": "2026-02-19T15:00:00Z"
}'
Response:
{
"uri": "/device/v1/commands/{command_id}",
"id": "{command_id}",
"name": "book",
"device": {
"id": "{device_id}",
"uri": "/device/v1/devices/{device_id}"
},
"state": {
"value": "SENT",
"timeout_at": "2026-02-26T10:00:03.000Z"
},
"events": [
{
"name": "REQUEST_INITIATED",
"timestamp": "2026-02-26T10:00:00.000Z"
}
]
}
The device state changes from UNLOCKED → BOOKED. If timeout_at is omitted, the booking defaults to 1 hour. After the timeout, the device automatically returns to UNLOCKED.
Cancel a booking if the user changes their mind:
curl -X POST \
https://services.bikeep.com/device/v1/devices/{device_id}/commands \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"command": "cancel-booking"}'
Poll GET /device/v1/commands/{command_id} until state.value reaches SUCCESS — the device returns to UNLOCKED.
Step 6: Check In (Lock)
When the user arrives and places their item in the locker, lock it:
curl -X POST \
https://services.bikeep.com/device/v1/devices/{device_id}/commands \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"command": "lock"}'
Response:
{
"uri": "/device/v1/commands/{command_id}",
"id": "{command_id}",
"name": "lock",
"device": {
"id": "{device_id}",
"uri": "/device/v1/devices/{device_id}"
},
"state": {
"value": "SENT",
"timeout_at": "2026-02-26T10:30:03.000Z"
},
"events": [
{
"name": "REQUEST_INITIATED",
"timestamp": "2026-02-26T10:30:00.000Z"
}
]
}
Key fields:
id— the command ID. Use this to poll for completion.state.value— starts asSENT(command dispatched to device). Progresses toSUCCESSorERROR.state.timeout_at— each command state has its own timeout. On the initialSENTstate, this is the deadline for the command to reach the device — if exceeded, the state becomesERROR. When the device enters theLOCKINGstate,timeout_atupdates to reflect how long the user has to physically secure their item — the dock arm or locker door stays engaged until this timeout or hardware confirmation. Terminal states (LOCKED,UNLOCKED,ERROR) havenulltimeouts.
State transitions: UNLOCKED (or BOOKED) → LOCKING → LOCKED
Poll for command completion
Use the command id from the POST response to check whether the hardware has confirmed the lock:
curl https://services.bikeep.com/device/v1/commands/{command_id} \
-H "Authorization: Bearer {token}"
Success response:
{
"uri": "/device/v1/commands/{command_id}",
"id": "{command_id}",
"name": "lock",
"device": {
"id": "{device_id}",
"uri": "/device/v1/devices/{device_id}"
},
"state": {
"value": "SUCCESS"
},
"events": [
{
"name": "REQUEST_INITIATED",
"timestamp": "2026-02-26T10:30:00.000Z"
},
{
"name": "REQUEST_SENT",
"timestamp": "2026-02-26T10:30:00.500Z"
},
{
"name": "RESPONSE_DEVICE_LOCKED",
"timestamp": "2026-02-26T10:30:02.000Z"
}
]
}
Polling guidance:
- Poll every 1–2 seconds. The command round-trip (API ↔ device) typically takes 1–5 seconds. Total completion time depends on the user — the device waits for them to physically secure their item before confirming
LOCKED. - When
state.valueisSUCCESS, the device is confirmedLOCKED— the item is secured. - If
state.valuereachesERROR, the command failed (e.g., the device is offline, or the hardware could not engage the lock). Your app should notify the user and offer a retry.
Step 7: Check Out (Unlock)
When the user is finished, unlock to end the session:
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"}'
The response follows the same structure as the lock command — you receive a command id and poll for completion.
State transitions: LOCKED → UNLOCKING → UNLOCKED
Poll GET /device/v1/commands/{command_id} until state.value reaches SUCCESS. The device is now available for the next user.
Note: The
state.timeout_atfield on unlock reflects how long the device holds the unlocked state before automatically re-locking. You can use this value for UI feedback (e.g., showing a countdown or unlock animation to the user).
Locker behavior: Lockers are normally-locked — the electronic latch is armed at all times. When the user closes the locker door, it latches automatically regardless of the device’s API state. The
unlockcommand releases the latch so the user can open the door, but the door locks again as soon as it closes. Keep this in mind when building UX around lockers (e.g., prompt the user to “close the door to secure your item”). For bike docks,UNLOCKEDmeans the mechanical arm is physically open.
Step 8: Monitor via Webhooks
Bikeep can send webhooks to your endpoint for several event types relevant to locker integrations:
| Type | When |
|---|---|
connection |
Device goes online or offline |
connection_confirmed |
Location offline for 2+ hours — confirms a real outage, not a brief flap |
alarm |
Hardware alarm triggered (e.g., case opened, vandalism) |
status |
Anomalous device state persisted (e.g., door left open 5+ min, interim state timed out) — requires partner access |
The status webhook is an alarming tool — it fires only when a device is stuck in an abnormal state, not on every state change. It does not replace polling for tracking normal state transitions. Use connection_confirmed to detect prolonged outages (2+ hours offline); note that it does not notify when a location comes back online.
Contact Bikeep to register your webhook endpoint. See Webhooks for payload format, all event types, and verification.
Delivery timing: Webhooks are aggregated by default (1–15 minutes depending on type). Direct delivery with sub-second latency is available on request.
Step 9: Monitor via Polling
Step 9a: Monitor Availability via Location API (Recommended)
For dashboards, availability displays, and background health monitoring, poll at the location level rather than per-device. A single call returns aggregated availability, connectivity, and device counts:
# Authenticated — returns all your operator's locations
curl https://services.bikeep.com/location/v1/locations \
-H "Authorization: Bearer {token}"
# Unauthenticated — returns locations in a public area (for public-facing displays)
curl https://services.bikeep.com/location/v1/public-areas/{public_area_id}/locations
Key aggregated fields:
| Field | What It Tells You |
|---|---|
devices.available |
Devices that are available AND online — show this to end users |
devices.online |
Total devices communicating with the cloud |
devices.total |
Total devices at the location |
connection |
Location connectivity: "online" or "offline" |
Recommended polling interval: Every 5 minutes for active dashboards, every 30 minutes to 1 hour for background monitoring.
For a full walkthrough of building an availability display, see Availability Display.
Step 9b: Device-Level Polling (During Active Interactions)
Poll individual devices only when a user is actively interacting with a specific device (e.g., waiting for a lock or unlock command to complete) or for maintenance/support tasks:
curl https://services.bikeep.com/device/v1/devices/{device_id} \
-H "Authorization: Bearer {token}"
Key fields to monitor:
| Field | What It Tells You |
|---|---|
state.value |
Current state (UNLOCKED, LOCKED, BOOKED, etc.) |
state.changed_at |
When the state last changed |
state.timeout_at |
Deadline for the current state — meaning depends on state.value: booking expiry in BOOKED, command delivery deadline in ALLOCATED, hardware confirmation window in LOCKING/UNLOCKING, null in terminal states. See State Machines |
hardware_state |
Hardware health — in practice you’ll see NORMAL. Other values (NOT_RESPONDING, DISABLED_*, ERROR) are legacy or reserved. |
heartbeat_v2.health |
Device connectivity: HEALTHY, UNHEALTHY (~15s no heartbeat), DEAD (~30s), or LONG_DEAD (2+ hours offline) |
heartbeat_v2.dead_since |
When the device stopped responding (null if healthy) — useful for support dashboards |
heartbeat_health |
Deprecated — use heartbeat_v2.health instead. Values: HEALTHY, UNHEALTHY, DEAD |
heartbeat |
Deprecated — use heartbeat_v2 instead |
Recommended polling interval: Every 1–2 seconds during command execution. Outside of active interactions, use the location-level API (Step 9a) instead.
Important Notes
- Booking timeout: Defaults to 1 hour if
timeout_atis omitted. Set a specific time to control when bookings expire. - Force commands: The API also supports
force-lock,force-unlock, andforce-cancel-bookingwhich bypass normal state checks. Use these only for administrative/recovery scenarios. - RFID format (if using RFID cards): RFID card IDs must be raw lowercase hexadecimal with no separators, even length (e.g.,
04a2b3c4d5e6f7— 14 characters). Do not use colons or uppercase. Maximum 20 characters. - Whitelist scope: Each whitelist has targets (devices/locations) configured by Bikeep. RFID cards within a whitelist share the same access scope — all cards in a whitelist can operate all targeted devices.
- Re-adding an entity: If you ADD an RFID card that already exists in the whitelist, the existing entry is replaced (upsert). This is useful for updating fields like
status,expires_at, orcommentwithout needing to REMOVE first. - Batch error handling: Entity and target modification endpoints process items individually. If some items succeed and others fail, the response is
400with anerror_resultsarray listing only the failed items. Successfully processed items are not rolled back. Always check theerror_resultsarray to identify which specific items failed. - Webhook delivery: Standard webhook delivery is aggregated (1–15 minute intervals depending on event type). Contact Bikeep for direct delivery if you need sub-second notifications.
- S2S limitations: Service-to-service OAuth2 does not create usage sessions. If you need session tracking (usage history, fees, statistics), use the External Users auth method.
- Lockers vs. bike docks in your UI: The only integration difference is presentation. Use device
type(LOCKERorBIKE_DOCK) to choose appropriate icons, labels, and user instructions in your app — the API calls are identical.