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 (LOCKER devices at BIKE_LOCKERS locations) are enclosed compartments — the user opens a door, places an item inside, and locks it. Bike docks (BIKE_DOCK devices at BIKE_DOCKS locations) 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:


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 GUARD device is the IoT gateway (parent controller) for the location. Its state field is not operationally meaningful — monitor heartbeat_v2.health for 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, UNLOCKED means “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 state UNLOCKED indicates the device is ready to accept a new lock command. For bike docks, UNLOCKED means 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, book commands 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 UNLOCKEDBOOKED. 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 as SENT (command dispatched to device). Progresses to SUCCESS or ERROR.
  • state.timeout_at — each command state has its own timeout. On the initial SENT state, this is the deadline for the command to reach the device — if exceeded, the state becomes ERROR. When the device enters the LOCKING state, timeout_at updates 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) have null timeouts.

State transitions: UNLOCKED (or BOOKED) → LOCKINGLOCKED

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.value is SUCCESS, the device is confirmed LOCKED — the item is secured.
  • If state.value reaches ERROR, 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: LOCKEDUNLOCKINGUNLOCKED

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_at field 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 unlock command 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, UNLOCKED means 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

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_at is omitted. Set a specific time to control when bookings expire.
  • Force commands: The API also supports force-lock, force-unlock, and force-cancel-booking which 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, or comment without 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 400 with an error_results array listing only the failed items. Successfully processed items are not rolled back. Always check the error_results array 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 (LOCKER or BIKE_DOCK) to choose appropriate icons, labels, and user instructions in your app — the API calls are identical.