This specification contains a data standard for mobility as a service providers to define a RESTful API for municipalities to access on-demand.
The following information applies to all provider API endpoints. Details on providing authorization to endpoints is specified in the auth document.
Currently, the provider API is implemented for shared dockless scooters, bikes, cars and mopeds. To implement another mode, please submit a PR.
provider APIs must handle requests for specific versions of the specification from clients.
Versioning must be implemented as specified in the General information versioning section.
The response to a client request must include a valid HTTP status code defined in the IANA HTTP Status Code Registry. It also must set the Content-Type header, as specified in the Versioning section.
Response bodies must be a UTF-8 encoded JSON object and must minimally include the MDS version and a data payload:
{
"version": "x.y.z",
"data": {
"trips": [{
"provider_id": "...",
"trip_id": "...",
}]
}
}- 200: OK: operation successful.
- 400: Bad request.
- 401: Unauthorized: Invalid, expired, or insufficient scope of token.
- 404: Not Found: Object(s) do not exist.
- 500: Internal server error.
{
"error": "...",
"error_description": "...",
"error_details": [ "...", "..." ]
}| Field | Type | Field Description |
|---|---|---|
error |
String | Error message string |
error_description |
String | Human readable error description (can be localized) |
error_details |
String[] (optional) | Array of error details |
All response fields must use lower_case_with_underscores.
MDS defines JSON Schema files for trips and status_changes.
provider API responses must validate against their respective schema files. The schema files always take precedence over the language and examples in this and other supporting documentation meant for human consumption.
The /trips and /status_changes endpoints must not use pagination.
If providers choose to use pagination for either of the /events or /vehicles endpoints, the pagination
must comply with the JSON API specification.
The following keys must be used for pagination links:
first: url to the first page of datalast: url to the last page of dataprev: url to the previous page of datanext: url to the next page of data
At a minimum, paginated payloads must include a next key, which must be set to null to indicate the last page of data.
{
"version": "x.y.z",
"data": {
"trips": [{
"provider_id": "...",
"trip_id": "...",
}]
},
"links": {
"first": "https://...",
"last": "https://...",
"prev": "https://...",
"next": "https://..."
}
}MDS defines the device as the unit that transmits GPS or GNSS signals for a particular vehicle. A given device must have a UUID (device_id below) that is unique within the Provider's fleet.
Additionally, device_id must remain constant for the device's lifetime of service, regardless of the vehicle components that house the device.
References to geographic datatypes (Point, MultiPolygon, etc.) imply coordinates encoded in the WGS 84 (EPSG:4326) standard GPS or GNSS projection expressed as Decimal Degrees.
Whenever an individual location coordinate measurement is presented, it must be
represented as a GeoJSON Feature object with a corresponding timestamp property and Point geometry:
{
"type": "Feature",
"properties": {
"timestamp": 1529968782421
},
"geometry": {
"type": "Point",
"coordinates": [
-118.46710503101347,
33.9909333514159
]
}
}For the purposes of this specification, the intersection of two geographic datatypes is defined according to the ST_Intersects PostGIS operation
If a geometry or geography shares any portion of space then they intersect. For geography -- tolerance is 0.00001 meters (so any points that are close are considered to intersect).
Overlaps, Touches, Within all imply spatial intersection. If any of the aforementioned returns true, then the geometries also spatially intersect. Disjoint implies false for spatial intersection.
Municipalities requiring MDS Provider API compliance should provide an unambiguous digital source for the municipality boundary. This boundary must be used when determining which data each provider API endpoint will include.
The boundary should be defined as a polygon or collection of polygons. The file defining the boundary should be provided in Shapefile or GeoJSON format and hosted online at a published address that all providers and provider API consumers can access and download.
Providers are not required to recalculate the set of historical data that is included when the municipality boundary changes. All new data must use the updated municipality boundary.
References to timestamp imply integer milliseconds since Unix epoch. You can find the implementation of unix timestamp in milliseconds for your programming language here.
The list of allowed vehicle_type referenced below is:
vehicle_type |
|---|
| bicycle |
| car |
| scooter |
| moped |
The list of allowed propulsion_type referenced below is:
propulsion_type |
Description |
|---|---|
| human | Pedal or foot propulsion |
| electric_assist | Provides power only alongside human propulsion |
| electric | Contains throttle mode with a battery-powered motor |
| combustion | Contains throttle mode with a gas engine-powered motor |
A device may have one or more values from the propulsion_type, depending on the number of modes of operation. For example, a scooter that can be powered by foot or by electric motor would have the propulsion_type represented by the array ['human', 'electric']. A bicycle with pedal-assist would have the propulsion_type represented by the array ['human', 'electric_assist'] if it can also be operated as a traditional bicycle.
Fields specifying a monetary cost use a currency as specified in ISO 4217. All costs should be given as integers in the currency's smallest unit. As an example, to represent $1 USD, specify an amount of 100 (100 cents).
If the currency field is null, USD cents is implied.
A trip represents a journey taken by a mobility as a service customer with a geo-tagged start and stop point.
The trips endpoint allows a user to query historical trip data.
Unless stated otherwise by the municipality, the trips endpoint must return all trips with a route which intersects with the municipality boundary.
Endpoint: /trips
Method: GET
Beta feature: No
Schema: trips schema
data Payload: { "trips": [] }, an array of objects with the following structure
| Field | Type | Required/Optional | Comments |
|---|---|---|---|
provider_id |
UUID | Required | A UUID for the Provider, unique within MDS |
provider_name |
String | Required | The public-facing name of the Provider |
device_id |
UUID | Required | A unique device ID in UUID format |
vehicle_id |
String | Required | The Vehicle Identification Number visible on the vehicle itself |
vehicle_type |
Enum | Required | See vehicle types table |
propulsion_type |
Enum[] | Required | Array of propulsion types; allows multiple values |
trip_id |
UUID | Required | A unique ID for each trip |
trip_duration |
Integer | Required | Time, in Seconds |
trip_distance |
Integer | Required | Trip Distance, in Meters |
route |
GeoJSON FeatureCollection |
Required | See Routes detail below |
accuracy |
Integer | Required | The approximate level of accuracy, in meters, of Points within route |
start_time |
timestamp | Required | |
end_time |
timestamp | Required | |
publication_time |
timestamp | Optional | Date/time that trip became available through the trips endpoint |
parking_verification_url |
String | Optional | A URL to a photo (or other evidence) of proper vehicle parking |
standard_cost |
Integer | Optional | The cost, in the currency defined in currency, that it would cost to perform that trip in the standard operation of the System (see Costs & Currencies) |
actual_cost |
Integer | Optional | The actual cost, in the currency defined in currency, paid by the customer of the mobility as a service provider (see Costs & Currencies) |
currency |
String | Optional, USD cents is implied if null. | An ISO 4217 Alphabetic Currency Code representing the currency of the payee (see Costs & Currencies) |
The /trips API should allow querying trips with the following query parameters:
| Parameter | Format | Expected Output |
|---|---|---|
end_time |
YYYY-MM-DDTHH, an ISO 8601 extended datetime representing an UTC hour between 00 and 23. |
All trips with an end time occurring within the hour. For example, requesting end_time=2019-10-01T07 returns all trips where 2019-10-01T07:00:00 <= trip.end_time < 2019-10-01T08:00:00 UTC. |
If the data does not exist or the hour has not completed, /trips shall return a 404 Not Found error.
Without an end_time query parameter, /trips shall return a 400 Bad Request error.
For the near-ish real time use cases, please use the events endpoint.
To represent a route, MDS provider APIs must create a GeoJSON FeatureCollection, which includes every observed point in the route, even those which occur outside the municipality boundary.
Routes must include at least 2 points: the start point and end point. Routes must include all possible GPS or GNSS samples collected by a Provider. Providers may round the latitude and longitude to the level of precision representing the maximum accuracy of the specific measurement. For example, a-GPS is accurate to 5 decimal places, differential GPS is generally accurate to 6 decimal places. Providers may round those readings to the appropriate number for their systems.
"route": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"timestamp": 1529968782421
},
"geometry": {
"type": "Point",
"coordinates": [
-118.46710503101347,
33.9909333514159
]
}
},
{
"type": "Feature",
"properties": {
"timestamp": 1531007628377
},
"geometry": {
"type": "Point",
"coordinates": [
-118.464851975441,
33.990366257735
]
}
}]
}The status of the inventory of vehicles available for customer use.
The status changes endpoint allows a user to query the historical availability for a system within a time range.
Unless stated otherwise by the municipality, this endpoint must return only those status changes with a event_location that intersects with the municipality boundary.
Note: As a result of this definition, consumers should query the trips endpoint to infer when vehicles enter or leave the municipality boundary.
Endpoint: /status_changes
Method: GET
Beta feature: No
Schema: status_changes schema
data Payload: { "status_changes": [] }, an array of objects with the following structure
| Field | Type | Required/Optional | Comments |
|---|---|---|---|
provider_id |
UUID | Required | A UUID for the Provider, unique within MDS |
provider_name |
String | Required | The public-facing name of the Provider |
device_id |
UUID | Required | A unique device ID in UUID format |
vehicle_id |
String | Required | The Vehicle Identification Number visible on the vehicle itself |
vehicle_type |
Enum | Required | see vehicle types table |
propulsion_type |
Enum[] | Required | Array of propulsion types; allows multiple values |
event_type |
Enum | Required | See event types table |
event_type_reason |
Enum | Required | Reason for status change, allowable values determined by event type |
event_time |
timestamp | Required | Date/time that event occurred at. See Event Times |
publication_time |
timestamp | Optional | Date/time that event became available through the status changes endpoint |
event_location |
GeoJSON Point Feature | Required | |
battery_pct |
Float | Required if Applicable | Percent battery charge of device, expressed between 0 and 1 |
associated_trip |
UUID | Required if Applicable | Trip UUID (foreign key to Trips API), required if event_type_reason is user_pick_up or user_drop_off, or for any other status change event that marks the end of a trip. |
associated_ticket |
String | Optional | Identifier for an associated ticket inside an Agency-maintained 311 or CRM system. |
Because of the unreliability of device clocks, the Provider is unlikely to know with total confidence what time an event occurred at. However, they are responsible for constructing as accurate a timeline as possible. Most importantly, the order of the timestamps for a particular device's events must reflect the Provider's best understanding of the order in which those events occurred.
The /status_changes API should allow querying status changes with the following query parameters:
| Parameter | Format | Expected Output |
|---|---|---|
event_time |
YYYY-MM-DDTHH, an ISO 8601 extended datetime representing an UTC hour between 00 and 23. |
All status changes with an event time occurring within the hour. For example, requesting event_time=2019-10-01T07 returns all status changes where 2019-10-01T07:00:00 <= status_change.event_time < 2019-10-01T08:00:00 UTC. |
If the data does not exist or the hour has not completed, /status_changes shall return a 404 Not Found error.
Without an event_time query parameter, /status_changes shall return a 400 Bad Request error.
event_type |
Description | event_type_reason |
Description |
|---|---|---|---|
available |
A device becomes available for customer use | service_start |
Device introduced into service at the beginning of the day (if program does not operate 24/7) |
user_drop_off |
User ends reservation | ||
rebalance_drop_off |
Device moved for rebalancing | ||
maintenance_drop_off |
Device introduced into service after being removed for maintenance | ||
agency_drop_off |
The administrative agency (ie, DOT) drops a device into the PROW using an admin code or similar | ||
reserved |
A customer reserves a device (even if trip has not started yet) | user_pick_up |
Customer reserves device. Note that this event_type is deemed ambiguous right now: it can mean reserved as in possession, or reserved as in future hold. This issue will be remedied in a future version with an extra event_type. For now, different providers have implemented this in either way. |
unavailable |
A device is on the street but becomes unavailable for customer use | maintenance |
A device is no longer available due to equipment issues |
low_battery |
A device is no longer available due to insufficient battery | ||
removed |
A device is removed from the street and unavailable for customer use | service_end |
Device removed from street because service has ended for the day (if program does not operate 24/7) |
rebalance_pick_up |
Device removed from street and will be placed at another location to rebalance service | ||
maintenance_pick_up |
Device removed from street so it can be worked on | ||
agency_pick_up |
The administrative agency (ie, DOT) removes a device using an admin code or similar |
All MDS compatible provider APIs must expose a public GBFS feed as well. Given that GBFS hasn't fully evolved to support dockless mobility yet, we follow the current guidelines in making bike information avaliable to the public.
gbfs.jsonis always required and must contain afeedsproperty that lists all published feedssystem_information.jsonis always requiredfree_bike_status.jsonis required for MDSstation_information.jsonandstation_status.jsondon't apply for MDS
The /events endpoint is a near-ish real-time feed of status changes, designed to give access to as recent as possible series of events.
The /events endpoint functions similarly to /status_changes, but shall not included data older than 2 weeks (that should live in /status_changes.)
Unless stated otherwise by the municipality, this endpoint must return only those events with an event_location that intersects with the municipality boundary.
Note: As a result of this definition, consumers should query the trips endpoint to infer when vehicles enter or leave the municipality boundary.
The schema and datatypes are the same as those defined for /status_changes.
Endpoint: /events
Method: GET
Beta feature: Yes (as of 0.4.0)
Schema: events schema
data Payload: { "status_changes": [] }, an array of objects with the same structure as in /status_changes
Because of the unreliability of device clocks, the Provider is unlikely to know with total confidence what time an event occurred at. However, they are responsible for constructing as accurate a timeline as possible. Most importantly, the order of the timestamps for a particular device's events must reflect the Provider's best understanding of the order in which those events occurred.
The events API should allow querying with a combination of query parameters:
| Parameter | Type | Expected Output |
|---|---|---|
start_time |
timestamp | status changes where start_time <= status_change.event_time |
end_time |
timestamp | status changes where status_change.event_time < end_time |
Should either side of the requested time range be missing, /events shall return a 400 Bad Request error.
Should either side of the requested time range be greater than 2 weeks before the time of the request, /events shall return a 400 Bad Request error.
The /vehicles endpoint returns the current status of vehicles on the PROW. Only vehicles that are currently in available, unavailable, or reserved states should be returned in this payload. Data in this endpoint should reconcile with data from the /status_changes enpdoint. The data returned by this endpoint should be as close to realtime as possible, but in no case should it be more than 5 minutes out-of-date.
In addition to the standard Provider payload wrapper, responses from this endpoint should contain the last update timestamp and amount of time until the next update:
{
"version": "x.y.z",
"data": {
"vehicles": []
},
"last_updated": "12345",
"ttl": "12345"
}Where last_updated and ttl are defined as follows:
| Field Name | Required | Defines |
|---|---|---|
| last_updated | Yes | Timestamp indicating the last time the data in this feed was updated |
| ttl | Yes | Integer representing the number of milliseconds before the data in this feed will be updated again (0 if the data should always be refreshed). |
Endpoint: /vehicles
Method: GET
Beta feature: Yes (as of 0.4.1)
Schema: vehicles schema
data Payload: { "vehicles": [] }, an array of objects with the following structure
| Field | Type | Required/Optional | Comments |
|---|---|---|---|
provider_id |
UUID | Required | A UUID for the Provider, unique within MDS |
provider_name |
String | Required | The public-facing name of the Provider |
device_id |
UUID | Required | A unique device ID in UUID format, should match this device in Provider |
vehicle_id |
String | Required | The Vehicle Identification Number visible on the vehicle itself, should match this device in provider |
vehicle_type |
Enum | Required | see vehicle types table |
propulsion_type |
Enum[] | Required | Array of propulsion types; allows multiple values |
last_event_time |
timestamp | Required | Date/time when last status change occurred. See Event Times |
last_event_type |
Enum | Required | Event type of most recent status change. See event types table |
last_event_type_reason |
Enum | Required | Event type reason of most recent status change, allowable values determined by event type |
last_event_location |
GeoJSON Point Feature | Required | Location of vehicle's last event |
current_location |
GeoJSON Point Feature | Required if Applicable | Current location of vehicle if different from last event, and the vehicle is not currently on a trip |
battery_pct |
Float | Required if Applicable | Percent battery charge of device, expressed between 0 and 1 |