Bike House (with Docks/Lockers) Integration

This guide covers integration with Bikeep docked bike houses — enclosed spaces with controlled door access and interior storage devices (lockers or bike docks). The full integration combines door access control with individual device session management: users enter through a BIKE_HOUSE_DOOR, then lock/unlock a specific LOCKER or BIKE_DOCK inside.

This guide covers location type BIKE_HOUSE.

Access-control only? If your bike house has no interior storage devices (just a GUARD and BIKE_HOUSE_DOORs), see Bike House (Dockless) Integration instead.

Prerequisites:

  • API credentials (client ID and secret) from Bikeep
  • Familiarity with Device Types (GUARD, BIKE_HOUSE_DOOR, LOCKER, BIKE_DOCK) and Location Types (BIKE_HOUSE)

Step 0: 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"

Use the returned access_token in the Authorization: Bearer {token} header for all subsequent requests. Tokens expire after 1 hour. See Authentication for all auth methods and refresh details.


Architecture

A docked bike house is a location with type: BIKE_HOUSE that contains a gateway, one or more doors, and interior storage devices:

BIKE_HOUSE (Location)
├── GUARD (Device)              — IoT gateway (parent controller, allowlist storage)
├── BIKE_HOUSE_DOOR (Device)    — entrance door #1 (RFID reader + electronic lock)
├── BIKE_HOUSE_DOOR (Device)    — entrance door #2 (if multiple entrances)
│
├── LOCKER (Device)             — locker #1 inside the room    ─┐
├── LOCKER (Device)             — locker #2 inside the room     ├─ if the bike house has lockers
├── LOCKER (Device)             — locker #3 inside the room    ─┘
│           — or —
├── BIKE_DOCK (Device)          — dock #1 inside the room   ─┐
├── BIKE_DOCK (Device)          — dock #2 inside the room    ├─ if the bike house has bike docks
└── BIKE_DOCK (Device)          — dock #3 inside the room   ─┘

The GUARD is the IoT gateway — it bridges all child devices to the cloud and distributes allowlists. Each BIKE_HOUSE_DOOR is a physical entrance that users interact with — a bike house may have one door or multiple doors at different entry points. Interior storage devices are either LOCKERs (enclosed lockers) or BIKE_DOCKs (mechanical parking arms) — a given bike house will have one type or the other, not both.

Note: Some bike houses have more than one GUARD. This can happen when there are too many child devices for a single controller, doors and interior devices are too far apart for wiring, or the building layout requires separate gateways for different sections. When listing devices, filter for type: "GUARD" — do not assume exactly one per location.

How this compares to a dockless bike house

Aspect Bike House (with Docks/Lockers) Bike House (Dockless)
Location type BIKE_HOUSE BIKE_HOUSE_DOCKLESS or BIKE_HANGAR
Interior devices LOCKER or BIKE_DOCK devices inside None — open floor parking
User flow Door access + lock/unlock interior devices Door access only — park freely inside
Device commands unlock on BIKE_HOUSE_DOOR + lock, unlock, book, cancel-booking on interior devices unlock on BIKE_HOUSE_DOOR only
Availability Count UNLOCKED interior devices (or use devices.available) Not available — no interior device count through the API
Session tracking Sessions created by interior device lock/unlock No sessions — no interior device activity
Allowlist scope Keys distributed to all children (BIKE_HOUSE_DOORs + interior devices) Keys distributed to BIKE_HOUSE_DOORs

Both variants share: GUARD gateway, one or more BIKE_HOUSE_DOOR entrances, RFID + API access models, automatic door re-locking.


Step 1: Discover Bike House Locations

curl https://services.bikeep.com/location/v1/locations \
  -H "Authorization: Bearer {token}"

Filter results for type: "BIKE_HOUSE".


Step 2: List Devices — Identify the Gateway, BIKE_HOUSE_DOOR(s), and Interior Devices

curl https://services.bikeep.com/device/v1/locations/{location_id}/devices \
  -H "Authorization: Bearer {token}"

Response:

