Introduction
What is TOMP?
TOMP (Transport Operator MaaS Provider) is an open API standard that defines how transport operators (like Voi) communicate with MaaS providers (transit apps, journey planners). It standardizes the full trip lifecycle: planning, booking, execution, and support.
Voi implements a minimal subset of TOMP v1.6.1 focused on e-scooter and e-bike rentals.
API contract
Voi supports a subset of the TOMP v1.6.1 specification. We commit to maintaining backward compatibility with the endpoints documented here. New endpoints may be added over time.
All major updates will be communicated to active partners via email before implementation.
Development
Test your integration against our staging environment. In staging, we provide virtual scooters in Berlin.
- Vehicle locations do not change during a rental.
- Zones and vehicles cover a larger area than in production.
For a broader description of the integration process, check our deep integration checklist.
Rate Limit
Default rate limit: 4000 requests per minute (sum of all requests). HTTP 429 is returned when exceeded.
For higher rate limits, contact our engineering support.
Staging & Production
| Environment | Base URL |
|---|---|
| Staging | https://api.stage.voiapp.io/edge/tomp |
| Production | https://api.voiapp.io/edge/tomp |
Authentication
All requests require an X-Access-Token header. Contact our engineering support to receive your API key.
Endpoints
Operator Information
Get Regions
Returns the geographic regions where Voi operates.
curl "https://api.voiapp.io/edge/tomp/operator/regions" \
-H "X-Access-Token: $TOKEN"
GET /operator/regions
Response 200
[
{
"regionId": "123",
"name": "Berlin",
"type": "OPERATING",
"serviceArea": {
"type": "Polygon",
"coordinates": [[[13.3, 52.5], [13.4, 52.5], [13.4, 52.6], [13.3, 52.6], [13.3, 52.5]]]
}
}
]
| Field | Type | Description |
|---|---|---|
| regionId | string | Unique identifier for the region (Voi zone ID) |
| name | string | Public name of the region |
| type | string | Area type. This endpoint only returns OPERATING regions |
| serviceArea | GeoJSON Polygon | Geographic boundary of the region |
Get Operator Metadata
Returns information about the running implementation, including supported endpoints and scenarios.
curl "https://api.voiapp.io/edge/tomp/operator/meta" \
-H "X-Access-Token: $TOKEN"
GET /operator/meta
Response 200
[
{
"version": "1.6.1",
"baseUrl": "https://api.voiapp.io/edge/tomp",
"endpoints": [
{ "method": "POST", "path": "/bookings/one-stop", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/bookings/{id}", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/legs/{id}", "status": "IMPLEMENTED" },
{ "method": "POST", "path": "/legs/{id}/events", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/operator/meta", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/operator/pricing-plans", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/operator/regions", "status": "IMPLEMENTED" },
{ "method": "POST", "path": "/support", "status": "IMPLEMENTED" }
],
"scenarios": ["POSTPONED_COMMIT", "PAY_WHEN_FINISHED"],
"processIdentifiers": {
"operatorInformation": [],
"planning": [],
"booking": ["AUTO_COMMIT"],
"tripExecution": [],
"support": [],
"payment": [],
"general": []
}
}
]
Get Pricing Plans
Returns pricing information for Voi vehicles in each region.
curl "https://api.voiapp.io/edge/tomp/operator/pricing-plans" \
-H "X-Access-Token: $TOKEN"
GET /operator/pricing-plans
Response 200
[
{
"planId": "plan-123",
"regionId": "456",
"name": "Standard E-Scooter",
"isTaxable": true,
"description": "Pay per minute pricing",
"fare": {
"estimated": false,
"parts": [
{ "amount": 1.0, "currencyCode": "EUR", "type": "FIXED", "name": "Unlock fee" },
{ "amount": 0.19, "currencyCode": "EUR", "type": "FLEX", "unitType": "MINUTE", "units": 1, "name": "Per minute" }
]
}
}
]
Booking
Create One-Stop Booking
Creates a booking in a single request. Combines planning and booking — provide a location and optionally a specific vehicle, and receive a booking directly.
curl -X POST "https://api.voiapp.io/edge/tomp/bookings/one-stop" \
-H "X-Access-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"from": {
"coordinates": { "lng": 13.3888, "lat": 52.5170 }
},
"useAssets": ["vehicle-id-123"],
"customer": {
"externalId": "user-abc-456"
}
}'
POST /bookings/one-stop
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| from | Place | yes | Departure location (lon/lat) |
| useAssets | string[] | yes | Specific vehicle IDs to book (exactly one) |
| customer | Customer | yes | End user information |
| customer.externalId | string | yes | Your identifier for this user |
Response 201
{
"id": "booking-789",
"state": "CONFIRMED",
"from": {
"coordinates": { "lng": 13.3888, "lat": 52.5170 }
},
"legs": [
{
"id": "leg-789",
"state": "NOT_STARTED",
"assetType": {
"id": "escooter",
"assetClass": "OTHER",
"assetSubClass": "scooter"
},
"asset": {
"id": "vehicle-id-123",
"stateOfCharge": 85,
"maxRange": 12000
},
"pricing": {
"estimated": true,
"parts": [
{ "amount": 1.0, "currencyCode": "EUR", "type": "FIXED" }
]
}
}
]
}
Booking states: STARTED → START_FINISHING → FINISHED
Get Booking
Retrieve the current state of a booking.
curl "https://api.voiapp.io/edge/tomp/bookings/booking-789" \
-H "X-Access-Token: $TOKEN"
GET /bookings/{id}
Returns the same booking object as the create response, with updated state.
Trip Execution
Get Leg
Retrieve the current state of a leg (trip segment).
curl "https://api.voiapp.io/edge/tomp/legs/leg-789" \
-H "X-Access-Token: $TOKEN"
GET /legs/{id}
Response 200
{
"id": "leg-789",
"state": "IN_USE",
"departureTime": "2026-04-15T10:30:00Z",
"assetType": {
"id": "escooter",
"assetClass": "OTHER",
"assetSubClass": "scooter"
},
"asset": {
"id": "vehicle-id-123",
"stateOfCharge": 72,
"maxRange": 9500
},
"pricing": {
"estimated": false,
"parts": [
{ "amount": 1.0, "currencyCode": "EUR", "type": "FIXED", "name": "Unlock fee" },
{ "amount": 2.85, "currencyCode": "EUR", "type": "FLEX", "unitType": "MINUTE", "units": 15, "name": "Ride time" }
]
}
}
Leg states: STARTED → START_FINISHING → FINISHED
Post Leg Event
Send an event to change the state of a leg.
curl -X POST "https://api.voiapp.io/edge/tomp/legs/leg-789/events" \
-H "X-Access-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event": "FINISH",
"userLocation": {
"coordinates": { "lng": 13.3920, "lat": 52.5200 }
}
}'
POST /legs/{id}/events
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | yes | FINISH or START_FINISHING |
| userLocation | Place | no | User’s current location |
| asset | Asset | no | Asset state at event time |
| url | string[] | no | Supporting media (e.g. parking photos) |
Response 200
Returns the updated leg object with new state and final pricing.
Note: A 503 response with a Retry-After header may be returned for temporary issues (e.g. Bluetooth lock jammed). Retry after the specified number of seconds.
Support
Create Support Ticket
Submit a support request on behalf of the end user.
curl -X POST "https://api.voiapp.io/edge/tomp/support" \
-H "X-Access-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "booking-789",
"supportType": "NOT_AT_LOCATION",
"time": "2026-04-15T10:45:00Z",
"contactInformationEndUser": "+49123456789",
"comment": "The scooter is not at the indicated location"
}'
POST /support
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | yes | The booking ID |
| supportType | string | yes | Type of issue (see below) |
| time | datetime | yes | When the issue occurred |
| contactInformationEndUser | string | yes | User’s contact info |
| comment | string | yes | Description of the issue |
Support types:
| Type | Description |
|---|---|
BROKEN_DOWN |
Vehicle is broken |
NOT_AT_LOCATION |
Vehicle not found at expected location |
MISSING_AFTER_PAUSE |
Vehicle gone after pausing trip |
NOT_CLEAN |
Vehicle is not clean |
NOT_AVAILABLE |
Vehicle not available |
UNABLE_TO_OPEN |
Cannot unlock vehicle |
UNABLE_TO_CLOSE |
Cannot lock vehicle |
API_TECHNICAL |
Technical API issue |
API_FUNCTIONAL |
Functional API issue |
ACCIDENT |
Accident occurred |
OTHER |
Other issue |
Response 200
{
"id": "booking-789",
"supportType": "NOT_AT_LOCATION",
"time": "2026-04-15T10:45:00Z",
"contactInformationEndUser": "+49123456789",
"comment": "The scooter is not at the indicated location",
"status": "PROCESSING"
}
Support statuses: PROCESSING, UPDATE_REQUESTED, RESOLVED, CANCELLED
Error Handling
All errors follow the TOMP error handling specification.
{
"status": 400,
"detail": "Invalid coordinates provided"
}
| HTTP Code | Description |
|---|---|
| 400 | Bad request — invalid input |
| 401 | Unauthenticated — missing or invalid token |
| 403 | Forbidden — valid token but insufficient permissions |
| 404 | Not found — resource doesn’t exist |
| 406 | Not acceptable — booking cannot be made (e.g. no vehicles available) |
| 409 | Conflict — state conflict (e.g. booking already finished) |
| 428 | Precondition required — e.g. user blocked by operator |
| 503 | Service unavailable — temporary issue, check Retry-After header |
Typical Flow
A typical integration follows this flow:
1. GET /operator/regions → Discover available zones
2. GET /operator/pricing-plans → Get pricing per zone
3. POST /bookings/one-stop → Book a specific vehicle
4. GET /legs/{id} → Monitor trip state
5. POST /legs/{id}/events → End the trip (FINISH)
6. GET /bookings/{id} → Get final booking with pricing
OpenAPI Specification
The full OpenAPI 3.0 specification for Voi’s TOMP implementation is available at:
For the complete TOMP v1.6.1 standard, see: