Download OpenAPI specification:
API for managing bike parking clusters, reservations, and pod access.
The Velomodo Controller API provides endpoints for:
All /api/* endpoints require authentication via Bearer token from Strata47.
The token should be included in the Authorization header:
Authorization: Bearer <token>
Admin endpoints (/api/admin/*) additionally require membership in the Velomodo admin namespace.
The /webhook endpoint uses API key authentication via the Authorization header (not Bearer format).
Returns the health status of the Controller service.
| status required | string Value: "healthy" |
| service required | string |
| timestamp required | string <date-time> |
{- "status": "healthy",
- "service": "controller",
- "timestamp": "2019-08-24T14:15:22Z"
}Receives telemetry data from Strata47 data feed.
Requires API key authentication via Authorization header.
Array of objects Database insert IDs from Strata47 | |
required | Array of objects (WebhookDataRecord) Array of telemetry records |
| processed required | boolean Whether all records were processed successfully |
required | Array of objects |
| totalRecords required | integer |
| processedRecords required | integer |
| totalEvents required | integer |
Array of objects | |
Array of objects |
{- "ids": [
- {
- "property1": 0,
- "property2": 0
}
], - "data": [
- {
- "deviceId": "string",
- "parentDeviceId": "string",
- "data": "string",
- "sourceIP": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "locationId": "string",
- "locationName": "string"
}
]
}{- "processed": true,
- "clusters": [
- {
- "clusterId": "string",
- "processed": true,
- "recordIds": [
- "string"
], - "eventCount": 0
}
], - "totalRecords": 0,
- "processedRecords": 0,
- "totalEvents": 0,
- "clusterErrors": [
- {
- "clusterId": "string",
- "error": "string"
}
], - "parseErrors": [
- {
- "deviceId": "string",
- "error": "string"
}
]
}Creates a temporary hold on a time block for a specific pod. Holds prevent double-booking during the checkout flow and expire automatically.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Pod to hold |
| startTime required | string <date-time> Reservation start time |
| endTime required | string <date-time> Reservation end time |
required | object (Hold) |
required | Array of objects (StateTransitionEvent) |
{- "podId": "string",
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z"
}{- "hold": {
- "holdId": "05363803-5510-4d71-8d1f-307021f9ad73",
- "clusterId": "string",
- "podId": "string",
- "userId": "string",
- "timeBlock": {
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z"
}, - "expiresAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z"
}, - "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}Confirms a hold, converting it to a reservation. The hold must belong to the authenticated user and not be expired.
| clusterId required | string Example: cluster-001 Cluster device ID |
| holdId required | string <uuid> Example: 01912345-6789-7abc-def0-123456789abc Hold ID (UUIDv7) |
required | object (Reservation) |
required | Array of objects (StateTransitionEvent) |
{- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "clusterId": "string",
- "podId": "string",
- "userId": "string",
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z",
- "status": "pending",
- "activatedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "cancelledAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}, - "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}Releases a hold without converting to a reservation. The hold must belong to the authenticated user.
| clusterId required | string Example: cluster-001 Cluster device ID |
| holdId required | string <uuid> Example: 01912345-6789-7abc-def0-123456789abc Hold ID (UUIDv7) |
| released required | boolean |
required | Array of objects (StateTransitionEvent) |
{- "released": true,
- "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}Creates a reservation directly without a hold. Use this for immediate bookings when the hold flow is not needed.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Pod to reserve |
| startTime required | string <date-time> Reservation start time |
| endTime required | string <date-time> Reservation end time |
required | object (Reservation) |
required | Array of objects (StateTransitionEvent) |
{- "podId": "string",
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z"
}{- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "clusterId": "string",
- "podId": "string",
- "userId": "string",
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z",
- "status": "pending",
- "activatedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "cancelledAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}, - "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}Retrieves a reservation by ID.
| clusterId required | string Example: cluster-001 Cluster device ID |
| reservationId required | string <uuid> Example: 01912345-6789-7abc-def0-123456789abc Reservation ID (UUIDv7) |
required | object (Reservation) |
{- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "clusterId": "string",
- "podId": "string",
- "userId": "string",
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z",
- "status": "pending",
- "activatedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "cancelledAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
}Cancels a pending reservation.
Only reservations in pending status can be cancelled.
| clusterId required | string Example: cluster-001 Cluster device ID |
| reservationId required | string <uuid> Example: 01912345-6789-7abc-def0-123456789abc Reservation ID (UUIDv7) |
required | object (Reservation) |
required | Array of objects (StateTransitionEvent) |
{- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "clusterId": "string",
- "podId": "string",
- "userId": "string",
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z",
- "status": "pending",
- "activatedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "cancelledAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}, - "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}Gets availability blocks for a time range. Optionally filter by a specific pod.
| clusterId required | string Example: cluster-001 Cluster device ID |
| startTime required | string <date-time> Start of the time range (ISO 8601) |
| endTime required | string <date-time> End of the time range (ISO 8601) |
| podId | string Filter by specific pod ID |
required | Array of objects (AvailabilityBlock) |
required | object (ReservationPolicy) |
{- "blocks": [
- {
- "podId": "string",
- "podNumber": 0,
- "timeBlock": {
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z"
}, - "status": "available",
- "holdUserId": "string"
}
], - "policy": {
- "minDurationMinutes": 30,
- "maxDurationMinutes": 480,
- "advanceBookingDays": 7,
- "holdsEnabled": true,
- "holdDurationMinutes": 10,
- "noShowGraceMinutes": 15,
- "slotIncrementMinutes": 15
}
}Validates if the authenticated user can access a pod right now. Checks for an active reservation covering the current time.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Pod to validate access for |
| valid required | boolean Whether the user has valid access |
required | Reservation (object) or null Active reservation (null if none) |
| remainingMinutes required | integer or null Minutes remaining in reservation |
| reason | string Reason if access is invalid |
{- "podId": "string"
}{- "valid": true,
- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "clusterId": "string",
- "podId": "string",
- "userId": "string",
- "startTime": "2019-08-24T14:15:22Z",
- "endTime": "2019-08-24T14:15:22Z",
- "status": "pending",
- "activatedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "cancelledAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}, - "remainingMinutes": 0,
- "reason": "string"
}Generic access request endpoint. Validates reservation and sends the specified command to the pod.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Pod to access |
| command required | string Enum: "unlock_pod" "take_picture" Command to execute |
| authorized required | boolean Whether access was granted |
| commandId | string <uuid> Command ID (UUIDv7) |
| jwt | string One-time JWT for the command |
object |
{- "podId": "string",
- "command": "unlock_pod"
}{- "authorized": true,
- "commandId": "9e2dd63c-3478-489f-86d3-8c292a65a0aa",
- "jwt": "string",
- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "remainingMinutes": 0
}
}Requests to unlock a pod. Validates the user has an active reservation and sends the unlock command.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Example: pod-001-01 Pod device ID |
| authorized required | boolean Whether access was granted |
| commandId | string <uuid> Command ID (UUIDv7) |
| jwt | string One-time JWT for the command |
object |
{- "authorized": true,
- "commandId": "9e2dd63c-3478-489f-86d3-8c292a65a0aa",
- "jwt": "string",
- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "remainingMinutes": 0
}
}Requests to take a picture from a pod's camera. Validates the user has an active reservation and sends the capture command.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Example: pod-001-01 Pod device ID |
| authorized required | boolean Whether access was granted |
| commandId | string <uuid> Command ID (UUIDv7) |
| jwt | string One-time JWT for the command |
object |
{- "authorized": true,
- "commandId": "9e2dd63c-3478-489f-86d3-8c292a65a0aa",
- "jwt": "string",
- "reservation": {
- "reservationId": "54c41ef9-5629-4a9c-bb0d-10f615966bd0",
- "remainingMinutes": 0
}
}Gets the current cluster status including all pod states.
| clusterId required | string Example: cluster-001 Cluster device ID |
required | object (ClusterState) |
required | Array of objects (PodState) |
{- "cluster": {
- "clusterId": "string",
- "connectivity": "online",
- "batteryLevel": 100,
- "cellularRssi": 0,
- "systemStatus": "healthy",
- "adminStatus": "online",
- "adminStatusReason": "string",
- "podCount": 0,
- "lastUpdated": "2019-08-24T14:15:22Z"
}, - "pods": [
- {
- "podId": "string",
- "podNumber": 0,
- "doorState": "open",
- "lightState": "on",
- "occupied": true,
- "podStatus": "online",
- "adminStatus": "online",
- "adminStatusReason": "string",
- "currentReservationId": "bb4787e0-c2c7-4037-bf62-13520f5c84c0",
- "nextReservationId": "1b81e358-6af5-434a-b02b-c70126e60b77",
- "lastMotionDetected": "2019-08-24T14:15:22Z",
- "lastUpdated": "2019-08-24T14:15:22Z"
}
]
}Gets the state of a specific pod.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Example: pod-001-01 Pod device ID |
required | object (PodState) |
{- "pod": {
- "podId": "string",
- "podNumber": 0,
- "doorState": "open",
- "lightState": "on",
- "occupied": true,
- "podStatus": "online",
- "adminStatus": "online",
- "adminStatusReason": "string",
- "currentReservationId": "bb4787e0-c2c7-4037-bf62-13520f5c84c0",
- "nextReservationId": "1b81e358-6af5-434a-b02b-c70126e60b77",
- "lastMotionDetected": "2019-08-24T14:15:22Z",
- "lastUpdated": "2019-08-24T14:15:22Z"
}
}Gets gap status for all devices in a cluster. Gaps occur when sequence numbers are missing from the telemetry stream.
| clusterId required | string Example: cluster-001 Cluster device ID |
required | Array of objects (DeviceGapStatus) |
{- "devices": [
- {
- "deviceId": "string",
- "hasGap": true,
- "lastProcessedSequence": 0,
- "gapStart": 0,
- "gapEnd": 0,
- "pendingCount": 0
}
]
}Forces processing of a gap for a specific device. Use when a gap is known to be unrecoverable (e.g., device reset). Requires a reason of at least 10 characters.
| clusterId required | string Example: cluster-001 Cluster device ID |
| deviceId required | string Device ID to force process gap for |
| reason required | string >= 10 characters Reason for forcing gap processing (min 10 characters) |
| skippedSequences required | Array of integers Sequences that were skipped |
required | Array of objects (StateTransitionEvent) |
{- "deviceId": "string",
- "reason": "stringstri"
}{- "skippedSequences": [
- 0
], - "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}Sets the administrative status for a cluster. This affects whether the cluster accepts new reservations and access requests.
| clusterId required | string Example: cluster-001 Cluster device ID |
| status required | string Enum: "online" "offline" "maintenance" New admin status |
| reason | string Reason for status change |
| success required | boolean |
required | object (ClusterState) |
required | Array of objects (StateTransitionEvent) |
{- "status": "online",
- "reason": "string"
}{- "success": true,
- "cluster": {
- "clusterId": "string",
- "connectivity": "online",
- "batteryLevel": 100,
- "cellularRssi": 0,
- "systemStatus": "healthy",
- "adminStatus": "online",
- "adminStatusReason": "string",
- "podCount": 0,
- "lastUpdated": "2019-08-24T14:15:22Z"
}, - "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}Sets the administrative status for a specific pod. This affects whether the pod accepts reservations and access requests.
| clusterId required | string Example: cluster-001 Cluster device ID |
| podId required | string Example: pod-001-01 Pod device ID |
| status required | string Enum: "online" "offline" "maintenance" New admin status |
| reason | string Reason for status change |
| success required | boolean |
required | object (PodState) |
required | Array of objects (StateTransitionEvent) |
{- "status": "online",
- "reason": "string"
}{- "success": true,
- "pod": {
- "podId": "string",
- "podNumber": 0,
- "doorState": "open",
- "lightState": "on",
- "occupied": true,
- "podStatus": "online",
- "adminStatus": "online",
- "adminStatusReason": "string",
- "currentReservationId": "bb4787e0-c2c7-4037-bf62-13520f5c84c0",
- "nextReservationId": "1b81e358-6af5-434a-b02b-c70126e60b77",
- "lastMotionDetected": "2019-08-24T14:15:22Z",
- "lastUpdated": "2019-08-24T14:15:22Z"
}, - "events": [
- {
- "eventId": "d6703cc8-9e79-415d-ac03-a4dc7f6ab43c",
- "eventType": "string",
- "clusterId": "string",
- "podId": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "previousValue": null,
- "currentValue": null,
- "payload": { }
}
]
}