{
  "data": [
    {
      "id": "guard-uuid-0000-0000-000000000001",
      "type": "GUARD",
      "alias": "C1",
      "code": "123456",
      "state": {
        "value": "LOCKED",
        "changed_at": "2025-12-15T09:00:00Z"
      },
      "hardware_state": "NORMAL",
      "heartbeat_health": "HEALTHY"
    },
    {
      "id": "door-uuid-0000-0000-000000000001",
      "type": "BIKE_HOUSE_DOOR",
      "alias": "D1",
      "code": "123457",
      "state": {
        "value": "LOCKED",
        "changed_at": "2025-12-15T09:00:00Z"
      },
      "hardware_state": "NORMAL",
      "heartbeat_health": "HEALTHY"
    },
    {
      "id": "door-uuid-0000-0000-000000000002",
      "type": "BIKE_HOUSE_DOOR",
      "alias": "D2",
      "code": "123460",
      "state": {
        "value": "LOCKED",
        "changed_at": "2025-12-15T09:00:00Z"
      },
      "hardware_state": "NORMAL",
      "heartbeat_health": "HEALTHY"
    },
    {
      "id": "locker-uuid-0000-0000-000000000001",
      "type": "LOCKER",
      "alias": "1",
      "code": "123458",
      "state": {
        "value": "UNLOCKED",
        "changed_at": "2025-12-15T08:30:00Z"
      },
      "hardware_state": "NORMAL",
      "heartbeat_health": "HEALTHY"
    },
    {
      "id": "locker-uuid-0000-0000-000000000002",
      "type": "LOCKER",
      "alias": "2",
      "code": "123459",
      "state": {
        "value": "LOCKED",
        "changed_at": "2025-12-14T18:00:00Z"
      },
      "hardware_state": "NORMAL",
      "heartbeat_health": "HEALTHY"
    }
  ]
}

Note: The GUARD’s state field is present in the response but is not operationally meaningful — do not use it to determine bike house status. Monitor heartbeat_v2.health instead for gateway connectivity.

  • GUARD — the IoT gateway (parent controller). Not user-facing; manages cloud connectivity and allowlist distribution.
  • BIKE_HOUSE_DOOR — a physical entrance door. A bike house may have multiple doors (one per entry point). Users tap RFID cards here, and you can send the unlock command to open it remotely. Filter devices for type: "BIKE_HOUSE_DOOR" to find all entrances.
  • LOCKER — individual storage lockers inside the room. Some bike houses have BIKE_DOCK devices instead — mechanical parking arms for bicycles. Both types support the same commands (lock, unlock, book, cancel-booking).

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.

Note: Devices may report type: "DOOR" if not yet synced with hardware — treat these the same as BIKE_HOUSE_DOOR.


Step 3: Door Access

Bike house entrances are controlled by BIKE_HOUSE_DOOR devices (children of the GUARD gateway). A bike house may have one door or multiple doors at different entry points. Users can access the bike room via RFID card tap and/or the API unlock command.

What happens physically:

  1. The user taps their whitelisted RFID card on a door’s reader — and/or your app sends an unlock command to the specific BIKE_HOUSE_DOOR device
  2. The electronic lock releases
  3. The user pulls the door open and enters
  4. The door closes behind them and the lock re-engages automatically

How integrators manage access: There are two access models, and you can use either or both:

  • API-only access — your app sends unlock commands to specific BIKE_HOUSE_DOOR devices (e.g., a “tap to unlock” button in your mobile app). No allowlist setup needed.
  • RFID card access — users tap physical RFID cards on the door reader. You manage cards via the Whitelist API. See Step 6: Allowlist Architecture below.

Most integrations use API-only access. Add RFID card access if your users need to enter the bike room without the app (e.g., dead phone, offline access).

For remote door access (e.g., from a mobile app), send the unlock command to the specific BIKE_HOUSE_DOOR device:

curl -X POST \
  https://services.bikeep.com/device/v1/devices/{door_device_id}/commands \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"command": "unlock"}'

Response:

{
  "uri": "/device/v1/commands/{command_id}",
  "id": "{command_id}",
  "name": "unlock",
  "device": {
    "id": "{door_device_id}",
    "uri": "/device/v1/devices/{door_device_id}"
  },
  "state": {
    "value": "SENT",
    "timeout_at": "2026-02-26T10:00:03.000Z"
  },
  "events": [
    {
      "name": "REQUEST_INITIATED",
      "timestamp": "2026-02-26T10:00:00.000Z"
    }
  ]
}

Poll GET /device/v1/commands/{command_id} until state.value reaches SUCCESS (or ERROR). The door lock releases on success — the user can then pull the door open. The command round-trip (API ↔ device) typically takes 1–5 seconds.

Multi-door bike houses: Some bike houses have multiple entrance doors. Filter the location’s devices for type: "BIKE_HOUSE_DOOR" and use the alias field (e.g., “D1”, “D2”) or device metadata to determine which door to unlock. Allowlists are distributed to all doors automatically — RFID access works at every entrance without per-door configuration.


Step 4: Use Interior Devices

Once inside the bike house, the user interacts with the interior storage devices — either LOCKER (enclosed lockers) or BIKE_DOCK (mechanical parking arms). Both device types support the same commands: lock, unlock, book, and cancel-booking.

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.

Step 4a: Reserve a Device (Book) — Optional

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-26T11: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 4b: Check In — Lock the Device

When the user places their item in the device, 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 4c: Check Out — Unlock the Device

When the user returns and wants to retrieve their item, unlock the device:

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.

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.

Cross-references


Step 5: Complete User Flow (Door + Interior Device)

Here is the typical end-to-end flow for a user storing a bike in a bike house with interior devices:

  1. Enter the bike house — User taps RFID card on a BIKE_HOUSE_DOOR reader (and/or app sends unlock to the BIKE_HOUSE_DOOR device at their entrance) → door opens → user enters
  2. Find an available device — List devices, find one with state.value: "UNLOCKED"
  3. Store the bike — Send lock to the interior device (LOCKER or BIKE_DOCK) → bike secured
  4. Leave the bike house — Most bike room doors allow exit without RFID (exit button or push bar)

Later, to retrieve the bike:

  1. Re-enter the bike house — User taps RFID card on a BIKE_HOUSE_DOOR reader (and/or app sends unlock)
  2. Release the device — Send unlock to the interior device → bike released
  3. Leave the bike house — Exit the room

Step 6: Allowlist Architecture (Optional — RFID Access)

Skip this step if your integration uses only API commands to open doors and control interior devices. Allowlists are only needed when users will access the bike room or its devices by tapping physical RFID cards. You can also combine both models — e.g., RFID cards for regular commuters and API commands for app-based visitors — on the same location and devices.

Allowlists for bike rooms follow a GUARD-distributes-to-children model that simplifies access management.

How allowlist distribution works

Allowlists target the GUARD (or location), and the GUARD distributes keys to ALL its children — BIKE_HOUSE_DOORs, LOCKERs, BIKE_DOCKs, and any other child devices.

This means you target the GUARD or location once, and access is automatically distributed. You do NOT need to create separate allowlists for each child device.

Multi-GUARD locations: If a bike house has more than one GUARD, targeting the location covers all GUARDs and their children automatically. If you target a specific GUARD device instead, only that GUARD’s children receive the keys.

Setting up access

Note: Whitelists and their targets (which locations/devices receive the keys) are managed by Bikeep during station setup. Use the API to find the existing whitelist id for your location, then add or remove RFID entities (cards) as needed:

# Discover existing whitelists for the location
curl https://services.bikeep.com/whitelist/v1/locations/{location_id}/whitelists \
  -H "Authorization: Bearer {token}"

Use the whitelist id from the response in the following steps. For detailed whitelist management (adding/removing entities and targets, entity statuses, batch operations), see Locker & Dock Integration — Step 4.

# 1. Add RFID cards
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": "User Alice"
      }
    ]
  }'

Step 7: Monitor Bike Room State

Door state

Each BIKE_HOUSE_DOOR device primarily alternates between LOCKED and UNLOCKING:

  • LOCKED — door is secured (this is the “available” state for BIKE_HOUSE_DOOR devices)
  • UNLOCKING — door lock is releasing

If the bike house has multiple BIKE_HOUSE_DOORs, each reports its state independently.

The GUARD (IoT gateway) also reports state and heartbeat data, but its state is not operationally meaningful — monitor the GUARD’s heartbeat_v2.health to assess gateway connectivity. The legacy heartbeat_health field is deprecated and does not distinguish LONG_DEAD from DEAD.

Interior device states

Each interior device (LOCKER or BIKE_DOCK) reports its own state independently. Monitor them the same way as standalone devices:

# All devices at the location (door + interior devices)
curl https://services.bikeep.com/device/v1/locations/{location_id}/devices \
  -H "Authorization: Bearer {token}"

# A specific device
curl https://services.bikeep.com/device/v1/devices/{device_id} \
  -H "Authorization: Bearer {token}"

Availability

To show “X spots available” for a bike house, count the interior devices (LOCKER or BIKE_DOCK) — exclude GUARD and BIKE_HOUSE_DOOR — with state.value: "UNLOCKED". Or use the devices.available count from the location endpoint, which computes this for you.


Important Notes

  • GUARD is not a door: The GUARD is the IoT gateway (parent controller). It stores and distributes allowlists but has no door-control commands. To open a door, send the unlock command to the specific BIKE_HOUSE_DOOR device.
  • Tailgating / piggybacking: Users can enter the bike house without triggering a door unlock event — for example, by following another user through an open door, entering as someone leaves, or finding the door propped open. While interior device sessions (lock/unlock on LOCKERs or BIKE_DOCKs) still track individual device usage, door entry itself is not guaranteed to be captured. Do not rely on door unlock events alone for occupancy tracking or to verify that a specific user is inside the space.
  • Interior device deep-dive: For detailed RFID interaction, booking workflows, and monitoring patterns for the interior devices (LOCKERs or BIKE_DOCKs), see the Locker & Dock Integration guide — the device-level API is identical whether the device is standalone or inside a bike house.
  • S2S limitations: Service-to-service OAuth2 does not create usage sessions. If you need Bikeep-managed session tracking, use the External Users auth method. Otherwise, you can build your own session tracking on top of S2S commands and device state changes.
  • 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.
  • 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.
  • 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. See Webhooks for payload format and event types.