├── Gobee.md ├── Bluegogo.md ├── Link.md ├── Bicikelj.md ├── Dropbike.md ├── Sigo.md ├── Zagster.md ├── Blip.md ├── Baqme.md ├── CityBikeWien.md ├── SocialBicycles.md ├── .github └── ISSUE_TEMPLATE │ └── new_provider.md ├── Wind.md ├── Coup.md ├── Bond.md ├── Felyx.md ├── Frank-e.md ├── Blueduck.md ├── Evo Sharing.md ├── Kruiser.md ├── Vaimoo.md ├── Eddy.md ├── Cargaroo.md ├── Go-Sharing.md ├── Motivate.md ├── Ufo.md ├── Stella.md ├── Hive.md ├── Zero.md ├── Scoota.md ├── Bixi.md ├── Onzo.md ├── EnTur.md ├── Donkey.md ├── Lidl-Bike.md ├── Cambio.md ├── Call-a-Bike.md ├── Pony.md ├── Ryde.md ├── VeloAntwerpen.md ├── Spin.md ├── Evhcle.md ├── Avocargo.md ├── Mobike.md ├── Dott.md ├── Tier.md ├── Obike.md ├── README.md ├── Bird.md ├── Circ.md ├── Ofo.md ├── EUBike.md ├── WeShare.md ├── Helbiz.md ├── Yobike.md ├── Voi.md ├── Neuron.md ├── Bolt.md ├── Nextbike.md ├── Beam.md ├── Emmy.md ├── Flamingo.md ├── Lime.md └── Jump.md /Gobee.md: -------------------------------------------------------------------------------- 1 | # Gobee Bike (gobee) 2 | 3 | Simple GET-Request example: `https://appaws.gobee.bike/GobeeBike/bikes/near_bikes?accuracy=20&lat=22.38&lng=114.198` 4 | Alternative endpoint: `https://api.gobee.bike/` 5 | -------------------------------------------------------------------------------- /Bluegogo.md: -------------------------------------------------------------------------------- 1 | # Bluegogo (defunct) 2 | 3 | POST-Request to `https://api-us.bluegogo.com/nearbyBikes?data={"token":"","version":1,"data":{"latitude":22.5526,"longitude":114.1029,"billingModelIds":"1,2,3"}}` 4 | 5 | (The JSON should be URLencoded.) – If the token is empty, the bikelist will also be empty. I guess you get a token when signing in. 6 | -------------------------------------------------------------------------------- /Link.md: -------------------------------------------------------------------------------- 1 | # Link 2 | [Link](https://superpedestrian.com) is an worldwide scootersharing company. 3 | 4 | ### Get Vehicles 5 | ```GET https://vehicles.linkyour.city/reservation-api/local-vehicles/?latitude=49.4404395&longitude=11.0760811``` 6 | No special headers or authentication required. If you open the page in an webbrowser, there will be an rendered version of the json. 7 | -------------------------------------------------------------------------------- /Bicikelj.md: -------------------------------------------------------------------------------- 1 | # Bicikelj 2 | [Bicikelj](https://www.bicikelj.si/en/) is an bike sharing provider. 3 | 4 | ### Get stations 5 | ```GET https://prominfo.projekti.si/web/api/MapService/Query/lay_bicikelj/query?f=json&returnGeometry=true&outSr=4326&geometry={"xmin":14.0321,"ymin":45.7881,"xmax":14.8499,"ymax":46.218,"spatialReference":{"wkid":4326}}&outFields=*``` 6 | Add your location after `geometry` 7 | -------------------------------------------------------------------------------- /Dropbike.md: -------------------------------------------------------------------------------- 1 | # Dropbike (Canada) 2 | 3 | * `POST`-Request: `https://dropbikeadminapi.herokuapp.com/v1/bikes_nearby` 4 | * (Header `Content-Type` to `application/json`) 5 | * Request Payload example: `{"lat":43.659415191015498,"lng":-79.395512826740742}` 6 | 7 | * You can also get their regions with a simple `POST`-Request (without payload) to `https://dropbikeadminapi.herokuapp.com/v1/region_polygons` 8 | -------------------------------------------------------------------------------- /Sigo.md: -------------------------------------------------------------------------------- 1 | # Sigo 2 | [Sigo](https://sigo.green) is a vehicle sharing service. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://sigo.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://sigo.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **Zone Locations?**: `https://sigo.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **All Endpoints**: `https://sigo.frontend.fleetbird.eu/api/prod/v1.06/` 13 | -------------------------------------------------------------------------------- /Zagster.md: -------------------------------------------------------------------------------- 1 | # Zagster (defunct) 2 | 3 | List of all cities: https://zapi.zagster.com/api/v1/bikeshares/ 4 | 5 | For more information about bikes in a specific city, you'll need the city ID (found in `metadata["data"]["_id"]` from the above link) 6 | 7 | List of stations in a city and station metadata: `https://zapi.zagster.com/api/v1/bikeshares/[City ID]/stations` 8 | 9 | List of bikes in a city and bike metadata: `https://zapi.zagster.com/api/v1/bikeshares/[City ID]/bikes` 10 | -------------------------------------------------------------------------------- /Blip.md: -------------------------------------------------------------------------------- 1 | # Blip 2 | [Blip](https://www.blipscooters.com/) is a scooter sharing service that operates in New Zealand. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://blip.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://blip.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **Zone Locations?**: `https://blip.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **All Endpoints**: `https://blip.frontend.fleetbird.eu/api/prod/v1.06/` 13 | -------------------------------------------------------------------------------- /Baqme.md: -------------------------------------------------------------------------------- 1 | # Baqme (E-cargo bike) 2 | [Baqme](https://www.baqme.com/en/) is an e-cargo bike provider in The Netherlands. 3 | 4 | ## Vehicles 5 | 6 | Send a `POST` request to this URL: 7 | 8 | ``` 9 | https://baqmeconnector-api.joyridecity.bike/api/v1/map/get-markers 10 | ``` 11 | 12 | With the following URL parameters: 13 | 14 | - latitude 15 | - longitude 16 | - radiusInKm 17 | 18 | E.g.: 19 | 20 | ``` 21 | curl -X POST 'https://baqmeconnector-api.joyridecity.bike/api/v1/map/get-markers?latitude=52.078663&longitude=4.288788&radiusInKm=10' 22 | ``` -------------------------------------------------------------------------------- /CityBikeWien.md: -------------------------------------------------------------------------------- 1 | # CityBike Wien (defunct) 2 | 3 | [CityBike Wien](https://www.citybikewien.at/en/) was an short-time rental service in Vienna/Austria 4 | 5 | ## API 6 | Simple XML API with a list of stations (address & lat/lng), number of free bikes and return boxes 7 | 8 | **URL**: [`https://dynamisch.citybikewien.at/citybike_xml.php`](https://dynamisch.citybikewien.at/citybike_xml.php) 9 | 10 | **Terms**: 11 | 12 | http://dynamisch.citybikewien.at/Terms_of_Use_XML.pdf (en) 13 | http://dynamisch.citybikewien.at/Nutzungsbedingungen_XML.pdf (de) 14 | -------------------------------------------------------------------------------- /SocialBicycles.md: -------------------------------------------------------------------------------- 1 | # SocialBicycles (USA, Canada, Czech Republic, Poland) 2 | 3 | [SocialBicycles](http://socialbicycles.com/) is JUMP's partership-based bikeshare program. They publish their [Data and APIs](https://app.socialbicycles.com/developer/#!/networks). This includes the following systems (cities) around the world: 4 | 5 | * Atlanta, Boise, Charlottesville, Eugene, New Orleans, Orlando, Phoenix, Portland, Santa Monica, Tampa (USA) 6 | * SoBi Hamilton (Hamilton, ON, CA) 7 | * Velonet (Czech Republic) 8 | * Wavelo (Warsaw, Poland) 9 | * many more.. 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Provider 3 | about: Use this template for adding a new provider. 4 | title: "Add [PROVIDER NAME]" 5 | labels: new provider 6 | --- 7 | 8 | Website: [...] 9 | iOS: https://apps.apple.com/app/... 10 | Android: https://play.google.com/store/apps/details?id=... 11 | 12 | Active in [list of cities, countries] 13 | 14 | [If you already know the endpoints the provider uses, add them here. If not, thats not a problem - remove this paragraph and people from the WoBike community have a look into it] 15 | 16 | -------------------------------------------------------------------------------- /Wind.md: -------------------------------------------------------------------------------- 1 | # WIND (former BYKE) 2 | 3 | Simple GET-Request example: `https://api-prod.ibyke.io/v2/bikes?latitude=52.55001&longitude=13.40902&order=nearby` 4 | 5 | With `https://api-prod.ibyke.io/v2/parkingPorts?latitude=52.55001&longitude=13.40902`, you can get the parking zones or parking ports for a location. However, that method requires authentication with a clientId and a userId. 6 | 7 | A simple GET request to `https://api-prod.ibyke.io/v2/bikes/7aad7d2a-4fd9-48cf-992b-1057471ec208`, where the ID can be replaced with any other WIND bike or scooter ID returns the status of a specific scooter or bike. 8 | -------------------------------------------------------------------------------- /Coup.md: -------------------------------------------------------------------------------- 1 | # Coup (defunct) 2 | 3 | [Coup](https://www.joincoup.com/) was a rental service for electric motorbikes 🛵 (also called scooter, the wording is a mess). 4 | 5 | First request the cities with `GET` request `https://app.joincoup.com/api/v3/markets`. Get the `id` for your city and request all vehicles with a `GET` request `https://app.joincoup.com/api/v3/markets/{id}/scooters` for example for Berlin: `https://app.joincoup.com/api/v3/markets/fb7aadac-bded-4321-9223-e3c30c5e3ba5/scooters`. A `GET` request to 6 | `https://app.joincoup.com/api/v3/markets/{id}/business_areas` will give you business areas for that city. 7 | -------------------------------------------------------------------------------- /Bond.md: -------------------------------------------------------------------------------- 1 | # Bond (defunct) 2 | [Bond](https://bond.info/) is an e-bike sharing service that operates in Europe. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://bond.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://bond.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **City Locations**: `https://bond.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **Zones** `https://bond.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` 13 | 14 | **Vehicle Types** `https://bond.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 15 | 16 | **All Endpoints**: `https://bond.frontend.fleetbird.eu/api/prod/v1.06/` 17 | -------------------------------------------------------------------------------- /Felyx.md: -------------------------------------------------------------------------------- 1 | # Felyx 2 | [Felyx](https://felyx.com/) is a scooter sharing service that operates in Europe. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://felyx.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://felyx.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **City Locations**: `https://felyx.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **Zones** `https://felyx.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` 13 | 14 | **Vehicle Types** `https://felyx.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 15 | 16 | **All Endpoints**: `https://felyx.frontend.fleetbird.eu/api/prod/v1.06/` 17 | -------------------------------------------------------------------------------- /Frank-e.md: -------------------------------------------------------------------------------- 1 | # Frank-e 2 | [Frank-e](https://suewag2go.de/frank-e) is a scooter (e-moped) sharing service that operates in Germany. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://franke.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://franke.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **City Locations**: `https://franke.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **Zones** `https://franke.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` 13 | 14 | **Vehicle Types** `https://franke.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 15 | 16 | **All Endpoints**: `https://franke.frontend.fleetbird.eu/api/prod/v1.06/` 17 | -------------------------------------------------------------------------------- /Blueduck.md: -------------------------------------------------------------------------------- 1 | # Blue Duck 2 | [Blue Duck](https://www.flyblueduck.com/) was a step scooter service that operates in the USA. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://blueduck.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://blueduck.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **City Locations**: `https://blueduck.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **Zones** `https://blueduck.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` 13 | 14 | **Vehicle Types** `https://blueduck.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 15 | 16 | **All Endpoints**: `https://blueduck.frontend.fleetbird.eu/api/prod/v1.06/` 17 | -------------------------------------------------------------------------------- /Evo Sharing.md: -------------------------------------------------------------------------------- 1 | # Evo Sharing 2 | [Evo Sharing](https://evo-sharing.ruhr/) is a scooter (e-moped) service that operates in Germany. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://evo-sharing.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://evo-sharing.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **City Locations**: `https://evo-sharing.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **Zones** `https://evo-sharing.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` 13 | 14 | **Vehicle Types** `https://evo-sharing.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 15 | 16 | **All Endpoints**: `https://evo-sharing.frontend.fleetbird.eu/api/prod/v1.06/` 17 | -------------------------------------------------------------------------------- /Kruiser.md: -------------------------------------------------------------------------------- 1 | # Kruiser 2 | [Kruiser](https://www.swk.de/privatkunden/mobilitaet/sharing-und-emobility/kruiser) is a scooter (e-moped) sharing service that operates in Krefeld, Germany. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://kruiser.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://kruiser.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **City Locations**: `https://kruiser.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **Zones** `https://kruiser.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` 13 | 14 | **Vehicle Types** `https://kruiser.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 15 | 16 | **All Endpoints**: `https://kruiser.frontend.fleetbird.eu/api/prod/v1.06/` 17 | -------------------------------------------------------------------------------- /Vaimoo.md: -------------------------------------------------------------------------------- 1 | # Vaimoo 2 | [Vaimoo](https://vaimoo.com) is a scooter sharing service that operates in Denmark, Italy & Netherlands. 3 | 4 | Active in 5 | * Copenhagen, Denmark: Copenhagen City Bike (Bycyklen) - https://bycyklen.dk 6 | iOS: https://apps.apple.com/dk/app/bycyklen-copenhagen-bike-share/id1504234151 7 | Android: https://play.google.com/store/apps/details?id=com.vaimoo.bycyklen 8 | * Rotterdam, Netherlands (?) 9 | * Statte, Italy: https://www.nexum.bike/stattebikesharing/ (looks like its stationary, without app) 10 | 11 | API Base URL: https://rtm-aps-prod-we-api-01.azurewebsites.net 12 | Copenhagen API Base URL: https://api-bycyklen.vaimoo.com 13 | 14 | It looks that is mostly built with firebase: https://bikesharingprod.firebaseio.com 15 | -------------------------------------------------------------------------------- /Eddy.md: -------------------------------------------------------------------------------- 1 | # eddy 2 | [eddy](https://www.swd-ag.de/mobilitaet/elektromobilitaet/e-roller-mieten/) is a scooter (e-moped) sharing service that operates in Düsseldorf. 3 | 4 | ## Get Vehicle Locations 5 | 6 | **URL**: `https://eddy-sharing.frontend.fleetbird.eu/api/prod/v1.06/cars/` 7 | 8 | **Map URL**: `https://eddy-sharing.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` 9 | 10 | **City Locations**: `https://eddy-sharing.frontend.fleetbird.eu/api/prod/v1.06/locations/` 11 | 12 | **Zones** `https://eddy-sharing.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` 13 | 14 | **Vehicle Types** `https://eddy-sharing.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 15 | 16 | **All Endpoints**: `https://eddy-sharing.frontend.fleetbird.eu/api/prod/v1.06/` 17 | -------------------------------------------------------------------------------- /Cargaroo.md: -------------------------------------------------------------------------------- 1 | # Cargaroo 2 | [Cargaroo](https://cargoroo.nl/en/) is an dutch e-cargobike sharing company. 3 | 4 | ### Get Stations 5 | ```GET https://api.cargoroo.eu/v1/stations/assets/?_limit=50&_offset=0&_sort=distance&radius=408394&latitude=52.487&longitude=13.393``` 6 | Returns bike stations with all the available vehicles. 7 | 8 | | URL Param | Explanation | 9 | |-----------|-------------| 10 | | _limit | How many stations are returned | 11 | | _offset | Page through stations - usually n*offset | 12 | | _sort | Sort by distance | 13 | | radius | Search radius - not sure if this works as intended | 14 | | latitude | Latitude | 15 | | longitude | Longitude | 16 | 17 | ### Info on Bike 18 | ```GET https://api.cargoroo.eu/v1/assets/BE-212/``` 19 | Get info on Bike. Replace `BE-212` with the bike ID you want to get info about. 20 | -------------------------------------------------------------------------------- /Go-Sharing.md: -------------------------------------------------------------------------------- 1 | # Go Sharing 2 | [Go Sharing](https://go-sharing.nl) is a scooter sharing service. 3 | 4 | **Base URL**: `https://greenmo.core.gourban-mobility.com/front` 5 | 6 | **Find Scooter Locations**: `https://greenmo.core.gourban-mobility.com/front/vehicles?lat=&lng=&rad=1000` 7 | 8 | **Settings**: `https://greenmo.core.gourban-mobility.com/front/settings?keys=minimumRequiredAppVersion,support.methods,navigation,payment.methods,areas,booking,user.feedback,reservation.extension,user.accounts,errorhandling,user.requiredFields,logins,plugins.auth.google,app.main,screens.login,packages,theme,theme.splashScreen,theme.colors.main,theme.colors.main.dark,theme.colors.map,theme.colors.map.dark,map,map.clusterImage,rental,plugins.onesignal,plugins.activated,vehicles,tutorial,currency,qr.code,rewardStations,radar,additions.insurance` 9 | -------------------------------------------------------------------------------- /Motivate.md: -------------------------------------------------------------------------------- 1 | # Motivate 2 | 3 | [Motivate](https://www.motivateco.com/) builds Bikesharing Systems. This includes the following systems (cities) in the US: 4 | 5 | * Ford GoBike (Bay Area, CA) 6 | * GBFS: `https://gbfs.fordgobike.com/gbfs/gbfs.json` 7 | * Biketown (Portland, OR) 8 | * Capital Bikeshare (Washington, DC) 9 | * Bike Chattanooga (Chattanooga, TN) 10 | * Citi Bike (New York) 11 | * CoGo (Columbus, OH) 12 | * Divvy (Chicago, IL) 13 | * Hubway (Boston, MA) 14 | 15 | All APIs and data are also listed on https://web.archive.org/web/20180924164851/https://www.motivateco.com/use-our-data/ 16 | 17 | Motivate also has undocumented APIs in the GeoJSON format. URLs might be tricky to divine for other cities, but Washington DC & NYC are: 18 | * https://layer.bicyclesharing.net/map/v1/wdc/map-inventory 19 | * https://layer.bicyclesharing.net/map/v1/nyc/map-inventory 20 | -------------------------------------------------------------------------------- /Ufo.md: -------------------------------------------------------------------------------- 1 | # Ufo (defunct) 2 | 3 | [Ufo](https://www.ufoscooters.com/) was a scootersharing company based in Europe. 4 | 5 | There is *no* authentication or special headers and only `GET`-Requests required for the API-Endpoint `https://ufo.frontend.fleetbird.eu/api/prod/v1.06/`. You can request all visible vehicles with `https://ufo.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` or filter them by bbox like `https://ufo.frontend.fleetbird.eu/api/prod/v1.06/map/cars/?lat1=35.0&lat2=44.0&lon1=-35.0&lon2=4.0` or show the 20 nearest vehicles around a location with `https://ufo.frontend.fleetbird.eu/api/prod/v1.06/cars/?lat=40.4021&lon=-3.6857`. 6 | 7 | Polygons with regions and parking restrictions are available on https://ufo.frontend.fleetbird.eu/api/prod/v1.06/territories/all/ (type: 0 looks like free floating region and type: 1 are no parking zones). 8 | 9 | Vehicle types with images are available on https://ufo.frontend.fleetbird.eu/api/prod/v1.06/cars/types/ 10 | -------------------------------------------------------------------------------- /Stella.md: -------------------------------------------------------------------------------- 1 | # stella 2 | 3 | [stella](https://www.ridestella.com) is a scootersharing company based in Europe. 4 | 5 | There is *no* authentication or special headers and only `GET`-Requests required for the API-Endpoint `https://stella.frontend.fleetbird.eu/api/prod/v1.06/`. You can request all(?) vehicles with `https://stella.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` or filter them by bbox like `https://stella.frontend.fleetbird.eu/api/prod/v1.06/map/cars/?lat1=48.5&lat2=49.5&lon1=8.5&lon2=9.5` or show the 20 nearest vehicles around a location with `https://stella.frontend.fleetbird.eu/api/prod/v1.06/cars/?lat=48.8&lon=9.1`. 6 | 7 | Polygons with regions and parking restrictions are available on `https://stella.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` (`type: 0` looks like free floating region and `type: 1` are no parking zones). 8 | 9 | Vehicle types with images are available on `https://stella.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 10 | -------------------------------------------------------------------------------- /Hive.md: -------------------------------------------------------------------------------- 1 | # Hive (defunct) 2 | 3 | [hive](https://www.ridehive.com) was a scootersharing company based in Europe. 4 | 5 | There is *no* authentication or special headers and only `GET`-Requests required for the API-Endpoint `https://hive.frontend.fleetbird.eu/api/prod/v1.06/`. You can request all(?) vehicles with `https://hive.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` or filter them by bbox like `https://hive.frontend.fleetbird.eu/api/prod/v1.06/map/cars/?lat1=46.8339&lat2=55.829&lon1=4.205&lon2=27.653` or show the 20 nearest vehicles around a location with `https://hive.frontend.fleetbird.eu/api/prod/v1.06/cars/?lat=53.4374&lon=9.9955`. 6 | 7 | Polygons with regions and parking restrictions are available on `https://hive.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` (`type: 0` looks like free floating region and `type: 1` are no parking zones). 8 | 9 | Vehicle types with images are available on `https://hive.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 10 | -------------------------------------------------------------------------------- /Zero.md: -------------------------------------------------------------------------------- 1 | # Zero (defunct) 2 | 3 | [Zero](https://gozero.eco/de/) was a scootersharing company based in Germany. 4 | 5 | There is *no* authentication or special headers and only `GET`-Requests required for the API-Endpoint `https://zero.frontend.fleetbird.eu/api/prod/v1.06/`. You can request all visible vehicles with `https://zero.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` or filter them by bbox like `https://zero.frontend.fleetbird.eu/api/prod/v1.06/map/cars/?lat1=46.8339&lat2=55.829&lon1=4.205&lon2=27.653` or show the 20 nearest vehicles around a location with `https://zero.frontend.fleetbird.eu/api/prod/v1.06/cars/?lat=53.4374&lon=9.9955`. 6 | 7 | Polygons with regions and parking restrictions are available on `https://zero.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` (`type: 0` looks like free floating region and `type: 1` are no parking zones). 8 | 9 | Vehicle types with images are available on `https://zero.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 10 | -------------------------------------------------------------------------------- /Scoota.md: -------------------------------------------------------------------------------- 1 | # Scoota (defunct) 2 | 3 | [Scoota](https://www.scoota.online/) was a scootersharing company based in Austria. 4 | 5 | There is *no* authentication or special headers and only `GET`-Requests required for the API-Endpoint `https://scoota.frontend.fleetbird.eu/api/prod/v1.06/`. You can request all visible vehicles with `https://scoota.frontend.fleetbird.eu/api/prod/v1.06/map/cars/` or filter them by bbox like `https://scoota.frontend.fleetbird.eu/api/prod/v1.06/map/cars/?lat1=46.8339&lat2=55.829&lon1=4.205&lon2=27.653` or show the 20 nearest vehicles around a location with `https://scoota.frontend.fleetbird.eu/api/prod/v1.06/cars/?lat=46.6231&lon=14.3080`. 6 | 7 | Polygons with regions and parking restrictions are available on `https://scoota.frontend.fleetbird.eu/api/prod/v1.06/territories/all/` (`type: 0` looks like free floating region and `type: 1` are no parking zones). 8 | 9 | Vehicle types with images are available on `https://scoota.frontend.fleetbird.eu/api/prod/v1.06/cars/types/` 10 | -------------------------------------------------------------------------------- /Bixi.md: -------------------------------------------------------------------------------- 1 | # Bixi (Montréal, QC, Canada) 2 | 3 | A GET request to: 4 | 5 | ``` 6 | https://layer.bicyclesharing.net/map/v1/mtl/stations 7 | ``` 8 | 9 | yields a response looking like: 10 | 11 | ```json 12 | { 13 | "type": "FeatureCollection", 14 | "features": [ 15 | { 16 | "type": "Feature", 17 | "geometry": { 18 | "type": "Point", 19 | "coordinates": [ 20 | -73.55650842189789, 21 | 45.51035067563653 22 | ] 23 | }, 24 | "properties": { 25 | "station_id": "1", 26 | "name": "Métro Champ-de-Mars (Sanguinet / Viger)", 27 | "terminal": "6001", 28 | "capacity": 33, 29 | "bikes_available": 11, 30 | "docks_available": 22, 31 | "bikes_disabled": 0, 32 | "docks_disabled": 0, 33 | "renting": true, 34 | "returning": true, 35 | "installed": true, 36 | "last_reported": 1533227391, 37 | "icon_pin_bike_layer": "pin-bike-green-half", 38 | "icon_pin_dock_layer": "pin-dock-green-most", 39 | "icon_dot_bike_layer": "dot-green", 40 | "icon_dot_dock_layer": "dot-green", 41 | "valet_status": "none" 42 | } 43 | } 44 | ] 45 | } 46 | ``` 47 | 48 | The array of "Features" contains status on each Bixi bike sharing station in Montréal. 49 | -------------------------------------------------------------------------------- /Onzo.md: -------------------------------------------------------------------------------- 1 | # Onzo (bikes, Location: New Zealand) (defunct) 2 | 3 | Simple GET request: https://app.onzo.co.nz/nearby/-36.848123/174.765588/50.0 4 | 5 | **Method:** GET 6 | 7 | **Base url:** `https://app.onzo.co.nz/nearby///` 8 | 9 | **URL-Params:** 10 | 11 | | Parameters | Required | 12 | | ---------- | :--------: | 13 | | latitude | X | 14 | | Longitude | X | 15 | | Search-radius | X | 16 | 17 | --- 18 | 19 | **Example request** (Auckland City): 20 | 21 | `curl https://app.onzo.co.nz/nearby/-36.848123/174.765588/50.0` 22 | 23 | Response: 24 | 25 | *response.data is an Array of bike-data Objects* 26 | ```json 27 | { 28 | "code": 0000, 29 | "data": [{}, {}, {}], 30 | "msg": "successful" 31 | } 32 | ``` 33 | Single bike-data object: 34 | ```json 35 | { 36 | "battery": 52, 37 | "chargeVoltage": 2, 38 | "createTime": 1509724800000, 39 | "direction": 346, 40 | "height": 0, 41 | "iccid": "89860117750007958102", 42 | "id": 199, 43 | "isLock": 0, 44 | "latitude": -36.843868, 45 | "locationMode": 10, 46 | "lockType": 2, 47 | "longitude": 174.760133, 48 | "lstatus": 0, 49 | "mac": "c240f333b3d9", 50 | "modelNum": "0000000000000000000000000000000000000000", 51 | "producid": "81001181", 52 | "psignal": 26, 53 | "pstatus": 3932166, 54 | "speed": 0, 55 | "unlockedTimes": 456, 56 | "updateTime": 1544345698000, 57 | "voltage": 369 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /EnTur.md: -------------------------------------------------------------------------------- 1 | # EnTur (Aggregate endpoint, Norway) 2 | The Entur endpoint is an aggregate endpoint that returns position of scooters in Norway. 3 | Examples of supported companies in the Oslo area are Zvipp, Tier and Voi. However the endpoint is not limited to Oslo. 4 | 5 | ## Requesting Data 6 | ### HTTP GET: 7 | Example get request: 8 | `GET https://api.entur.io/mobility/v1/scooters?lat=59.909&lon=10.746&range=200&max=20 9 | ` 10 | ### Parameters: 11 | |Parameter |Description | 12 | |--|--| 13 | | lat | Latitude| 14 | |lon|Longitude| 15 | |range|Radius in meters (optional), 200 default| 16 | |max| Maximum amount of scooters (optional), 20 default| 17 | 18 | ## Example response 19 | ```json 20 | [ 21 | { 22 | "id": "cec80aa5-5497-4b82-b186-6bccab7c87e6", 23 | "operator": "voi", 24 | "lat": 59.908581, 25 | "lon": 10.745911, 26 | "code": "-", 27 | "battery": 70 28 | }, 29 | { 30 | "id": "10fc0759-20ec-4512ab8dd1b3-1ca9adcb", 31 | "operator": "zvipp", 32 | "lat": 59.910015, 33 | "lon": 10.747366, 34 | "code": "ZVS1332", 35 | "battery": 33 36 | }, 37 | { 38 | "id": "10fc0759-20ec-4512ab8dd1b3-1ca9adcb", 39 | "operator": "zvipp", 40 | "lat": 59.910015, 41 | "lon": 10.747366, 42 | "code": "ZVS1332", 43 | "battery": 33 44 | } 45 | ] 46 | ``` 47 | 48 | ## Source 49 | More information about EnTurs endpoints can be found at: 50 | https://developer.entur.org/ 51 | and at: 52 | https://github.com/entur 53 | -------------------------------------------------------------------------------- /Donkey.md: -------------------------------------------------------------------------------- 1 | # Donkey Republic 2 | 3 | - Base URL: `https://stables.donkey.bike/api/public/` 4 | - Header: `"Accept": "application/com.donkeyrepublic.v7"` 5 | 6 | ## Authentication 7 | For the endpoints known so far, no authentication is required. 8 | 9 | ## Endpoints 10 | As of writing this, only endpoints getting information are known. 11 | 12 | ### Get hubs 13 | Hubs are the gathering points, where the vehicles wait to be rented. 14 | 15 | - Base URL: `nearby` 16 | - Description: List all hubs within a bounding box 17 | 18 | Parameter | Value(s) | Description 19 | --------------|-----------------|------------------------------------------- 20 | `top_right` | `ne_lat,ne_lon` | North-East coordinates of the bounding box 21 | `bottom_left` | `sw_lat,sw_lon` | South-West coordinates of the bounding box 22 | `filter_type` | `box` | Unknown, but mandatory. 23 | 24 | #### Example 25 | ``` 26 | curl -X GET \ 27 | --header 'Accept:application/com.donkeyrepublic.v7' \ 28 | "https://stables.donkey.bike/api/public/nearby?top_right=52.51778%2C13.38200&bottom_left=52.51509%2C13.37627&filter_type=box" 29 | ``` 30 | 31 | ### Get more information about hub 32 | 33 | - Base URL: `hubs/{HUB_ID}/{type}`, where `HUB_ID` is the ID from the `Get hubs` endpoint and `type` is either `bike`, `ebike`, `escooter` or `trailer`. 34 | 35 | #### Example 36 | ``` 37 | curl -X GET \ 38 | --header 'Accept:application/com.donkeyrepublic.v7' \ 39 | "https://stables.donkey.bike/api/public/hubs/hub_6004/bike" 40 | ``` 41 | -------------------------------------------------------------------------------- /Lidl-Bike.md: -------------------------------------------------------------------------------- 1 | # Lidl-Bike (defunct) 2 | 3 | Lidl-Bike uses a RPC endpoint for their requests. 4 | As of writing this, only one procedure is known, `listBikes`. 5 | 6 | ## Authentication 7 | For listing their bikes, authentication is not required. 8 | 9 | ## RPC Endpoint 10 | 11 | - Base URL: `https://www.lidl-bike.de/de/rpc` 12 | - Method: `POST` 13 | - Data: See tables below, has to be encoded as JSON. 14 | 15 | ### Root Object 16 | 17 | Key | Value(s) | Description 18 | ---------|---------------------|----------------------------------------------------- 19 | `method` | `Map.listBikes` | The RPC method for listing bikes (only known so far) 20 | `params` | List of parameters* | Parameters for the given method 21 | 22 | ### Params for `Map.listBikes` method 23 | 24 | Param | Value(s) | Description 25 | -----------|---------------|--------------------------------------------------- 26 | `lat` | latitude | Center latitude for bike search 27 | `long` | longitude | Center longitude for bike search 28 | `maxItems` | 0 < max < 100 | The number of results (limit is 100) 29 | `radius` | meters | Radius around center in meters for the search 30 | `name` | `drag` | This is mandatory. I have no idea what this means. 31 | 32 | ## Examples 33 | ### Get 100 bikes within 400 meters 34 | 35 | ``` 36 | curl --request POST \ 37 | --url https://www.lidl-bike.de/de/rpc \ 38 | --data '{"method":"Map.listBikes","params":[{"lat":52.52581526868782,"long":13.433961158935514,"maxItems":100,"radius":400,"name":"drag"}]}' 39 | ``` 40 | -------------------------------------------------------------------------------- /Cambio.md: -------------------------------------------------------------------------------- 1 | # Cambio 2 | 3 | [Cambio](https://https://www.cambio-carsharing.de/) is a carsharing company based in Germany and [Belgium](https://www.cambio.be/en-bxl). 4 | 5 | There is *no* authentication or special headers and only `GET`-Requests required for their region-specific API-Endpoints: 6 | 7 | 8 | Country | City / Region | Eurocode | Link 9 | --- | --- | --- | --- 10 | DE | Aachen | AAC | https://cwapi.cambio-carsharing.com/pub/AAC/stations 11 | DE | Berlin | BRL | https://cwapi.cambio-carsharing.com/pub/BRL/stations 12 | DE | Bielefeld | BIL | https://cwapi.cambio-carsharing.com/pub/BIL/stations 13 | DE | Bremen | BRE | https://cwapi.cambio-carsharing.com/pub/BRE/stations 14 | DE | Flensburg | FLB | https://cwapi.cambio-carsharing.com/pub/FLB/stations 15 | DE | Hamburg | HAM | https://cwapi.cambio-carsharing.com/pub/HAM/stations 16 | DE | Köln | KOE | https://cwapi.cambio-carsharing.com/pub/KOE/stations 17 | DE | Lüneburg | LBG | https://cwapi.cambio-carsharing.com/pub/LBG/stations 18 | DE | Oldenburg | OLD | https://cwapi.cambio-carsharing.com/pub/OLD/stations 19 | DE | Saarbrücken | SAB | https://cwapi.cambio-carsharing.com/pub/SAB/stations 20 | DE | Wuppertal | WUP | https://cwapi.cambio-carsharing.com/pub/WUP/stations 21 | BE | Brussels | BXL | https://cwapi.cambio-carsharing.com/pub/BXL/stations 22 | BE | Wallonia | WAL | https://cwapi.cambio-carsharing.com/pub/WAL/stations 23 | BE | Flanders | VLA | https://cwapi.cambio-carsharing.com/pub/VLA/stations 24 | 25 | Deeplinks follow this pattern, where BRE is the Eurocode, 2936 the station ID. 26 | https://www.cambio-carsharing.de/station/BRE/2936.html 27 | -------------------------------------------------------------------------------- /Call-a-Bike.md: -------------------------------------------------------------------------------- 1 | # Call a Bike 2 | 3 | Call-a-Bike has [historic datasets](http://data.deutschebahn.com/dataset/data-call-a-bike) OpenData on the DeutscheBahn OpenData portal under CC-BY License. 4 | 5 | You can use the [Flinkster API](http://data.deutschebahn.com/dataset/flinkster-api) with `providernetwork=2` to access Call-a-Bike live Data. You need to register on [developer.deutschebahn.com](https://developer.deutschebahn.com/store/site/pages/sign-up.jag) to get a free, unlimited API Key (Zugangstoken). 6 | 7 | Example Request: `https://api.deutschebahn.com/flinkster-api-ng/v1/bookingproposals?lat=48.15&lon=11.5&radius=5000&limit=100&providernetwork=2` – You also have to set the `Authorization` header to `Bearer ` 8 | 9 | * Paramter `limit` max value is `100`, but you can use `offset` to request more 10 | * Paramter `radius` is the searchradius in meters, max value is `10000`, min value is `100`, default `500`, 11 | * You can also add parameter `expand` to `rentalobject,price` to get vehicle and price info 12 | 13 | There is also a [Documentation PDF](https://developer.deutschebahn.com/store/site/themes/responsive/templates/api/documentation/download.jag?tenant=carbon.super&resourceUrl=/registry/resource/_system/governance/apimgt/applicationdata/provider/DBOpenData/Flinkster_API_NG/v1/documentation/files/Schnittstellenspezifikation_FlinksterApiNG.pdf) (german only), and you can use the [API-console (3rd tab)](https://developer.deutschebahn.com/store/apis/info?name=Flinkster_API_NG&version=v1&provider=DBOpenData) 14 | 15 | If you need GBFS, maybe this [flinkster2gbfs](https://github.com/mfdz/flinkster2gbfs) adapter can help. 16 | -------------------------------------------------------------------------------- /Pony.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | Pony logo 4 |
5 | 6 | # Pony 7 | 8 | **Base URL**: `https://api.getapony.com` 9 | 10 | # Cities (aka regions) 11 | ## Get nearest region 12 | 13 | **Path**: `/georegion/resolve` 14 | 15 | **Method**: `GET` 16 | 17 | **Parameters**: 18 | 19 | | Parameters | Value | Mandatory | 20 | | ---------- | ------------------------ | :-------: | 21 | | latitude | latitude | X | 22 | | longitude | longitude | X | 23 | 24 | ## Get regions list 25 | 26 | **Path**: `/georegion/all/` 27 | 28 | 29 | **Method**: `GET` 30 | 31 | --- 32 | # Get scooters and bikes informations (GBFS) 33 | 34 | ## Main GBFS url 35 | `https://gbfs.getapony.com/v1/{city}/en/gbfs.json` 36 | 37 | `city` : provide city name (aka region) 38 | 39 | ## Get the different types of vehicles available in the city (bike, scooter, ...) 40 | 41 | **URL**: `https://gbfs.getapony.com/v1/{city}/en/vehicle_types.json` 42 | 43 | **Method**: `GET` 44 | ## Get prices by vehicle type 45 | 46 | **URL**: `https://gbfs.getapony.com/v1/{city}/en/system_pricing_plans.json` 47 | 48 | 49 | **Method**: `GET` 50 | ## Get real-time information on available vehicles 51 | 52 | **URL**: `https://gbfs.getapony.com/v1/{city}/en/free_bike_status.json` 53 | 54 | **Method**: `GET` 55 | 56 | ## Get geofencing zones 57 | 58 | **URL**: `https://gbfs.getapony.com/v1/{city}/en/geofencing_zones.json` 59 | 60 | **Method**: `GET` 61 | ## Get vehicle parking locations 62 | 63 | **URL**: `https://gbfs.getapony.com/v1/{city}/en/station_information.json` 64 | 65 | **Method**: `GET` 66 | -------------------------------------------------------------------------------- /Ryde.md: -------------------------------------------------------------------------------- 1 | # Ryde 2 | [Ryde](https://www.ryde-technology.com) is an e-scooter sharing firm operating in Scandinavia. 3 | 4 | To send requests to the API, no authentication is needed. However, you do need a cityId depending in which city you are. You can find an table at the end of this document. 5 | 6 | ### Get Scooters 7 | ``` 8 | POST https://qw-test.ryde.vip/appRyde/getNearScooters 9 | Body: 10 | iotLa: 63.4335711 11 | iotLo: 10.3983865 12 | nearRadius: 10 13 | cityId: 5 14 | ``` 15 | 16 | `iotLa` and `iotLo` are your coordinates. Choose `cityId` from the list below. 17 | 18 | 19 | | # | City | 20 | | --- | ------------------------- | 21 | | 1 | Oslo | 22 | | 5 | Trondheim | 23 | | 6 | Kristiansand | 24 | | 7 | Stavanger | 25 | | 11 | Karlstad | 26 | | 12 | Halmstad | 27 | | 15 | Bergen | 28 | | 25 | Gävle | 29 | | 26 | Tønsberg | 30 | | 27 | Växjö | 31 | | 28 | Havna(Lillestrøm) | 32 | | 29 | Stockholm | 33 | | 30 | Umeå | 34 | | 31 | Helsinki | 35 | | 32 | Göteborg | 36 | | 33 | Sundsvall | 37 | | 34 | Turku | 38 | | 35 | Tampere | 39 | | 36 | Bodø | 40 | | 37 | Örnsköldsvik | 41 | | 38 | Luleå | 42 | | 39 | Jyväskylä | 43 | | 40 | Pori | 44 | | 41 | Oulu | 45 | | 43 | Drammen | 46 | | 44 | Skien | 47 | | 45 | Sandefjord | 48 | | 46 | Fredrikstad & Sarpsborg | 49 | | 47 | Tromsø | 50 | -------------------------------------------------------------------------------- /VeloAntwerpen.md: -------------------------------------------------------------------------------- 1 | # Velo Antwerpen 2 | [Velo Antwerpen](https://velo-antwerpen.be) is a bike sharing service that operates in Antwerp. The technology used is the Clear Channel SmartBike system. 3 | 4 | **Base URL**: `https://www.velo-antwerpen.be/availability_map` 5 | 6 | ## Get stations with available bikes and free slots 7 | **Path**: `/getJsonObject` 8 | 9 | **Method**: `GET` 10 | 11 | Response: 12 | ``` 13 | [ 14 | { 15 | "address": "Koningin Astridplein", 16 | "bikes": "27", 17 | "id": "001", 18 | "lat": "51.217820000000000000", 19 | "lon": "4.420650000000000000", 20 | "name": "001- Centraal Station - Astrid", 21 | "slots": "6", 22 | "stationType": "BIKE", 23 | "status": "OPN" 24 | }, 25 | { 26 | "address": "Koningin Astridplein", 27 | "bikes": "1", 28 | "id": "002", 29 | "lat": "51.21755807739536", 30 | "lon": "4.420725651433662", 31 | "name": "002- Centraal Station - Astrid 2", 32 | "slots": "34", 33 | "stationType": "BIKE", 34 | "status": "OPN" 35 | }, 36 | { 37 | "address": "Kievitplein", 38 | "bikes": "8", 39 | "id": "003", 40 | "lat": "51.213085853125690000", 41 | "lon": "4.421771338842944000", 42 | "name": "003- Centraal station Kievit 2", 43 | "slots": "22", 44 | "stationType": "BIKE", 45 | "status": "OPN" 46 | }, 47 | { 48 | "address": "De Keyserlei", 49 | "bikes": "0", 50 | "id": "004", 51 | "lat": "51.217363888888890000", 52 | "lon": "4.420094444444445000", 53 | "name": "004- De Keyserlei", 54 | "slots": "21", 55 | "stationType": "BIKE", 56 | "status": "OPN" 57 | }, 58 | { 59 | "address": "Lange Kievitstraat", 60 | "bikes": "12", 61 | "id": "005", 62 | "lat": "51.213010729744000000", 63 | "lon": "4.421732916607050000", 64 | "name": "005- Centraal Station / Kievit", 65 | "slots": "24", 66 | "stationType": "BIKE", 67 | "status": "OPN" 68 | } 69 | ] 70 | ``` 71 | -------------------------------------------------------------------------------- /Spin.md: -------------------------------------------------------------------------------- 1 | # Spin (Bikes and Scooters) 2 | [Spin](https://www.spin.pm/) is a Bike and E-Scotter sharing service in the US. 3 | 4 | To Access the API, you need to generate an Auth token. This requires only a verifiable email address. Make a POST request to `https://web.spin.pm/api/v1/magic_links` with the body: 5 | ``` 6 | {"email": ""} 7 | ``` 8 | 9 | From there, you will need to find the token within your email. This token needs to be sent with a POST request to `https://web.spin.pm/api/v1/auth_tokens` with the body: 10 | ``` 11 | { 12 | "grant_type": "magic_link", 13 | "magic_link": { 14 | "email": "", 15 | "token": "" 16 | } 17 | } 18 | ``` 19 | 20 | This request returns a JSON that looks like this: `{"jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyVW5pcXVlS2V5IjoiNDE2NWZmYmI0ZDVjODA0ODc5OTMzMTIwYzRiMGQ5NmYiLCJyZWZlcnJhbENvZGUiOm51bGwsImlzQXBwbGVQYXlEZWZhdWx0IjpmYWxzZSwiaXNBZG1pbiI6ZmFsc2UsImlzQ2hhcmdlciI6ZmFsc2UsImlzQ29ycG9yYXRlIjpmYWxzZSwiYXV0b1JlbG9hZCI6ZmFsc2UsImNyZWRpdEJhbGFuY2UiOjAsInRvdGFsVHJpcENvdW50IjowLCJzcGluVW5saW1pdGVkIjpmYWxzZSwic3BpblVubGltaXRlZE5leHRCaWxsaW5nQ3ljbGUiOm51bGwsInNwaW5VbmxpbWl0ZWRNZW1iZXJzaGlwIjpudWxsLCJpc1F1YWxpZmllZEZvclJpZGUiOmZhbHNlLCJyYXRlRGlzY291bnRQZXJjZW50YWdlIjowLCJ0eXBlIjoiZGV2aWNlIiwiZXhwIjoxNTI3NzkxMTMwfQ.p11U0XDfwGiI2HgM3m4FxQepFNmzBlouEdPHAGcXVLw","refreshToken":"2cf6fd9775313f975ef8b077abab5030","existingAccount":false}` 21 | 22 | The important part is the `jwt`-Token. This Token is used for Authentication. 23 | This tokens invalidates after some minutes. 24 | 25 | To get the position of vehicles so a GET-Request to: 26 | `https://web.spin.pm/api/v3/vehicles?lng=-77.0146489&lat=38.8969363&distance=&mode=` 27 | User Header `Authorization`: `Bearer ` to Authenticate and use the `jwt`-Token we got from the Auth request. 28 | 29 | You will get something like this as return JSON `{"vehicles":[{"lat":37.69247,"lng":-122.46595,"last4":"3595","vehicle_type":"bicycle","batt_percentage":null,"rebalance":null}, … ]}` 30 | -------------------------------------------------------------------------------- /Evhcle.md: -------------------------------------------------------------------------------- 1 | # Evhcle 2 | 3 | [Evhcle](https://evhcle.com/) is an custom rental system for companys and communitys (e.g. hotels or villages). 4 | 5 | There is *no* authentication or special headers, and only `GET`-Requests. 6 | 7 | 8 | ## Directory 9 | Get (some) available endpoints. 10 | 11 | ``` 12 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/ 13 | 14 | { 15 | "cars": "https://evhcle.frontend.fleetbird.eu/api/prod/v1.06/cars/", 16 | "map/cars": "https://evhcle.frontend.fleetbird.eu/api/prod/v1.06/map/cars/", 17 | "locations": "https://evhcle.frontend.fleetbird.eu/api/prod/v1.06/locations/" 18 | } 19 | ``` 20 | The version number does not appear to affect the actual data returned, however as the app uses 3.06 well stick with that. 21 | 22 | ## Territories 23 | Get buisness-areas. 24 | ``` 25 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/territories/all 26 | ``` 27 | `type 0` is the free floating parking zone. This zone is usually just a few meters arround the company cooperating with evhcle. 28 | 29 | ## Vehicle locations 30 | Request available vehicles. Detailed information but no filtering. See below. 31 | ``` 32 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/cars/ 33 | ``` 34 | 35 | ## Vehicle locations (filtering) 36 | Request available vehicles. 37 | ``` 38 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/map/cars 39 | ``` 40 | 41 | You can filter the returned vehicles: 42 | ``` 43 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/map/cars/?lat1=48.1&lat2=48.2&lon1=11.4&lon2=11.5 44 | ``` 45 | 46 | # Get vehicle 47 | Get information about an car by id. 48 | ``` 49 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/cars/{ID} 50 | ``` 51 | For example: 52 | ``` 53 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/cars/51 54 | ``` 55 | 56 | ## Vehicle Types 57 | Returns vehicle types with images. 58 | ``` 59 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/cars/types 60 | ``` 61 | 62 | ## Locations 63 | All citys/zones/companys they are in/cooperate with. 64 | ``` 65 | GET https://evhcle.frontend.fleetbird.eu/api/prod/v3.02/locations 66 | ``` 67 | -------------------------------------------------------------------------------- /Avocargo.md: -------------------------------------------------------------------------------- 1 | # Avocargo (defunct) 2 | 3 | [Avocargo](https://www.avocargo.one/) was a cargo-bike sharing company based in Germany. 4 | 5 | There is *no* authentication or special headers, and only `GET`-Requests. 6 | 7 | 8 | ## Directory 9 | Get (some) available endpoints. 10 | 11 | ``` 12 | GET https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/ 13 | 14 | { 15 | "cars": "https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/cars/", 16 | "map/cars": "https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/map/cars/", 17 | "locations": "https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/locations/" 18 | } 19 | ``` 20 | 21 | ## Territories 22 | Get no-parking zones. 23 | ``` 24 | GET https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/territories/all/ 25 | ``` 26 | Result see [here](https://gist.github.com/BastelPichi/ffbc239707fbf08390e35c99d1b5f6e3). (12.12.2022) 27 | 28 | `type 0` looks like free floating region, `type: 1` menas no parking zone. 29 | 30 | ## Vehicle locations 31 | Request available vehicles. Detailed information but no filtering. See below. 32 | ``` 33 | GET https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/cars/ 34 | ``` 35 | Result see [here](https://gist.github.com/BastelPichi/dd2dc84299dfad495f64523cd00d1017). (12.12.2022) 36 | 37 | ## Vehicle locations (filtering) 38 | Request available vehicles. 39 | ``` 40 | GET https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/map/cars 41 | ``` 42 | Result see [here](https://gist.github.com/BastelPichi/a7ecd669f586b73bd90e19d7c8b3837f). (12.12.2022) 43 | 44 | You can filter the returned vehicles: 45 | ``` 46 | GET https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/map/cars/?lat1=52.3&lat2=52.5&lon1=13.1&lon2=13.4 47 | ``` 48 | Result see [here](https://gist.github.com/BastelPichi/260d5bc5620bb2aaecc201766c6347b6). (12.12.2022) 49 | 50 | ## Vehicle Types 51 | Returns vehicle types with images. 52 | ``` 53 | GET https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/cars/types 54 | ``` 55 | Result see [here](https://gist.github.com/BastelPichi/aba977b45acdfdb03496b3dbba59fad9). (12.12.2022) 56 | 57 | ## Locations 58 | Unclear what this does. Maybe their warehouses? 59 | ``` 60 | GET https://avocargo.frontend.fleetbird.eu/api/prod/v1.06/locations/ 61 | ``` 62 | Result see [here](https://gist.github.com/BastelPichi/d3b5ae49173fdcd1aeabae26fe29d8b4). (12.12.2022) 63 | -------------------------------------------------------------------------------- /Mobike.md: -------------------------------------------------------------------------------- 1 | # Mobike (defunct) 2 | 3 | **Base url**: `http://app.mobike.com/api` 4 | 5 | ## Methods 6 | 7 | ### Get bicycles by lat/lng 8 | 9 | **Method**: `POST` 10 | 11 | **Path**: `/nearby/v4/nearbyBikeInfo` 12 | 13 | **Header**: 14 | 15 | | Header | Value | Mandatory | 16 | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------: | 17 | | content-type | application/x-www-form-urlencoded | X | 18 | | platform | 1 | X | 19 | | user-agent | Mozilla/5.0 (Android 7.1.2; Pixel Build/NHG47Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.9 NTENTBrowser/3.7.0.496 (IWireless-US) Mobile Safari/537.36 (or whatever) | X | 20 | 21 | **Parameters**: 22 | 23 | | Parameters | Descriptions | Mandatory | 24 | | ---------- | ------------ | :-------: | 25 | | latitude | latitude | X | 26 | | Longitude | Longitude | X | 27 | 28 | **Example** 29 | 30 | ```bash 31 | curl --request POST \ 32 | --url http://global-n-mobike-g.mobike.com/api/nearby/v4/nearbyBikeInfo \ 33 | --header 'platform: 1' \ 34 | --header 'Content-Type: application/x-www-form-urlencoded' \ 35 | --header 'User-Agent: Mozilla/5.0 (Android 7.1.2; Pixel Build/NHG47Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.9 NTENTBrowser/3.7.0.496 (IWireless-US) Mobile Safari/537.36' \ 36 | --data 'latitude=52.53&longitude=13.38' 37 | ``` 38 | 39 | ```JSON 40 | { 41 | "code": 0, 42 | "message": "", 43 | "bike": [ 44 | { 45 | "distId": "A816046038", 46 | "distX": 13.379844, 47 | "distY": 52.529358, 48 | "distNum": 1, 49 | "distance": "72", 50 | "bikeIds": "A816046038#", 51 | "biketype": 2, 52 | "type": 0, 53 | "boundary": null, 54 | "operateType": 2 55 | } 56 | ], 57 | "mpl": [], 58 | "biketype": 0, 59 | "radius": 500, 60 | "autoZoom": false, 61 | "hasRedPacket": 0 62 | } 63 | ``` 64 | 65 | ## Implementations 66 | 67 | * https://www.npmjs.com/package/@multicycles/mobike (JavaScript) 68 | -------------------------------------------------------------------------------- /Dott.md: -------------------------------------------------------------------------------- 1 | # Dott 2 | 3 | ## Introduction 4 | 5 | Dott provides an API to access available cities by countries and information about scooters and bikes in those cities. The API uses the GBFS (General Bikeshare Feed Specification) format to provide standardized data. 6 | 7 | ## Table of Contents 8 | 9 | 1. [Get Available Cities by Countries](#get-available-cities-by-countries) 10 | 2. [Get Scooters and Bikes Information (GBFS)](#get-scooters-and-bikes-information-gbfs) 11 | - [Main GBFS URL](#main-gbfs-url) 12 | - [Get Vehicle Types](#get-vehicle-types) 13 | - [Get Prices by Vehicle Type](#get-prices-by-vehicle-type) 14 | - [Get Real-Time Vehicle Availability](#get-real-time-vehicle-availability) 15 | - [Get Geofencing Zones](#get-geofencing-zones) 16 | - [Get Vehicle Parking Locations](#get-vehicle-parking-locations) 17 | 18 | ## Get Available Cities by Countries 19 | 20 | To get the list of available cities by countries, use the following endpoint: 21 | 22 | **URL**: `https://gbfs.api.ridedott.com/public/v2/countries/{countries}/gbfs.json` 23 | 24 | **Countries**: fr, de, ... (check the website for more countries) 25 | 26 | --- 27 | 28 | ## Get Scooters and Bikes Information (GBFS) 29 | 30 | ### Main GBFS URL 31 | 32 | The main GBFS URL provides general information about the city's bike and scooter sharing system. 33 | 34 | **URL**: `https://gbfs.api.ridedott.com/public/v2/{city}/gbfs.json` 35 | 36 | **Parameter**: 37 | - `city`: Provide the city name. 38 | 39 | ### Get Vehicle Types 40 | 41 | To get the different types of vehicles available in the city (bike, scooter, etc.), use the following endpoint: 42 | 43 | **URL**: `https://gbfs.api.ridedott.com/public/v2/{city}/vehicle_types.json` 44 | 45 | **Method**: `GET` 46 | 47 | ### Get Prices by Vehicle Type 48 | 49 | To get the pricing information for different vehicle types, use the following endpoint: 50 | 51 | **URL**: `https://gbfs.api.ridedott.com/public/v2/{city}/system_pricing_plans.json` 52 | 53 | **Method**: `GET` 54 | 55 | ### Get Real-Time Vehicle Availability 56 | 57 | To get real-time information on available vehicles, use the following endpoint: 58 | 59 | **URL**: `https://gbfs.api.ridedott.com/public/v2/{city}/free_bike_status.json` 60 | 61 | **Method**: `GET` 62 | 63 | ### Get Geofencing Zones 64 | 65 | To get the geofencing zones for the city, use the following endpoint: 66 | 67 | **URL**: `https://gbfs.api.ridedott.com/public/v2/{city}/geofencing_zones.json` 68 | 69 | **Method**: `GET` 70 | 71 | ### Get Vehicle Parking Locations 72 | 73 | To get the parking locations for vehicles, use the following endpoint: 74 | 75 | **URL**: `https://gbfs.api.ridedott.com/public/v2/{city}/station_information.json` 76 | 77 | **Method**: `GET` 78 | -------------------------------------------------------------------------------- /Tier.md: -------------------------------------------------------------------------------- 1 | 2 | # Tier 3 | 4 | [Tier](https://www.tier.app/) is a scootersharing company based in Europe. 5 | 6 | To authenticate you need to add `X-Api-Key: bpEUTJEBTf74oGRWxaIcW7aeZMzDDODe1yBoSxi2` in your header of the API-Endpoint `https://platform.tier-services.io`. 7 | 8 | Official docs: https://api-documentation.tier-services.io/ 9 | 10 | ## v1 11 | 12 | ### Get Vehicles 13 | Vehicles within a range: `GET https://platform.tier-services.io/v1/vehicle?lat=48.1&lng=16.3&radius=5000` 14 | Vehicles within a zone: `GET https://platform.tier-services.io/v1/vehicle?zoneId=BERLIN` 15 | Vehicles by Code (encoded in the vehicle QR-code): `GET https://platform.tier-services.io/v1/vehicle/code/12345` 16 | Vehicles by UUID: `GET https://platform.tier-services.io/v1/vehicle/` 17 | 18 | ### Make vehicle flash 19 | `POST https://platform.tier-services.io/v1/vehicle//flash` 20 | 21 | ### Get vehicles with battery swap option 22 | This endpoint needs an X-Firebase-Auth: Bearer instead of X-Api-Key 23 | `GET https://platform.tier-services.io/v1/user-battery-swap/config?lat=&lng=&radius=` 24 | 25 | ### Zones 26 | Get zones by type: `GET https://platform.tier-services.io/v1/zone?type=` 27 | Get subzones by type: `GET https://platform.tier-services.io/v1/zone/BERLIN/subzone?type=constrained` 28 | 29 | Zone Types: 30 | |Parameter| Description | 31 | |--|--| 32 | | `root` | The zone to which all other zone types are attached, e.g. BERLIN | 33 | | `business` | An area in which customers can rent a vehicle | 34 | | `warehouse` | An area in which scooters are in MAINTENANCE | 35 | | `constrained` | An area which may not allow parking or only allow reduced speed or both (may overlap with the business zone) |An area which may not allow parking or only allow reduced speed or both (may overlap with the business zone)| 36 | | `parking` | An area where customers may park their vehicle only in designated areas. 37 | 38 | Get zones near location: `GET https://platform.tier-services.io/v1/zone?lat=&lng=` 39 | Validate zone for parking: `GET https://platform.tier-services.io/v1/zone/validate-constraint?lat=&lng=` 40 | 41 | 42 | ### Pricing 43 | `GET https://platform.tier-services.io/v2/pricing?vehicleId=52911ecb-60a4-2535-2875-fb443eb5409f` 44 | 45 | ## v2 46 | 47 | Only difference seems to be `emoped` as an additional vehicle type. 48 | Get all vehicles within a zone: `GET https://platform.tier-services.io/v2/vehicle?zoneId=BERLIN` 49 | 50 | ### Get current subscription 51 | This endpoint needs an X-Firebase-Auth: Bearer instead of X-Api-Key 52 | `GET https://platform.tier-services.io/v2/subscription` 53 | 54 | ## v3 55 | Passes and flatrate offers are provided at a v3 API endpoint but not at the /v2/pricing endpoint. All offers available in a zone are at /article. For this endpoint you need to provide a X-Firebase-Auth: Bearer header instead of a X-Api-Key. 56 | `GET https://platform.tier-services.io/v3/article?zoneId=` 57 | -------------------------------------------------------------------------------- /Obike.md: -------------------------------------------------------------------------------- 1 | # Obike (defunct) 2 | 3 | **Base url**: `https://mobile.o.bike/api/v2` 4 | 5 | In this documentation we use the `3.2.4` (or `324`) version. 6 | 7 | `oBiOSX4buhBMG` and `oBiOSMYFUzLed` are constantes 8 | 9 | ## Methods 10 | 11 | ### Get bicycles by lat/lng 12 | 13 | **Method**: `POST` 14 | 15 | **Path**: `/bike/list` 16 | 17 | **Header**: 18 | 19 | | Header | Value | Mandatory | 20 | | ------------- | -------------------------------------------------- | :-------: | 21 | | content-type | application/json | X | 22 | | version | 3.2.4 | X | 23 | | platform | iOS | X | 24 | | User-Agent | DOTCBike/${3.2.4} (iPhone; iOS 11.2.6; Scale/2.00) | | 25 | | Authorization | | | 26 | 27 | **Parameters**: 28 | 29 | :warning: Body parameters should be encoded and set body `value` field :warning: 30 | 31 | | Parameters | Descriptions | Mandatory | 32 | | ----------- | --------------------------------------------------- | :-------: | 33 | | latitude | Latitude | X | 34 | | longitude | Longitude | X | 35 | | countryCode | 49 (should be an other code, doesn't really matter) | X | 36 | | deviceId | User token (get by auth) | | 37 | | dateTime | Timestamp + '.000000' | | 38 | 39 | **Encoding** 40 | 41 | 1. JSON stringify data, remove all spaces and line breaks 42 | 2. concat (,`&oBiOSX4buhBMG324`) 43 | 3. concat (,'&',SHA1()) 44 | 4. encode the result of 3 in AES128 with `oBiOSMYFUzLed324` as key and `1234567890123456` as IV and return it in hex 45 | 46 | **Example** 47 | 48 | with simple data 49 | 50 | ```JSON 51 | { 52 | "countryCode": "33", 53 | "longitude": 2.369336, 54 | "latitude": 48.852775 55 | } 56 | ``` 57 | 58 | ```bash 59 | curl --request POST \ 60 | --url https://mobile.o.bike/api/v2/bike/list \ 61 | --header 'content-type: application/json' \ 62 | --header 'platform: iOS' \ 63 | --header 'version: 3.2.4' \ 64 | --data '{ "value": "1152a752ff6623d245263cc1b500d38306d974dbf57110d8ace721bbaa34511e540976066d0d2d419172a34d7e1283b277d6a17091fd79d38e919ece60608c841378fef75ddf9f434067c768e88983d516a79ef7645c5d0150a3252da11ae30e20a74c8e7243c0fa1f6504536542c43c" }' 65 | ``` 66 | 67 | ```JSON 68 | { 69 | "data": { 70 | "iconUrl": null, 71 | "list": [ 72 | { 73 | "id": "033004123", 74 | "longitude": 2.371797, 75 | "latitude": 48.853308, 76 | "imei": "3166756E63746937", 77 | "iconUrl": null, 78 | "promotionActivityType": null, 79 | "rideMinutes": null, 80 | "countryId": 55, 81 | "cityId": 250, 82 | "helmet": 0 83 | } 84 | ] 85 | }, 86 | "success": true, 87 | "errorCode": 100 88 | } 89 | ``` 90 | 91 | ## Implementations 92 | 93 | * https://www.npmjs.com/package/@multicycles/obike (JavaScript) 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WoBike 2 | 3 | Documentation of Bike Sharing APIs 4 | 5 | Public transport and multimodal routing apps could benefit from showing nearby bikes from bikesharing services. So here's a list showing the APIs of a few of these platforms. 6 | 7 | ## Bikes 8 | - [Nextbike (Worldwide)](Nextbike.md) 9 | - [Call-a-Bike (Germany / Deutsche Bahn)](Call-a-Bike.md) 10 | - [Motivate (US; Powers FordGoBike, Biketown, Capital Bikeshare, Bike Chattanooga, Citi Bike, CoGo, Divvy, Hubway)](Motivate.md) 11 | - [BYKE/WIND](Wind.md) 12 | - [Dropbike (Canada)](Dropbike.md) 13 | - [SocialBicycles (USA, Canada, Czech Republic & Poland)](SocialBicycles.md) 14 | - [Bixi (Montreal)](Bixi.md) 15 | - [Donkey Republic](Donkey.md) 16 | - [Velo Antwerpen (Antwerp/Belgium)](VeloAntwerpen.md) 17 | - [Cargaroo](Cargaroo.md) 18 | - [Bicikelj](Bicikelj.md) 19 | 20 | ## E-Scooters (🛴, that you stand on) 21 | - [Bird](Bird.md) 22 | - [VOI](Voi.md) 23 | - [Helbiz](Helbiz.md) 24 | - [TIER](Tier.md) 25 | - [Beam](Beam.md) 26 | - [Flamingo](Flamingo.md) 27 | - [Blip](Blip.md) 28 | - [Vaimoo](Vaimoo.md) 29 | - [Go Sharing](Go-Sharing.md) 30 | - [Lime & JUMP](Lime.md) 31 | - [Spin](Spin.md) 32 | - [Pony](Pony.md) 33 | - [Bolt](Bolt.md) 34 | - [Evhcle](Evhcle.md) 35 | - [Link](Link.md) 36 | - [Ryde](Ryde.md) 37 | 38 | ## Scooters/Mopeds (🛵, that you sit on) 39 | - [Emmy (Germany)](Emmy.md) 40 | - [Stella (Germany (Stuttgart))](Stella.md) 41 | - [Felyx (Europe)](Felyx.md) 42 | - [Eddy (Düsseldorf, Germany)](Eddy.md) 43 | 44 | ## Cars 45 | - [Cambio (Germany & Belgium)](Cambio.md) 46 | - [Miles (Germany)](Miles.md) 47 | 48 | ## Dead (e.g. bankrupt) 49 | - [Mobike](Mobike.md) Closed December 2020 50 | - [Zagster](Zagster.md) - Closed June 2020 51 | - [Circ](Circ.md) - Bought by Bird 27.1.2020 52 | - [oBike (Worldwide)](Obike.md) - Closed June 2018 53 | - [OnzO (New Zealand)](Onzo.md) - Closed March 2020 54 | - [Bluegogo (China, US)](Bluegogo.md) - Closed November 2017 55 | - [Gobee bike (Hong Kong & Italy)](Gobee.md) - Closed July 2018 56 | - [Coup (Germany, Spain & France)](Coup.md) - Closed December 2019 57 | - [JUMP (USA, Europe, Australia & NZ)](Jump.md) - Closed 16.6.2020 58 | - [ofo bike (China, UK, US, Austria, Thailand, Singapore, France & India)](Ofo.md) - Closed January 2019 59 | - [Hive](Hive.md) 60 | - [Scoota](Scoota.md) 61 | - [Ufo (Europe)](Ufo.md) 62 | - [Blueduck](Blueduck.md) 63 | - [Lidl-Bike](Lidl-Bike.md) 64 | - [Zero (Germany)](Zero.md) 65 | - [EUBIKE (Sweden)](EUBike.md) 66 | - [WeShare (Germany)](WeShare.md) 67 | - [Avocargo (Berlin)](Avocargo.md) 68 | - [Yobike/Ohbike/Indigoweel](Yobike.md) 69 | - [CityBike Wien (Vienna/Austria)](CityBikeWien.md) 70 | 71 | 72 | ## More... 73 | * Also have a look at [this project](https://github.com/eskerda/pybikes/tree/master/pybikes) 74 | * [GBFS (General Bikeshare Feed Specification)](https://github.com/NABSA/gbfs) 75 | * [Bikesharing World Map](https://bikesharingworldmap.com) 76 | * [Open Bike Share Data](https://bikeshare-research.org/) 77 | * For more documentation and some updates on the APIs, a look into the issues section could be helpful. Also feel free to add APIs from the issues into the documentation by creating a Pull Request. 78 | 79 | ## Todo 80 | Some new providers are listed in [issues with the new provider tag](https://github.com/ubahnverleih/WoBike/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+provider%22). Some of them are already documented there and just need to be put in an own file in this repo. 81 | -------------------------------------------------------------------------------- /Bird.md: -------------------------------------------------------------------------------- 1 | # Bird (Scooters) 2 | [Bird](https://www.bird.co/) is a E-Scooter sharing service in the US. 3 | 4 | ## Get Auth Token 5 | 6 | First, you need to get authorization. This requires a verifiable email and a GUID. Send a POST request to `https://api-auth.prod.birdapp.com/api/v1/auth/email` with the body: 7 | ``` 8 | {"email":""} 9 | ``` 10 | And the Headers should include 11 | ``` 12 | User-Agent: Bird/4.119.0(co.bird.Ride; build:3; iOS 14.3.0) Alamofire/5.2.2 13 | Device-Id: 14 | Platform: ios 15 | App-Version: 4.119.0 16 | Content-Type: application/json 17 | ``` 18 | 19 | For the Device-Id you need to generate an random 16 Byte [GUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) like `123E4567-E89B-12D3-A456-426655440000` 20 | 21 | 22 | Then, you'll need to find the token in your email and send that in a POST to `https://api-auth.prod.birdapp.com/api/v1/auth/magic-link/use` 23 | 24 | The body: 25 | ``` 26 | {"token":""} 27 | ``` 28 | 29 | As a result you get something like this: 30 | ``` 31 | { 32 | "access": "", 33 | "refresh": "" 34 | } 35 | ``` 36 | ## Refresh Auth Token 37 | 38 | By default bird tokens expire after one day. They can be easily refreshed without having to get another magic link. Send a POST request to `https://api-auth.prod.birdapp.com/api/v1/auth/refresh/token` with the following headers: 39 | ``` 40 | User-Agent: Bird/4.119.0(co.bird.Ride; build:3; iOS 14.3.0) Alamofire/5.2.2 41 | Device-Id: 42 | Platform: ios 43 | App-Version: 4.119.0 44 | Content-Type: application/json 45 | Authorization: Bearer 46 | ``` 47 | Note, authorization header is Bearer and not Bird 48 | As a result youll get two new tokens: access and refresh 49 | 50 | ## Request Location 51 | 52 | Now you can request the locations of the Scooters: 53 | 54 | Send a GET request to `https://api-bird.prod.birdapp.com/bird/nearby?latitude=37.77184&longitude=-122.40910&radius=1000` 55 | Set the following headers: 56 | 57 | * `Authorization`: `Bearer ` – Use the Access token you got from Auth-Request. 58 | * `User-Agent`: `Bird/4.119.0(co.bird.Ride; build:3; iOS 14.3.0) Alamofire/5.2.2` - If you omit this header, the response will consist of dummy data, i.e., it looks correct, but the ids are all random and their location lays on a straight line. I assue this for internal testing. 59 | * `legacyrequest`: `false` - this can be safly be omitted. However it is documented in case it becomes relevant in the near future. 60 | * `Device-id`: `` – You can reuse the GUID from Auth-Request, but don't have to 61 | * `App-Version`: `4.119.0` 62 | * `Location`: `{"latitude":37.77249,"longitude":-122.40910,"altitude":500,"accuracy":65,"speed":-1,"heading":-1}` – Yes this is JSON in a header ;) – You should use the same data like from the GET request params. 63 | 64 | The Result looks like this: `{"birds":[{"id":"1486a00c-fd73-4370-9250-782f5c60ee2d","code":"6JLE","location":{"latitude":37.77216,"longitude":-122.409485},"battery_level":89}, ... ]}` 65 | 66 | ## Request Areas 67 | 68 | You can request no-parking zones or operational service areas by sending a GET request to `https://api.birdapp.com/bird/nearby?latitude=37.77184&longitude=-122.40910&radius=1000` 69 | 70 | This endpoint requires the same headers as the `Request Location` endpoint above. 71 | 72 | ## Request Configuration 73 | 74 | Send a GET request to `https://api.birdapp.com/config/location?latitude=42.3140089&longitude=-71.2490943`. 75 | The only needed header is `App-Version: 4.119.0`. 76 | 77 | The result contains the configuration for the app in this specific location, including stuff like `weather_alert` and service periods and `current_service_status`. 78 | 79 | ## Libraries 80 | 81 | * https://github.com/jzarca01/node-bird 82 | -------------------------------------------------------------------------------- /Circ.md: -------------------------------------------------------------------------------- 1 | # Flash / Circ / Bird (scooters) (defunct) 2 | [Bird](https://www.bird.co/) is a E-Scooter sharing service in the US. 3 | 4 | Note: Circ has been bought by Bird. 5 | 6 | ## Get Auth Token 7 | 8 | First, you need to get authorization. This requires a verifiable email and a GUID. Send a POST request to `https://api-auth.prod.birdapp.com/api/v1/auth/email` with the body: 9 | ``` 10 | {"email":""} 11 | ``` 12 | And the Headers should include 13 | ``` 14 | User-Agent: Bird/4.119.0(co.bird.Ride; build:3; iOS 14.3.0) Alamofire/5.2.2 15 | Device-Id: 16 | Platform: ios 17 | App-Version: 4.119.0 18 | Content-Type: application/json 19 | ``` 20 | 21 | For the Device-Id you need to generate an random 16 Byte [GUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) like `123E4567-E89B-12D3-A456-426655440000` 22 | 23 | 24 | Then, you'll need to find the token in your email and send that in a POST to `https://api-auth.prod.birdapp.com/api/v1/auth/magic-link/use` 25 | 26 | The body: 27 | ``` 28 | {"token":""} 29 | ``` 30 | 31 | As a result you get something like this: 32 | ``` 33 | { 34 | "access": "", 35 | "refresh": "" 36 | } 37 | ``` 38 | ## Refresh Auth Token 39 | 40 | By default bird tokens expire after one day. They can be easily refreshed without having to get another magic link. Send a POST request to `https://api-auth.prod.birdapp.com/api/v1/auth/refresh/token` with the following headers: 41 | ``` 42 | User-Agent: Bird/4.119.0(co.bird.Ride; build:3; iOS 14.3.0) Alamofire/5.2.2 43 | Device-Id: 44 | Platform: ios 45 | App-Version: 4.119.0 46 | Content-Type: application/json 47 | Authorization: Bearer 48 | ``` 49 | Note, authorization header is Bearer and not Bird 50 | As a result youll get two new tokens: access and refresh 51 | 52 | ## Request Location 53 | 54 | Now you can request the locations of the Scooters: 55 | 56 | Send a GET request to `https://api-bird.prod.birdapp.com/bird/nearby?latitude=37.77184&longitude=-122.40910&radius=1000` 57 | Set the following headers: 58 | 59 | * `Authorization`: `Bearer ` – Use the Access token you got from Auth-Request. 60 | * `User-Agent`: `Bird/4.119.0(co.bird.Ride; build:3; iOS 14.3.0) Alamofire/5.2.2` - If you omit this header, the response will consist of dummy data, i.e., it looks correct, but the ids are all random and their location lays on a straight line. I assue this for internal testing. 61 | * `legacyrequest`: `false` - this can be safly be omitted. However it is documented in case it becomes relevant in the near future. 62 | * `Device-id`: `` – You can reuse the GUID from Auth-Request, but don't have to 63 | * `App-Version`: `4.119.0` 64 | * `Location`: `{"latitude":37.77249,"longitude":-122.40910,"altitude":500,"accuracy":65,"speed":-1,"heading":-1}` – Yes this is JSON in a header ;) – You should use the same data like from the GET request params. 65 | 66 | The Result looks like this: `{"birds":[{"id":"1486a00c-fd73-4370-9250-782f5c60ee2d","code":"6JLE","location":{"latitude":37.77216,"longitude":-122.409485},"battery_level":89}, ... ]}` 67 | 68 | ## Request Areas 69 | 70 | You can request no-parking zones or operational service areas by sending a GET request to `https://api.birdapp.com/bird/nearby?latitude=37.77184&longitude=-122.40910&radius=1000` 71 | 72 | This endpoint requires the same headers as the `Request Location` endpoint above. 73 | 74 | ## Request Configuration 75 | 76 | Send a GET request to `https://api.birdapp.com/config/location?latitude=42.3140089&longitude=-71.2490943`. 77 | The only needed header is `App-Version: 4.119.0`. 78 | 79 | The result contains the configuration for the app in this specific location, including stuff like `weather_alert` and service periods and `current_service_status`. 80 | 81 | ## Libraries 82 | 83 | * https://github.com/jzarca01/node-bird 84 | -------------------------------------------------------------------------------- /Ofo.md: -------------------------------------------------------------------------------- 1 | # Ofo (defunct) 2 | 3 | **Base url**: `https://one.ofo.com` 4 | 5 | ## Methods 6 | 7 | ### Login 8 | 9 | Login is in two step: + Request code with your phone number, you ll receive an sms + Send back this code 10 | 11 | #### Request sms code 12 | 13 | **Method**: `POST` 14 | 15 | **Path**: `/verifyCode_v2` 16 | 17 | **Header**: 18 | 19 | | Header | Value | Mandatory | 20 | | ------------ | --------------------------------- | :-------: | 21 | | content-type | application/x-www-form-urlencoded | X | 22 | 23 | **Parameters**: 24 | 25 | | Parameters | Descriptions | Mandatory | 26 | | ---------- | ------------------------ | :-------: | 27 | | tel | phone number intl format | X | 28 | | type | Value at 1 | X | 29 | | ccc | Country calling codes (like `33`) | X | 30 | | lat | latitude | X | 31 | | lng | Longitude | X | 32 | 33 | **Exemple** 34 | 35 | will send an sms to `06 12 34 56 78` phone with an OTP code. 36 | 37 | ```bash 38 | curl --request POST \ 39 | --url https://one.ofo.com/verifyCode_v2 \ 40 | --header 'content-type: application/x-www-form-urlencoded' \ 41 | --data 'tel=612345678&type=1&ccc=33&lng=2.37&lat=48.85' 42 | ``` 43 | 44 | #### Send back OTP code 45 | 46 | **Method**: `POST` 47 | 48 | **Path**: `/api/login_v2` 49 | 50 | **Header**: 51 | 52 | | Header | Value | Mandatory | 53 | | ------------ | --------------------------------- | :-------: | 54 | | content-type | application/x-www-form-urlencoded | X | 55 | 56 | **Parameters**: 57 | 58 | | Parameters | Descriptions | Mandatory | 59 | | ---------- | ------------------------ | :-------: | 60 | | tel | phone number intl format | X | 61 | | code | OTP code (length of 4) | X | 62 | | ccc | Country calling codes (like `33`) | X | 63 | | lat | latitude | X | 64 | | lng | Longitude | X | 65 | 66 | **Exemple** 67 | 68 | ```bash 69 | curl --request POST \ 70 | --url https://one.ofo.com/api/login_v2 \ 71 | --header 'content-type: application/x-www-form-urlencoded' \ 72 | --data 'tel=612345678&ccc=33&lng=2.37&lat=48.85&code=1234' 73 | ``` 74 | 75 | ```JSON 76 | { 77 | "errorCode": 200, 78 | "msg": "登陆成功", 79 | "values": { 80 | "token": "3655ff61-dc6c-4abb-a267-61c59b59eb73", 81 | "isNewuser": false, 82 | "needDeposit": false, 83 | "depositToPay": 0, 84 | "depositCurrency": "€", 85 | "paymentMethod": {} 86 | } 87 | } 88 | ``` 89 | 90 | ### Get bicycles by lat/lng 91 | 92 | :warning: Auth are mandatory for this endpoint 93 | 94 | **Method**: `POST` 95 | 96 | **Path**: `/nearbyofoCar` 97 | 98 | **Header**: 99 | 100 | | Header | Value | Mandatory | 101 | | ------------ | --------------------------------- | :-------: | 102 | | content-type | application/x-www-form-urlencoded | X | 103 | 104 | **Parameters**: 105 | 106 | | Parameters | Descriptions | Mandatory | 107 | | ---------- | ----------------------------- | :-------: | 108 | | lat | latitude | X | 109 | | lng | Longitude | X | 110 | | source | ??? (maybe android or iphone) | X | 111 | | token | user token (get by auth) | X | 112 | 113 | **Exemple** 114 | 115 | ```bash 116 | curl --request POST \ 117 | --url https://one.ofo.so/nearbyofoCar \ 118 | --header 'content-type: application/x-www-form-urlencoded' \ 119 | --data 'source=2&token=3655ff61-dc6c-4abb-a267-61c59b59eb73&lat=48.84&lng=2.38' 120 | ``` 121 | 122 | ```JSON 123 | { 124 | "errorCode": 200, 125 | "msg": "附近车辆位置", 126 | "values": { 127 | "cars": [ 128 | { 129 | "carno": "PzeDlM", 130 | "bomNum": "5CA", 131 | "userIdLast": "1", 132 | "lng": 2.3796870561551, 133 | "lat": 48.839922829334 134 | } 135 | ] 136 | } 137 | } 138 | ``` 139 | 140 | ## Implementations 141 | 142 | * https://www.npmjs.com/package/@multicycles/ofo (JavaScript) 143 | * https://github.com/qxzzxq/pyofo (Python3) 144 | * https://npmjs.org/ofo (JavaScript) 145 | -------------------------------------------------------------------------------- /EUBike.md: -------------------------------------------------------------------------------- 1 | # Introduction (defunct) 2 | EU-BIKE was a Swedish bike sharing service available in some bigger and smaller Swedish cities. 3 | ### Viewing bikes 4 | 5 | **PLEASE NOTE: EUBIKE has had some serious problems related to displaying correct locations of bikes. I know that because I have tried their system and I have read some reviews online. With that said, not all bike locations are inaccurate.** 6 | 7 | To see the list of available bikes for a location, you don´t need any authentication. The app "EU-BIKE" is connecting to an Alibaba Cloud server 8 | at the IP "47.91.87.181" at port 8080. 9 | This is a basic URL to list available bikes: 10 | http://47.91.87.181:8080/UserApi/AppUser?lng=18.0668918788433075&lat=59.3109666242335791&cmd=areabike&dist=0.5 (**POST**) 11 | The app adds a "logintoken" to the request, but it is not required in order to see bikes. 12 | Here is a detailed explaination of each accepted key in the request: 13 | **lng**: The longtitude of the search. 14 | **lat**: The latitude of the search. 15 | **cmd**: I am unsure what this means, butit it is probably to identify the query request. 16 | **dist**: The distance (from the latitude and longtitude parameters) to perform a seach from. Probably in a radius format and in kilometers. Doesn´t seem to have an upper limit. The app´s default value when performing a search is 0.5. 17 | 18 | Example response (in JSON, with a distance value of 0.5): 19 | `{"success":true,"data":[{"devid":"10002165","shebeistatus":0,"devlat":59.31163,"devlng":18.06885,"devpower":0,"dist":0.133420259},{"devid":"10000991","shebeistatus":0,"devlat":59.30986,"devlng":18.06548,"devpower":0,"dist":0.147037461},{"devid":"10001231","shebeistatus":0,"devlat":59.3098221,"devlng":18.0654163,"devpower":0,"dist":0.15217796},{"devid":"10001958","shebeistatus":0,"devlat":59.3130035,"devlng":18.0675964,"devpower":0,"dist":0.230080515},{"devid":"10001357","shebeistatus":0,"devlat":59.31213,"devlng":18.0620575,"devpower":0,"dist":0.303918362},{"devid":"10002477","shebeistatus":0,"devlat":59.3123932,"devlng":18.0622749,"devpower":0,"dist":0.306723356},{"devid":"10001918","shebeistatus":0,"devlat":59.3137474,"devlng":18.0648251,"devpower":0,"dist":0.330999374},{"devid":"10000956","shebeistatus":0,"devlat":59.3138161,"devlng":18.0649929,"devpower":0,"dist":0.334925145},{"devid":"10002714","shebeistatus":0,"devlat":59.3076477,"devlng":18.0688934,"devpower":0,"dist":0.386613458},{"devid":"10001682","shebeistatus":0,"devlat":59.3134842,"devlng":18.0621243,"devpower":0,"dist":0.390006721},{"devid":"10000901","shebeistatus":0,"devlat":59.30748,"devlng":18.0644283,"devpower":0,"dist":0.412274241},{"devid":"10002153","shebeistatus":0,"devlat":59.3124542,"devlng":18.07411,"devpower":0,"dist":0.442282349},{"devid":"10002307","shebeistatus":0,"devlat":59.3143,"devlng":18.0712128,"devpower":0,"dist":0.444873065},{"devid":"10001967","shebeistatus":0,"devlat":59.3112335,"devlng":18.0747318,"devpower":0,"dist":0.446369052},{"devid":"10002368","shebeistatus":0,"devlat":59.310463,"devlng":18.0749969,"devpower":0,"dist":0.463875979},{"devid":"10002375","shebeistatus":0,"devlat":59.31275,"devlng":18.0743713,"devpower":0,"dist":0.469272941},{"devid":"10003093","shebeistatus":0,"devlat":59.3102531,"devlng":18.0750942,"devpower":0,"dist":0.472640872},{"devid":"10001165","shebeistatus":1,"devlat":59.3100624,"devlng":18.0752888,"devpower":0,"dist":0.487477541},{"devid":"10001402","shebeistatus":0,"devlat":59.3102951,"devlng":18.0754051,"devpower":0,"dist":0.489349037}],"maparea":[]}` 20 | 21 | Explaination of values returned in the response: 22 | 23 | **success**: Pretty self-explainatory. Indicates if a request has failed or not. 24 | 25 | **Please note: If there is no bikes available at the performed query, the server will return False as the success value and "Interface call error" in a "msg" parameter.** 26 | 27 | ### Bike object (listed in the "data" list returned on a successfull request): 28 | 29 | **devid** : The device ID of the bike. This ID is probably used when unlocking a bike, but I have not confirmed that. 30 | 31 | **sheibeistatus** : Unconfirmed. Seems to always be set to zero. 32 | 33 | **devlat** : The latitude position of the bike. 34 | 35 | **devlng** : The longtitude position of the bike. 36 | 37 | **devpower** : Probably has to do with the device power, but it seems to always be set to zero, even with bikes that have power. 38 | 39 | **dist** : The distance to the bike from the coordinates given in the request. This value is most likely in kilometers. 40 | 41 | [END OF BIKE OBJECT] 42 | 43 | **maparea** : Not confirmed. 44 | -------------------------------------------------------------------------------- /WeShare.md: -------------------------------------------------------------------------------- 1 | # WeShare (defunct) 2 | 3 | [WeShare](https://www.we-share.io/) was a carsharing company based in Germany providing vehicles in Berlin and Hamburg. 4 | 5 | There is *no* authentication or special headers and only `GET`-Requests required for their API-Endpoints. 6 | 7 | ## Request available Cities (and their ids) 8 | 9 | You need to know the id of your city first, so send a GET request to `https://mos-p-euw-weshare-apm.azure-api.net/assets/getCities` 10 | 11 | The result looks like this: 12 | 13 | ```json 14 | { 15 | "data": [ 16 | { 17 | "cityId": "81cb439f-3b59-11e9-9928-06cf929d95ea", 18 | "name": "Berlin", 19 | "cityName": "Berlin", 20 | "countryName": "Deutschland", 21 | "midpoint": { 22 | "lat": 52.52437, 23 | "lon": 13.41053 24 | }, 25 | "radius": 6000, 26 | "supportPhoneNumber": "+4930255585657", 27 | "helpUrl": "https://www.we-share.io/faq-in-app/", 28 | "imprintUrl": "https://www.we-share.io/imprint-in-app/", 29 | "serviceDocuments": { 30 | "serviceDocumentsId": "146fa38f-d300-4ce6-94e8-64189a063353", 31 | "termsAndConditionsUrl": "https://www.we-share.io/app-terms-in-app/", 32 | "dataPrivacyPolicyUrl": "https://www.we-share.io/app-privacy-in-app/", 33 | "version": 2 34 | } 35 | }, 36 | { 37 | "cityId": "75d659f4-7978-42ca-b2ee-b25efcc5eaa9", 38 | "name": "Hamburg", 39 | "cityName": "Hamburg", 40 | "countryName": "Deutschland", 41 | "midpoint": { 42 | "lat": 53.552546, 43 | "lon": 10.014038 44 | }, 45 | "radius": 6000, 46 | "supportPhoneNumber": "+4930255585657", 47 | "helpUrl": "https://www.we-share.io/faq-in-app", 48 | "imprintUrl": "https://www.we-share.io/imprint-in-app/", 49 | "serviceDocuments": { 50 | "serviceDocumentsId": "146fa38f-d300-4ce6-94e8-64189a063353", 51 | "termsAndConditionsUrl": "https://www.we-share.io/app-terms-in-app/", 52 | "dataPrivacyPolicyUrl": "https://www.we-share.io/app-privacy-in-app/", 53 | "version": 2 54 | } 55 | } 56 | ] 57 | } 58 | ``` 59 | 60 | ## Get all available vehicles 61 | 62 | Now you can load all available vehicles using another GET request to `https://mos-p-euw-weshare-apm.azure-api.net/assets/getAvailableVehicles?cityId=` 63 | 64 | The result contains an array with all vehicles in the following format: 65 | 66 | ```json 67 | { 68 | "data": [ 69 | { 70 | "id": "007f5ae4-6902-9f85-0294-3e8f7c8a937d", 71 | "plate": "WI-D5320E", 72 | "iconBaseUrl": "https://storage.googleapis.com/weshare-public-assets/vehicle-images/ID3/529", 73 | "cityId": "81cb439f-3b59-11e9-9928-06cf929d95ea", 74 | "serviceId": "ccf9d6e2-d945-46d5-96f6-5f77464a4f15", 75 | "modelName": "ID.3", 76 | "updatedAt": null, 77 | "pricingModels": { 78 | "standard": { 79 | "currency": "EUR", 80 | "basicFee": 1, 81 | "perMinute": 0.29, 82 | "perMinuteStopOver": 0.29, 83 | "perDay": 58, 84 | "mileage": { 85 | "included": 100, 86 | "perExtraUnit": 0.29 87 | } 88 | }, 89 | "wesharePlus": { 90 | "currency": "EUR", 91 | "basicFee": 1, 92 | "perMinute": 0.19, 93 | "perMinuteStopOver": 0.05, 94 | "perDay": 48, 95 | "mileage": { 96 | "included": 150, 97 | "perExtraUnit": 0.29 98 | } 99 | } 100 | }, 101 | "location": { 102 | "lat": 52.53689, 103 | "lon": 13.26205 104 | }, 105 | "autonomyLevel": 30, 106 | "autonomyRange": 101, 107 | "isCharging": false 108 | } 109 | ] 110 | } 111 | ``` 112 | 113 | ## Get the served area 114 | 115 | This GET request returns all limits of the service-area: `https://mos-p-euw-weshare-apm.azure-api.net/assets/getHomeZone?cityId=` 116 | 117 | Result similar to: 118 | 119 | ```json 120 | { 121 | "data": [ 122 | { 123 | "zoneId": "1035edc5c05445129e609b1012f90a8e", 124 | "name": "Lidl - Prinzenallee 75", 125 | "area": [ 126 | { 127 | "latitude": 52.555029, 128 | "longitude": 13.383484 129 | }, 130 | { 131 | "latitude": 52.555248, 132 | "longitude": 13.382687 133 | }, 134 | { 135 | "latitude": 52.55487, 136 | "longitude": 13.382435 137 | }, 138 | { 139 | "latitude": 52.554656, 140 | "longitude": 13.38321 141 | }, 142 | { 143 | "latitude": 52.555029, 144 | "longitude": 13.383484 145 | } 146 | ], 147 | "forbidden": true 148 | } 149 | ] 150 | } 151 | ``` 152 | -------------------------------------------------------------------------------- /Helbiz.md: -------------------------------------------------------------------------------- 1 | # Helbiz (Scooter) 2 | [Helbiz Go](https://helbiz.com/go) provides scooter sharing in Italy, Spain and France: https://helbiz.com/cities 3 | 4 | The API for getting scooter positions needs full authentication. Therefore, the User Creation Flow is described here: 5 | 6 | ## Create User 7 | 8 | POST `https://api.helbiz.com/prod/user/create` 9 | 10 | Headers: 11 | ``` 12 | X-Requested-With: XMLHttpRequest 13 | User-Agent: Helbiz (com.helbiz.android) 14 | Content-Type: application/json 15 | ``` 16 | 17 | Body: 18 | ``` 19 | {"email": "helbiz@example.com","password": "...","firstName": "...","lastName": "...","phone": "+12125550100"} 20 | ``` 21 | 22 | Response: 23 | ``` 24 | { 25 | "result": { 26 | "status": "Created", 27 | "temporaryId": "..." 28 | } 29 | } 30 | ``` 31 | 32 | You get also sent an SMS with a four digit code. Use this and the `temporaryId` to call the verification endpoint. 33 | 34 | ## Verify Phone Number 35 | 36 | POST `https://api.helbiz.com/prod/user/mobile/verify/{code}/{temporaryId}` 37 | 38 | Headers: 39 | ``` 40 | X-Requested-With: XMLHttpRequest 41 | User-Agent: Helbiz (com.helbiz.android) 42 | Content-Type: application/json 43 | ``` 44 | 45 | Response: HTTP 204 46 | 47 | ## Login 48 | 49 | POST `https://api.helbiz.com/prod/user/authenticate` 50 | 51 | Headers: 52 | ``` 53 | X-Requested-With: XMLHttpRequest 54 | User-Agent: Helbiz (com.helbiz.android) 55 | Content-Type: application/json 56 | ``` 57 | 58 | Body: 59 | ``` 60 | {"email": "helbiz@example.com","password": "..."} 61 | ``` 62 | 63 | As value for the password field you have to provide the md5 hash of the password sent into the create request. 64 | 65 | In order to obtain the hash, supposing you're using a *nix system, you could execute the following command: 66 | ``` 67 | $ echo -n "password_string" | md5sum | tr --delete '-' 68 | 5f4dcc3b5aa765d61d8327deb882cf99 69 | ``` 70 | then copy and paste the string obtained as result. 71 | 72 | Response: 73 | ``` 74 | { 75 | "token": "eyJ...kE" 76 | } 77 | ``` 78 | 79 | ## Get Regions 80 | 81 | GET `https://api.helbiz.com/prod/regions` 82 | 83 | Headers: 84 | ``` 85 | X-Requested-With: XMLHttpRequest 86 | User-Agent: Helbiz (com.helbiz.android) 87 | Content-Type: application/json 88 | X-access-token: eyJ...kE 89 | ``` 90 | 91 | 92 | Response: 93 | ``` 94 | [ 95 | { 96 | "name": "milano1", 97 | "center": { 98 | "lat": 45.465097, 99 | "lon": 9.188581 100 | }, 101 | "startTime": "07:00", 102 | "endTime": "22:00", 103 | "polygon": [ 104 | [45.47408, 9.16964], 105 | [45.47591, 9.16811], 106 | [45.47807, 9.16895], 107 | [45.47883, 9.17189], 108 | [45.47771, 9.1753], 109 | [45.4765, 9.17685], 110 | [45.47783, 9.18045], 111 | [45.48445, 9.17753], 112 | [45.48469, 9.17994], 113 | [45.48589, 9.18182], 114 | [45.48812, 9.18246], 115 | [45.49018, 9.19128], 116 | [45.49259, 9.19295], 117 | [45.48951, 9.2012], 118 | [45.49179, 9.20311], 119 | [45.48878, 9.21032], 120 | [45.48637, 9.20809], 121 | [45.48324, 9.21616], 122 | [45.47398, 9.20723], 123 | [45.46097, 9.20792], 124 | [45.45277, 9.20198], 125 | [45.45471, 9.21289], 126 | [45.44773, 9.20774], 127 | [45.4463, 9.18774], 128 | [45.45173, 9.18327], 129 | [45.45274, 9.17643], 130 | [45.46127, 9.16271], 131 | [45.47217, 9.16655], 132 | [45.47408, 9.16964] 133 | ], 134 | "bounds": [ 135 | [45.518166, 9.066294], 136 | [45.519128, 9.369104], 137 | [45.349039, 9.368418], 138 | [45.344696, 9.063547] 139 | ], 140 | "image": "https://s3-eu-west-1.amazonaws.com/helbiz-avatars/regions/Milan-Icon.png", 141 | "badge": "https://s3-eu-west-1.amazonaws.com/helbiz-avatars/regions/badges/Milan.png", 142 | "message": "hours", 143 | "blacklist": null 144 | }, 145 | ... 146 | ] 147 | ``` 148 | 149 | ## Get Scooter Positions 150 | 151 | GET `https://api.helbiz.com/prod/scooters?northWest=45.518166,9.066294&southEast=45.349039,9.368418` 152 | 153 | Headers: 154 | ``` 155 | X-Requested-With: XMLHttpRequest 156 | User-Agent: Helbiz (com.helbiz.android) 157 | Content-Type: application/json 158 | X-access-token: eyJ...kE 159 | ``` 160 | 161 | Response: 162 | ``` 163 | [ 164 | { 165 | "id": "5bf7d74930212164763155af", 166 | "lat": 45.46080583333333, 167 | "lon": 9.202304333333334, 168 | "batteryLevelInMiles": 8.1, 169 | "pricing": { 170 | "hourly": 15 171 | }, 172 | "geofence": "milano1" 173 | }, 174 | { 175 | "id": "5bf7db6030212164763155c3", 176 | "lat": 45.4560585, 177 | "lon": 9.195604333333334, 178 | "batteryLevelInMiles": 15, 179 | "pricing": { 180 | "hourly": 15 181 | }, 182 | "geofence": "milano1" 183 | }, 184 | ... 185 | ] 186 | ``` 187 | 188 | The API returns `[]` when the scooters are not in service, remember to look at the `message` of the region to find out why (eg. hours) 189 | -------------------------------------------------------------------------------- /Yobike.md: -------------------------------------------------------------------------------- 1 | # Yobike and yobike-based provider (defunct) 2 | 3 | Yobike (ex ohbike) sell their systems under white label. 4 | Only the Application key differ 5 | 6 | **Base url**: `https://en.api.ohbike.com` 7 | 8 | Yobike (ex ohbike) sell their systems under white label. 9 | Only the Application key differ 10 | 11 | | Brand | Application key | 12 | | ------------ | ---------------------------------------------------------------- | 13 | | Yobike | WWTaJQrg-NHe_Zl0iwghHyYypYw6g-6GEZHPGBBF6TI7OzZWo9VVLXWRs2ngQJ18 | 14 | | Indigo Wheel | NaDl8eR81njaT7FRMNn2oqH020bAfUG7d7Iqa2kMvZm8qCga5cg_QIlk_XZVZvWI | 15 | 16 | ## Methods 17 | 18 | ### Signature 19 | 20 | Requests should be "signed". We just need to add an `sign` attribute with an md5 hash. 21 | To get the corresponding md5 hash: 22 | 23 | * get all POST _values_ (excl. sign) 24 | * sort the values 25 | * concat the sorted values (ignoring empty values) to string 26 | * add `@ohbile` on the end of the string 27 | 28 | **Javascript example** (from apk) 29 | 30 | ```JavaScript 31 | function getSignature(data) { 32 | var c = [], 33 | g, k; 34 | 35 | for (k in data) { 36 | g = data[k], void 0 == g && (g = "", data[k] = ""), c.push(g); 37 | } 38 | 39 | c.sort(); 40 | data = ""; 41 | 42 | for (k in c) { 43 | data += c[k]; 44 | } 45 | 46 | return md5(data + "@ohbile"); 47 | } 48 | ``` 49 | 50 | **Python Example** (adapted from JS) 51 | 52 | ```python3 53 | import hashlib 54 | 55 | def get_signature(data): 56 | relevant_values = [str(v) 57 | for (k, v) in query_data.items() 58 | if v and k != 'sign'] 59 | hash_input = ''.join(sorted(relevant_values)) 60 | hash_input += '@ohbile' 61 | md5 = hashlib.md5(hash_input.encode('ascii')).hexdigest() 62 | return md5 63 | ``` 64 | 65 | ### Get bicycles by lat/lng 66 | 67 | **Method**: `POST` 68 | 69 | **Path**: `/v1/vehicle/` 70 | 71 | **Header**: 72 | 73 | | Header | Value | Mandatory | 74 | | ------------ | --------------------------------- | :-------: | 75 | | content-type | application/x-www-form-urlencoded | X | 76 | 77 | **Parameters**: 78 | 79 | | Parameters | Descriptions | Mandatory | 80 | | ---------- | ---------------------- | :-------: | 81 | | lat | Latitude | X | 82 | | lng | Longitude | X | 83 | | distance | | X | 84 | | coord_type | Value: 1 | X | 85 | | t | value: "geonear" | X | 86 | | ts | Timestamp in second | X | 87 | | ak | Application key | X | 88 | | bounds | Bounds around position | X | 89 | | zoom | | X | 90 | | sign | Signature (see above) | X | 91 | 92 | **Bounds** 93 | 94 | Bounds parameter is a string of concatenated positions (southwest.latitude + "," + southwest.longitude + ";" + northeast.latitude + "," + northeast.longitude). 95 | 96 | **Bash/Curl Example** 97 | 98 | ```bash 99 | curl --request POST \ 100 | --url https://en.api.ohbike.com/v1/vehicle/ \ 101 | --header 'content-type: multipart/form-data' \ 102 | --form 'bounds=51.388098,-2.681621;51.578447,-2.509771' \ 103 | --form uk= \ 104 | --form ak=WWTaJQrg-NHe_Zl0iwghHyYypYw6g-6GEZHPGBBF6TI7OzZWo9VVLXWRs2ngQJ18 \ 105 | --form t=geonear \ 106 | --form distance=700 \ 107 | --form ts=1515775926 \ 108 | --form zoom=11.583380 \ 109 | --form lat=51.483372 \ 110 | --form sign=f9f22c789aaf4e997a44b64436e007cf \ 111 | --form lng=-2.595696 112 | ``` 113 | 114 | **Python Example** 115 | 116 | ```python3 117 | import json 118 | import time 119 | import requests 120 | 121 | # for indigoweel 122 | app_key = 'NaDl8eR81njaT7FRMNn2oqH020bAfUG7d7Iqa2kMvZm8qCga5cg_QIlk_XZVZvWI' 123 | 124 | # for yobiki 125 | # app_key = 'WWTaJQrg-NHe_Zl0iwghHyYypYw6g-6GEZHPGBBF6TI7OzZWo9VVLXWRs2ngQJ18' 126 | 127 | def prep_query(lat_sw, long_sw, lat_ne, long_ne): 128 | lat_mean = str((lat_sw + lat_ne) / 2) 129 | long_mean = str((long_sw + long_ne) / 2) 130 | query_data = { 131 | "lat": lat_mean, 132 | "lng": long_mean, 133 | "distance": "100000", 134 | "t": "geonear", 135 | "ts": str(int(time.time())), 136 | "ak": app_key, 137 | "zoom": "11.583380", 138 | "bounds": f'{lat_sw},{long_sw};{lat_ne},{long_ne}' 139 | } 140 | return query_data 141 | 142 | # all of grenoble 143 | query_data = prep_query(45.140106, 5.668827, 45.211247, 5.794435) 144 | 145 | # just around prefecture verdun 146 | # query_data = prep_query(45.18850600964806, 5.730357170104981, 45.18925458649066, 5.731741189956665) 147 | 148 | query_data['sign'] = get_signature(query_data) 149 | 150 | print('='*80) 151 | print('REQUEST:') 152 | print('='*80) 153 | print(json.dumps(query_data, indent=2)) 154 | print('='*80) 155 | 156 | headers = {'content-type': 'multipart/form-data'} 157 | headers = {'content-type': 'application/x-www-form-urlencoded'} 158 | req = requests.post('https://en.api.ohbike.com/v1/vehicle/', 159 | headers=headers, 160 | data=query_data) 161 | bikes = json.loads(req.text) 162 | response_json = json.dumps(bikes, indent=2) 163 | print() 164 | print('='*80) 165 | print('RESPONSE:') 166 | print('='*80) 167 | try: 168 | from pygments import highlight, lexers, formatters 169 | colorful_json = highlight(response_json, lexers.JsonLexer(), formatters.TerminalFormatter()) 170 | print(colorful_json) 171 | except: 172 | print(response_json) 173 | print('='*80) 174 | ``` 175 | 176 | :warning: Multiple requests with the same `ts` could cause errors. 177 | 178 | ```JSON 179 | { 180 | "errcode": 0, 181 | "message": "ok", 182 | "data": [ 183 | { 184 | "latitude": 51.44402, 185 | "longitude": -2.63239, 186 | "plate_no": "7701606", 187 | "outside": 0 188 | } 189 | ] 190 | } 191 | ``` 192 | 193 | ## Implementations 194 | 195 | * https://www.npmjs.com/package/@multicycles/yobike (JavaScript) 196 | -------------------------------------------------------------------------------- /Voi.md: -------------------------------------------------------------------------------- 1 | # Introduction: 2 | 3 | [VOI](https://voi.com) is a Swedish ride sharing company focused on electric scooters. They have scooters placed in several cities and countries around the world, including Sweden, Spain, Italy, France and more. 4 | 5 | Base url of the API is `https://api.voiapp.io/` 6 | 7 | 8 | # Authentication: 9 | The API requires an **Access Token**. This token is valid for only a short time (10 minutes I think, I haven't tried to measure). You get the **Access Token** by opening a session. You need an **Authentication Token** to open a session. When opening a session, you get: 10 | * an **Access Token** that you can use in the next 10 minutes or so to query data 11 | * a new **Authentication Token** to open the next session 12 | 13 | To get the first Authentication Token, see steps 1,2,3 below. 14 | 15 | Python Script to easely obtain your token: [https://gist.github.com/BastelPichi/b084aa5260331424735fadc3b8e1719d](https://gist.github.com/BastelPichi/b084aa5260331424735fadc3b8e1719d) 16 | 17 | See here example of python implementation of the session authentication (once the step 1-2-3 are done): [https://github.com/hawisizu/scooter_scrapper/blob/master/providers/voi.py](https://github.com/hawisizu/scooter_scrapper/blob/master/providers/voi.py) 18 | 19 | ## 1 - Get OTP 20 | `POST` request to `https://api.voiapp.io/v1/auth/verify/phone` 21 | 22 | Body: raw/JSON 23 | ```json 24 | { 25 | "country_code": "DE", 26 | "phone_number": "176xxxxxxxx" 27 | } 28 | ``` 29 | Note: phone number is without any 0. 30 | 31 | In the body of the answer, you get a UUID in a param `token`, and of course an SMS message to the provided phone number. 32 | ```json 33 | { 34 | "token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 35 | } 36 | ``` 37 | ## 2 - Verify OTP 38 | This request gets nothing in the answer, but is necessary so that the token works. The `code` should be the value received by SMS. 39 | 40 | `POST` request to `https://api.voiapp.io/v2/auth/verify/code` 41 | 42 | Body: raw/JSON 43 | ```json 44 | { 45 | "code": "123456", 46 | "token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 47 | } 48 | ``` 49 | Answer is like the following: 50 | ```json 51 | { 52 | "verificationStep": "emailValidationRequired", 53 | "authToken": "XXXXXXXXXXXX" 54 | } 55 | ``` 56 | `verificationStep` can be `emailValidationRequired`, `authorized`, `deviceActivationRequired`. 57 | 58 | 59 | ## 3 - Get first authToken 60 | **If `verificationStep` is `emailValidationRequired`:** 61 | This very likely means your phone number isn't linked to any account yet. 62 | 63 | `POST` request to `https://api.voiapp.io/v1/auth/verify/presence` 64 | 65 | Body: raw/JSON: 66 | ```json 67 | { 68 | "email": "xxx@xxx.com", 69 | "token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 70 | } 71 | ``` 72 | (the email should apparently be valid) 73 | Answer will be: 74 | ```json 75 | { 76 | "authToken": "xxxxxxxxxxxxxxxx" 77 | } 78 | ``` 79 | 80 | **If `verificationStep` is `deviceActivationRequired`:** 81 | This very likely means you logged into multiple devices. Per VOI tos this isn't allowed (they don't really care tho), you'll get logged out of other devices above when you have more than a few. 82 | 83 | `POST` request to `https://api.voiapp.io/v3/auth/verify/device/activate` 84 | 85 | Body: raw/JSON: 86 | ```json 87 | { 88 | "token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 89 | "provider": "sms" 90 | } 91 | ``` 92 | (the email should apparently be valid) 93 | Answer will be: 94 | ```json 95 | { 96 | "authToken": "xxxxxxxxxxxxxxxx" 97 | } 98 | ``` 99 | 100 | **If `verificationStep` is `deviceActivationRequired`:** 101 | Nothing further to do. An `authToken` is already in the response. 102 | 103 | ## 4 - Open Session 104 | `POST` request to `https://api.voiapp.io/v1/auth/session` 105 | 106 | Body: raw/JSON 107 | ```json 108 | { 109 | "authenticationToken": "xxxxxxxxxxxxxxxx" 110 | } 111 | ``` 112 | Answer will be: 113 | ```json 114 | { 115 | "accessToken": "yyyyyyyyyyyyyyyyy", 116 | "authenticationToken": "zzzzzzzzzzzzzzzzzzz" 117 | } 118 | ``` 119 | You will then use the `accessToken` to query the data, and the `authenticationToken` to open the next session, so that you don't have to re-do step 1-2-3 120 | 121 | # Access scooter data 122 | Once you have an Access Token, you can query the zones info, or if you already know which zone you want to query, get the scooters of the zone. 123 | 124 | ## Get zones info 125 | `GET` request to `https://api.voiapp.io/v1/zones` 126 | In the headers you need the access token you got when opening a session: 127 | `x-access-token`: `yyyyyyyyyyyyyyyyy` 128 | 129 | Answer: a long json with all the zones where VOI is currently operating, with details on the prices and forbidden zones, the max speed, etc. 130 | See here an example of the full zones json retrieved on Dec 20th, 2019: 131 | [https://gist.github.com/hawisizu/4d54000dc4d5d6d2e39f6994006b74d2](https://gist.github.com/hawisizu/4d54000dc4d5d6d2e39f6994006b74d2) 132 | 133 | Notes: 134 | * you can also filter the results by passing `lng` and `lat` as params of the GET request. 135 | * in the json there are some test cities 136 | 137 | ## Get Scooter info 138 | `GET` request to `https://api.voiapp.io/v2/rides/vehicles?zone_id=` 139 | 140 | `` has to be the ID of one of the zones. 141 | 142 | In the headers you need the access token you got when opening a session: 143 | `x-access-token`: `yyyyyyyyyyyyyyyyy` 144 | 145 | Answer: 146 | ```json 147 | { 148 | "data": { 149 | "vehicle_groups": [ 150 | { 151 | "price_token": "...", 152 | "group_type": "scooter", 153 | "vehicles": [ 154 | { 155 | "id": "274db72d-f108-417a-93e3-46acb61cfd26", 156 | "short": "kpne", 157 | "battery": 55, 158 | "location": { 159 | "lng": 18.11346435546875, 160 | "lat": 59.34138107299805 161 | }, 162 | "zone_id": "1" 163 | }, 164 | { 165 | "id": "3bd0b7bf-15c0-46f8-9d3b-ecb962f1f5d5", 166 | "short": "bnjs", 167 | "battery": 78, 168 | "location": { 169 | "lng": 18.088924407958984, 170 | "lat": 59.354530334472656 171 | }, 172 | "zone_id": "1" 173 | } 174 | ] 175 | } 176 | ] 177 | } 178 | } 179 | ``` 180 | 181 | The response is a nested object with information about each scooter, in a JSON format. Here is a list of the parameters in each scooter object and information about what they (most likely) do: 182 | 183 | `id`: A scooter ID that most likely is used internally. 184 | 185 | `short`: A four-character shortcode of the VOI scooter, which is used to unlock it in the app if you don´t scan the QR code on the scooter. 186 | 187 | `battery`: An integer representing the battery percentage of the scooter. 188 | 189 | `location`: An object of the scooter location. 190 | 191 | `zone_id`: This is most likely an indication of what zone the scooter is located in. Each VOI zone is represented by an integer. 192 | 193 | -------------------------------------------------------------------------------- /Neuron.md: -------------------------------------------------------------------------------- 1 | [Neuron](https://rideneuron.com/) is a scooter sharing service that operates worldwide. 2 | 3 | Base URL: https://au-api.neuron-mobility.com - for Australia 4 | 5 | Base URL: https://nz-api.neuron-mobility.com - for New Zealand 6 | 7 | Base URL: https://sg-api.neuron-mobility.com - for Singapore 8 | 9 | Base URL: https://kr-api.neuron-mobility.com - for Korea 10 | 11 | Base URL: https://gb-api.neuron-mobility.com - for UK 12 | 13 | The API uses a custom `sign` signature header. This is built by using the GET/POST parameters, the `timestamp` header and a secret. 14 | 15 | ```python 16 | import hashlib 17 | import urllib.parse 18 | from collections import OrderedDict 19 | 20 | # get params or post body 21 | params = {"distance":"335","latitude":"-27.469746172603777","longitude":"153.02297267664835"} 22 | # add timestamp from header 23 | params["timestamp"] = "1616942614041" 24 | 25 | secret = "neuron(!@#2018^&*)mobility" 26 | 27 | params = OrderedDict(sorted(params.items())) 28 | url_params = urllib.parse.urlencode(params, doseq=True, quote_via=lambda x, *_: x) 29 | hash_input = url_params + "#" + secret 30 | digest = hashlib.md5(hash_input.encode()) 31 | print(digest.hexdigest()) 32 | ``` 33 | 34 | To get the API endpoints: 35 | ``` 36 | GET https://au-api.neuron-mobility.com/api/public/countries 37 | 38 | timestamp: 1616942614041 39 | sign: 261492ea9bbf5bafca7ddc6dbba86c5d 40 | ``` 41 | 42 |
43 | 44 | ``` 45 | { 46 | "data": [ 47 | { 48 | "name": "Singapore", 49 | "numeric": 702, 50 | "code": "SG", 51 | "digitalLines": [], 52 | "servers": { 53 | "pro": "https://sg-api.neuron-mobility.com" 54 | }, 55 | "cities": [ 56 | { 57 | "name": "Singapore", 58 | "code": "SG", 59 | "latitude": 1.313996, 60 | "longitude": 103.704164, 61 | ... 62 | } 63 | ], 64 | ... 65 | }, 66 | { 67 | "name": "Australia", 68 | "numeric": 36, 69 | "code": "AU", 70 | "servers": { 71 | "pro": "https://au-api.neuron-mobility.com" 72 | }, 73 | "cities": [ 74 | { 75 | "name": "Brisbane", 76 | "code": "BNE", 77 | "latitude": -27.470192, 78 | "longitude": 153.025769, 79 | "zones": [ 80 | { 81 | "code": "BB1", 82 | "cityCode": "BNE", 83 | "name": "Default", 84 | "baseFee": 1.0, 85 | "unitFee": 0.38, 86 | "createdAt": 1571017159000, 87 | "updatedAt": 1571017159000 88 | } 89 | ], 90 | ... 91 | }, 92 | { 93 | "name": "Adelaide CBD", 94 | "code": "ADL-CBD", 95 | "latitude": -34.931056, 96 | "longitude": 138.603944, 97 | "zones": [ 98 | { 99 | "code": "ADLCBD", 100 | "cityCode": "ADL-CBD", 101 | "name": "ADL-CBD", 102 | "baseFee": 1.0, 103 | "unitFee": 0.38, 104 | "createdAt": 1580262340000, 105 | "updatedAt": 1596159043000 106 | }, 107 | { 108 | "code": "ADLNTH", 109 | "cityCode": "ADL-CBD", 110 | "name": "ADL-CBD", 111 | "baseFee": 1.0, 112 | "unitFee": 0.38, 113 | "createdAt": 1580262482000, 114 | "updatedAt": 1596159042000 115 | }, 116 | { 117 | "code": "CHARLESSTURT", 118 | "cityCode": "ADL-CBD", 119 | "name": "CHARLESSTURT", 120 | "baseFee": 1.0, 121 | "unitFee": 0.38, 122 | "createdAt": 1605653824000, 123 | "updatedAt": 1605653824000 124 | }, 125 | { 126 | "code": "NORWOOD", 127 | "cityCode": "ADL-CBD", 128 | "name": "NORWOOD", 129 | "baseFee": 1.0, 130 | "unitFee": 0.38, 131 | "createdAt": 1605653769000, 132 | "updatedAt": 1605653769000 133 | } 134 | ], 135 | ... 136 | }, 137 | { 138 | "name": "Adelaide Western Alliance", 139 | "code": "ADL-WA", 140 | "latitude": -34.931056, 141 | "longitude": 138.540355, 142 | "zones": [ 143 | { 144 | "code": "TEST1", 145 | "cityCode": "ADL-WA", 146 | "name": "TESTZONE", 147 | "baseFee": 1.0, 148 | "unitFee": 0.38, 149 | "createdAt": 1594338398000, 150 | "updatedAt": 1594345218000 151 | } 152 | ], 153 | ... 154 | }, 155 | { 156 | "name": "Darwin", 157 | "code": "DRW", 158 | "latitude": -12.454054, 159 | "longitude": 130.846789, 160 | ... 161 | }, 162 | { 163 | "name": "Canberra", 164 | "code": "CBR", 165 | "latitude": -35.28346, 166 | "longitude": 149.12807, 167 | ... 168 | }, 169 | { 170 | "name": "Townsville", 171 | "code": "TSV", 172 | "latitude": -19.25097, 173 | "longitude": 146.81012, 174 | ... 175 | } 176 | ], 177 | ... 178 | }, 179 | { 180 | "name": "New Zealand", 181 | "numeric": 554, 182 | "code": "NZ", 183 | "servers": { 184 | "pro": "https://nz-api.neuron-mobility.com" 185 | }, 186 | "cities": [ 187 | { 188 | "name": "Auckland", 189 | "code": "AKL", 190 | "latitude": -36.8485, 191 | "longitude": 174.7633, 192 | ... 193 | }, 194 | { 195 | "name": "Dunedin", 196 | "code": "DUD", 197 | "latitude": -45.8666632, 198 | "longitude": 170.499998, 199 | ... 200 | } 201 | ], 202 | ... 203 | }, 204 | { 205 | "name": "United Kingdom", 206 | "numeric": 826, 207 | "code": "GB", 208 | "digitalLines": [ 209 | { 210 | "nickName": "Digital Line UK", 211 | "lang": "en" 212 | } 213 | ], 214 | "servers": { 215 | "pro": "https://gb-api.neuron-mobility.com" 216 | }, 217 | "cities": [ 218 | { 219 | "name": "Slough", 220 | "code": "SLG", 221 | "latitude": 51.50949, 222 | "longitude": -0.59541, 223 | ... 224 | }, 225 | { 226 | "name": "Newcastle", 227 | "code": "NCL", 228 | "latitude": 54.97328, 229 | "longitude": -1.61396, 230 | ... 231 | } 232 | ], 233 | ... 234 | }, 235 | { 236 | "name": "Korea", 237 | "numeric": 410, 238 | "code": "KR", 239 | "servers": { 240 | "pro": "https://kr-api.neuron-mobility.com" 241 | }, 242 | "cities": [ 243 | { 244 | "name": "Seoul", 245 | "code": "SEL", 246 | "latitude": 37.564539, 247 | "longitude": 126.980798, 248 | ... 249 | } 250 | ], 251 | ... 252 | } 253 | ] 254 | } 255 | ``` 256 | 257 |
258 | 259 | To get the auth header (jwt), you have to login. First you have to aquire an password / login code that is sent to you by sms: 260 | ``` 261 | POST https://au-api.neuron-mobility.com/api/auth/sms 262 | 263 | timestamp: 1616944256428 264 | sign: 6cb9133f9d760f10d87541f060210aef 265 | 266 | {"countryCode":"61","mobile":"401061840"} 267 | ``` 268 | 269 | After that, you can get the jwt auth header: 270 | ``` 271 | POST https://au-api.neuron-mobility.com/api/auth/v1/login 272 | 273 | timestamp: 1616944354546 274 | sign: 4133bccf633a388c127484da67e8692d 275 | 276 | {"password": "763618","username": "401061840"} 277 | 278 | { 279 | "data": { 280 | "token": "", 281 | "refreshToken": "", 282 | "isNewUser": true, 283 | "emailMarketing": true, 284 | "notificationMarketing": true, 285 | "locationMarketing": true, 286 | "usageTracking": true, 287 | "smsMarketing": true 288 | } 289 | } 290 | ``` 291 | 292 | To get positions, put the jwt into the `X-Authorization` header: 293 | ``` 294 | GET https://au-api.neuron-mobility.com/api/scooters/userMap?distance=335&latitude=-27.469746172603777&longitude=153.02297267664835 295 | 296 | X-Authorization: Bearer 297 | timestamp: 1616942614041 298 | sign: 261492ea9bbf5bafca7ddc6dbba86c5d 299 | 300 | { 301 | "data": [{ 302 | "id": 5372, 303 | "qrCode": "51207883", 304 | "vehicleType": "SCOOTER", 305 | "helmetLock": true, 306 | "status": "IN_STATION", 307 | "latitude": -27.46785, 308 | "longitude": 153.02368, 309 | "remainingBattery": 0.46, 310 | "generation": "N3", 311 | "remainingRange": 13 312 | }, { 313 | "id": 6004, 314 | "qrCode": "51250199", 315 | "vehicleType": "SCOOTER", 316 | "helmetLock": true, 317 | "status": "IN_STATION", 318 | "latitude": -27.46757, 319 | "longitude": 153.02251, 320 | "remainingBattery": 0.67, 321 | "parkingTime": 1616934876000, 322 | "generation": "N3", 323 | "remainingRange": 32, 324 | "photoView": { 325 | "large": "https://images.neuron.sg/fit-in/800x/AU/SCOOTER/bf77e248d81145c4a548.JPEG", 326 | "small": "https://images.neuron.sg/fit-in/200x/AU/SCOOTER/bf77e248d81145c4a548.JPEG" 327 | } 328 | } 329 | //... 330 | ] 331 | } 332 | ``` 333 | 334 | Credit [@robbi5](https://github.com/robbi5) 335 | -------------------------------------------------------------------------------- /Bolt.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Bolt (E-Scooters) 4 | [Bolt](https://bolt.eu/) is a E-Scooter sharing service in the EU. 5 | 6 | 7 | ## Get mobile number in international format 8 | This request step is performed by the app after entering your phone number, but seems to be **fully optional**. 9 | 10 | Send a ```GET``` request to ```https://user.bolt.eu/user/getMobileNumberInInternationalFormat``` with the following parameters: 11 | 12 | | Parameter|Value (examples used in test)| 13 | |-------------|-------------| 14 | |phone|*Phone number (URL encoded!)*| 15 | |version|CI.23.0| 16 | |deviceId|*UUID value*| 17 | |deviceType|iphone| 18 | |device_name|iPhone12,3| 19 | |device_os_version|iOS15.0| 20 | |language|en| 21 | 22 | All values except the phone number seem to be for analytic purposes only. (uuid too) 23 | 24 | You'll receive a response like this: 25 | ```json 26 | { 27 | "code": 0, 28 | "message": "OK", 29 | "data": { 30 | "phone_number_in_international_format": "+4912345678901" 31 | } 32 | } 33 | ``` 34 | 35 | ## Request SMS OTP 36 | Send a ```POST```request to ```https://user.bolt.eu/user/register/phone``` with the following (json-)body: 37 | ```json 38 | { 39 | "phone" : "", 40 | "phone_uuid": "UUID Value" 41 | } 42 | ``` 43 | 44 | | Parameter|Value (examples used in test)| 45 | |-------------|-------------| 46 | |preferred_verification_method|sms| 47 | |version|CI.23.0| 48 | |deviceId|*UUID value*| 49 | |deviceType|iphone| 50 | |device_name|iPhone12,3| 51 | |device_os_version|iOS15.0| 52 | |language|en| 53 | 54 | You will receive a response like this: 55 | 56 | ```json 57 | { 58 | "code": 0, 59 | "message": "OK", 60 | "data": { 61 | "verification": { 62 | "method": "sms", 63 | "confirmation_data": {} 64 | }, 65 | "resend_confirmation_interval_ms": 20000 66 | } 67 | } 68 | ``` 69 | And an SMS containing the OTP (4 digit numeric). 70 | 71 | ⚠️ Warning: All requests, even failed ones, will return a 200 Status code. Check ```response.code``` to be 0 for success. 72 | 73 | ## Submitting the SMS OTP 74 | Send a ```POST``` request to ```https://node.bolt.eu/user/user/v1/confirmVerification``` with this body containing the obtained OTP: 75 | ``` json 76 | { 77 | "phone" : "", 78 | "phone_uuid" : "", 79 | "verification" : { 80 | "confirmation_data" : { 81 | "code" : "1234" 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | Parameters are the following: 88 | | Parameter|Value (examples used in test)| 89 | |-------------|-------------| 90 | |preferred_verification_method|sms| 91 | |version|CI.23.0| 92 | |deviceId|*UUID value*| 93 | |deviceType|iphone| 94 | |device_name|iPhone12,3| 95 | |device_os_version|iOS15.0| 96 | |language|en| 97 | 98 | You will receive a response like this: 99 | ```json 100 | { 101 | "code": 0, 102 | "message": "OK", 103 | "data": { 104 | "id": 12345678, 105 | "phone": "+4911234680", 106 | "first_name": "John", 107 | "last_name": "Doe", 108 | "email": "your@email.com", 109 | "birthday": "1980-01-01", 110 | "allow_sending_news": 1, 111 | "language": "en", 112 | "allowed_events": {} 113 | } 114 | } 115 | ``` 116 | This seems to mark the used ```deviceId``` as authenticated internally. Reuse it for all future requests. You must include the header "Authorization" with the value "Basic \:\" in Base64. 117 | The ```DeviceId``` parameter seems to be ignored. 118 | 119 | ## Finding nearby scooters 120 | Send a ```GET``` request to ```https://rental-search.bolt.eu/categoriesOverview``` with the following parameters: 121 | |Parameter | Value (examples used in test)| 122 | |-------------|-------------| 123 | | lat|50.00000000| 124 | |lng|10.00000000| 125 | |version|CI.24.0| 126 | |deviceId|*UUID value*| 127 | |deviceType|iphone| 128 | |device_name|iPhone12,3| 129 | |device_os_version|iOS15.0| 130 | |language|en| 131 | 132 | The response looks something like this: 133 | ```json 134 | { 135 | "code": 0, 136 | "message": "OK", 137 | "data": { 138 | ... 139 | "server_url": "https://germany-rental.taxify.eu", 140 | "categories": [ 141 | { 142 | "id": 396, 143 | "price_rate": { 144 | "duration_rate_str": "0.01€/MIN", 145 | "unlock_rate_str": "0.00€", 146 | "reservation_rate_str": "0.01€/MIN", 147 | "full_rate_str": "Entsperren 0.00€ • 0.01€/MIN", 148 | "fine_rate_str": "10.00€", 149 | ... 150 | }, 151 | "messages": { 152 | "booking": { 153 | "confirmation": { 154 | "title": "Reservieren für 0.01€/MIN", 155 | "description": "Du kannst das Fahrzeug für bis zu 30 Minuten reservieren. Die Abrechnung beginnt sobald du die Reservierung bestätigst." 156 | }, 157 | ... 158 | } 159 | }, 160 | "validations": { 161 | "low_battery_warning": 5 162 | }, 163 | "vehicles": [ 164 | { 165 | "id": 160062, 166 | "name": "XXX-798", 167 | "type": "scooter", 168 | "lat": 49.59605026245117, 169 | "lng": 11.002751350402832, 170 | "charge": 56, 171 | "distance_on_charge": 12960, 172 | "search_category_id": 396 173 | }, 174 | ... 175 | ] 176 | } 177 | ], 178 | "vehicle_type_config": { 179 | "scooter": { 180 | "images": { 181 | "map_marker_url": "https://images.bolt.eu/mobile/ic_scooter_map_icon.png", 182 | "details_url": "https://images.bolt.eu/mobile/scooter_details.png", 183 | "parking_photo_overlay_url": "https://images.bolt.eu/mobile/scooter_photo_overlay.png" 184 | } 185 | } 186 | }, 187 | "city_area_filters": [ 188 | { 189 | "key": "micromobility_vehicle_type", 190 | "value": "scooter" 191 | } 192 | ] 193 | } 194 | } 195 | ``` 196 | 197 | Please note the ```server_url``` aswell as the ```id``` under ```categories``` (city id) as you need them later. 198 | 199 | ## Get scooter internal information 200 | Gets data about the scooter, including ```vehicle_id```, battery percentage and pricing. 201 | send a ```GET``` request to ```{server_url}/client/getVehicleDetails```. Replace ```{server_url}``` with the ```server_url``` from prevoius request. 202 | The Endpoint requires Basic authentication. 203 | 204 | Parameters: 205 | | Parameter|Value (examples used in test)| 206 | |-------------|-------------| 207 | |vehicle_uuid|*Scooter code under QR*| 208 | |version|CI.23.0| 209 | |deviceId|*UUID value*| 210 | |gps_lat|50.00000000 | 211 | |gps_lng |10.00000000| 212 | 213 | Note: ```gps_lat``` and ```gps_lng``` are getting completely ignored. 214 | 215 | Response: 216 | ```json 217 | { 218 | "code": 0, 219 | "message": "OK", 220 | "data": { 221 | "search_category_id": 345, 222 | "validations": { 223 | "low_battery_warning": 5 224 | }, 225 | "price_rate": { 226 | "duration_rate_str": "0.19€/MIN", 227 | "unlock_rate_str": "0.00€", 228 | "reservation_rate_str": "0.09€/MIN", 229 | "full_rate_str": "Unlock 0.00€ • 0.19€/MIN", 230 | "fine_rate_str": "20.00€" 231 | }, 232 | "vehicle": { 233 | "id": 454945, 234 | "lat": 52.504005, 235 | "lng": 13.337695, 236 | "charge": 90, 237 | "name": "XXX-878", 238 | "type": "scooter", 239 | "distance_on_charge": 30400, 240 | "search_category_id": 345 }, 241 | "city_area_filters": [{ 242 | "key": "search_category_id", 243 | "value": "345" 244 | }] 245 | } 246 | } 247 | ``` 248 | 249 | Here again, is the city id ```value``` under ```city_area_filters```. 250 | 251 | ## Ring scooter 252 | Make an scooter ring an blink. 253 | send a ```GET``` request to ```{server_url}/client/ringVehicle```. Replace ```{server_url}``` with the ```server_url``` from prevoius request. 254 | The Endpoint requires Basic authentication. 255 | 256 | Parameters: 257 | | Parameter|Value (examples used in test)| 258 | |-------------|-------------| 259 | |version|CI.23.0| 260 | |deviceId|*UUID value*| 261 | |gps_lat|50.00000000 | 262 | |gps_lng |10.00000000| 263 | |deviceType|iphone| 264 | |device_name|iPhone12,3| 265 | |device_os_version|iOS15.0| 266 | |language|en| 267 | 268 | Note: ```gps_lat``` and ```gps_lng``` need to be close to the scooter, but you can simply set it to the exact same location as the scooter. Ring from anywhere! 269 | 270 | Body: 271 | ```json 272 | { 273 | "vehicle_id": "123456" 274 | } 275 | ``` 276 | 277 | Vehicle_id can be gotten from "Get Scooter internal information" 278 | 279 | Response: 280 | ```json 281 | { 282 | "code": 0, 283 | "message": "OK", 284 | "data": { 285 | "snackbar": { 286 | "text_html": "Scooter XXX-878 is ringing...", 287 | "id": "ring-vehicle-snackbar" 288 | } 289 | } 290 | } 291 | ``` 292 | 293 | -------------------------------------------------------------------------------- /Nextbike.md: -------------------------------------------------------------------------------- 1 | # Nextbike (Worldwide) 2 | 3 | ## API 4 | 5 | URL: `https://api.nextbike.net/maps/nextbike-live.json` as JSON or `https://api.nextbike.net/maps/nextbike-live.xml` as XML. 6 | 7 | ### Filtering 8 | 9 | You also can filter by city, with the GET-Parameter `city`. Eg. `https://api.nextbike.net/maps/nextbike-live.json?city=362` for Berlin. 10 | In some cities there's an artificial split into separate systems (while the bikes are interchangeable) - you can provide multiple city ids separated by commas - e.g. `https://nextbike.net/maps/nextbike-official.xml?city=372,210,475` lists all Warsaw bikes. For a list of valid city values, see the section `Cities` below 11 | 12 | ### Flexzones 13 | 14 | For some cities nextbike has flexzones (free floating in these zones). At the moment these are: 15 | 16 | * Berlin `https://api.nextbike.net/reservation/geojson/flexzone_bn.json` 17 | * Dresden `https://api.nextbike.net/reservation/geojson/flexzone_sz.json` 18 | * Karlsruhe `https://api.nextbike.net/reservation/geojson/flexzone_fg.json` 19 | * Köln `https://api.nextbike.net/reservation/geojson/flexzone_kg.json` 20 | * Leipzig `https://api.nextbike.net/reservation/geojson/flexzone_le.json` 21 | * Nürnberg `https://api.nextbike.net/reservation/geojson/flexzone_nb.json` 22 | * For all zones: `https://api.nextbike.net/reservation/geojson/flexzone_all.json` 23 | 24 | ## GBFS 25 | 26 | Nextbike provides different GBFS endpoints for their different networks/brands/countries: 27 | `https://api.nextbike.net/maps/gbfs/v1/nextbike_/gbfs.json`. You can find the Network-ID in the `https://api.nextbike.net/maps/nextbike-live.json`, just search for the `"domain"` JSON Key in country root object. So GBFS endpoint for Germany would be `https://api.nextbike.net/maps/gbfs/v1/nextbike_de/gbfs.json`. Warning: Not all german citys are in the `de` endpoint, e.g. in Berlin nextbike partnered with Deezer and uses Network-ID/country code `bn`. 28 | 29 | 30 | ## Cities 31 | Currently Nextbike operates in the following regions: 32 | |Country Code|City Name|Bikeshare Name|city id| 33 | |----|----|----|---| 34 | |AT|Innsbruck|Stadtrad Innsbruck Austria|199| 35 | |AT|Klagenfurt|nextbike Klagenfurt Austria|396| 36 | |AT|Kufstein|VVT REGIORAD Tirol|773| 37 | |AT|Linz|city bike Linz|692| 38 | |AT|Neusiedler See|nextbike Burgenland Austria|23| 39 | |AT|Serfaus|nextbike Tirol Austria|286| 40 | |AT|St.Pölten|nextbike Niederösterreich Austria|57| 41 | |AT|Wien|WienMobil Rad|748| 42 | |BA|Banja Luka|BL bike (BIH)|494| 43 | |BA|Sarajevo|nextbike BIH|350| 44 | |BA|Zenica|Zenica (BIH)|427| 45 | |CH|Sursee Plus|nextbike Switzerland|88| 46 | |CY|Limassol|nextbike Cyprus|190| 47 | |CZ|Benešov|nextbike Benešov|887| 48 | |CZ|Berounsko|nextbike Berounsko|655| 49 | |CZ|Brno|nextbike Brno|660| 50 | |CZ|Česká Třebová|nextbike Česká Třebová|869| 51 | |CZ|Dvůr Králové|nextbike Dvůr Králové|768| 52 | |CZ|Frýdek-Místek|nextbike Frýdek-Místek|700| 53 | |CZ|Havířov|nextbike Havířov|637| 54 | |CZ|Hořice|nextbike Hořice|888| 55 | |CZ|Hradec Králové|nextbike Hradec Králové|682| 56 | |CZ|Jablonec nad Nisou|nextbike Jablonec|876| 57 | |CZ|Jihlava|nextbike Jihlava|719| 58 | |CZ|Kladno|nextbike Kladno|659| 59 | |CZ|Klášterec nad Ohří|nextbike Klášterec nad Ohří|866| 60 | |CZ|Krnov|nextbike Krnov|707| 61 | |CZ|Mladá Boleslav|nextbike Mladoboleslavsko|681| 62 | |CZ|Moravská Třebová|nextbike Moravská Třebová|865| 63 | |CZ|Olomouc|nextbike Olomouc|663| 64 | |CZ|Opava|nextbike Opava|662| 65 | |CZ|Ostrava|nextbike Ostrava|271| 66 | |CZ|Pardubice|nextbike Pardubice|680| 67 | |CZ|Písek|nextbike Písek|709| 68 | |CZ|Praha|nextbike Praha|661| 69 | |CZ|Přerov|nextbike Přerov|917| 70 | |CZ|Prostejov|nextbike Prostejov|549| 71 | |CZ|Rychnovsko|nextbike Rychnovsko|708| 72 | |CZ|Třebíč|nextbike Třebíč|863| 73 | |CZ|Uherské Hradiště|nextbike Uherské Hradiště|702| 74 | |CZ|Vrchlabí|nextbike Vrchlabí|769| 75 | |CZ|Zlín|nextbike Zlín|703| 76 | |DE|Augsburg|SWA Rad|178| 77 | |DE|Bad Oeynhausen|Oeynrad|689| 78 | |DE|Bergisches e-Bike|Bergisches e-Bike|676| 79 | |DE|Berlin|nextbike Berlin|362| 80 | |DE|Bielefeld|meinSiggi|16| 81 | |DE|Bonn|Bonn nextbike|547| 82 | |DE|Braunschweig|Nibelungen-Bike|657| 83 | |DE|Bremen|WK-Bike (Bremen)|379| 84 | |DE|Dortmund|metropolradruhr Germany|129| 85 | |DE|Dresden|MOBIbike|685| 86 | |DE|Düsseldorf|nextbike Düsseldorf|50| 87 | |DE|Eifel e-Bike|Eifel e-Bike|706| 88 | |DE|Erfurt|nextbike Erfurt|493| 89 | |DE|Erkelenz|westBike|807| 90 | |DE|Erlangen|nextbike Erlangen|829| 91 | |DE|Frankfurt|nextbike Frankfurt|8| 92 | |DE|Freiburg|Frelo Freiburg|619| 93 | |DE|Gießen|nextbike Gießen|467| 94 | |DE|Graben|Graben - ready4green|647| 95 | |DE|Grünheide (Mark)|EDEKA Grünheide|819| 96 | |DE|Gütersloh|nextbike Gütersloh|160| 97 | |DE|Hanau|Heraeus Hanau|301| 98 | |DE|Hannover|sprintRAD|87| 99 | |DE|Heidelberg|VRNnextbike|194| 100 | |DE|Karlsruhe|KVV.nextbike|21| 101 | |DE|Kassel|nextbike Kassel|462| 102 | |DE|Köln|KVB Rad Germany|14| 103 | |DE|Lahr|nextbike Lahr (Pedelecs)|505| 104 | |DE|Leipzig|nextbike Leipzig|1| 105 | |DE|Leverkusen|wupsiRad Leverkusen|607| 106 | |DE|Lippstadt|nextbike Lippstadt|536| 107 | |DE|Marburg|nextbike Marburg|438| 108 | |DE|Mönchengladbach|NEW MöBus nextbike|530| 109 | |DE|Norderstedt|nextbike Norderstedt|177| 110 | |DE|Nürnberg|VAG_Rad|626| 111 | |DE|Offenburg|nextbike Offenburg|155| 112 | |DE|Oldenburg|OLi-Bike|754| 113 | |DE|Potsdam|Potsdam Rad|158| 114 | |DE|REVG|mobic|817| 115 | |DE|Roche - Mannheim|DCS Bike|860| 116 | |DE|RSVG|RSVG-Bike|691| 117 | |DE|Rüsselsheim am Main|nextbike Rüsselsheim am Main|439| 118 | |DE|RVK e-Bike|RVK|648| 119 | |DE|SAP Walldorf|SAP Walldorf|592| 120 | |DE|Schkeuditz|Landkreis Nordsachsen - Deutschland|867| 121 | |DE|Tecklenburg|Tecklenburger Land|858| 122 | |DE|Wiesbaden|nextbike Wiesbaden|7| 123 | |DE|Winsen (Luhe)|WinsenRad|794| 124 | |ES|Barcelona|Ambici |833| 125 | |ES|Bilbaobizi|Bilbaobizi (Bilbao)|532| 126 | |ES|Getxobizi|Getxobizi|749| 127 | |ES|Ibiza-City|ibizi|629| 128 | |ES|Las Palmas de Gran Canaria|Sitycleta (Las Palmas)|408| 129 | |ES|León|nextbike León|701| 130 | |ES|Lovesharing (Canary Islands) - Gran Canaria|Lovesharing (Canary Islands)|638| 131 | |ES|Mislata|BiciMislata Mislata|835| 132 | |ES|Palma|BiciPalma|789| 133 | |ES|Urdaibai|BBK Klimabizi|772| 134 | |GB|Belfast|BelfastBikes|238| 135 | |GB|Brunel University|Santander Cycles - Brunel|487| 136 | |GB|Cardiff|OVO Bikes Cardiff & Vale of Glamorgan|476| 137 | |GB|Exeter|Co-bikes|354| 138 | |GB|Glasgow|OVO Bikes Glasgow|237| 139 | |GB|Milton Keynes|Santander Cycles - Milton Keynes|320| 140 | |GB|Stirling|nextbike Stirling|243| 141 | |GB|Swansea University|Santander Cycles - Swansea|486| 142 | |HR|Brinje|Općina Brinje (Croatia)|325| 143 | |HR|Drniš|Grad Drniš (Croatia)|426| 144 | |HR|Ivanic Grad|Grad Ivanić-Grad (Croatia)|337| 145 | |HR|Jastrebarsko|Jastrebarsko (Croatia)|425| 146 | |HR|Karlovac|Grad Karlovac (Croatia)|305| 147 | |HR|Križevci|Grad Križevci (Croatia)|714| 148 | |HR|Makarska|Grad Makarska (Croatia)|324| 149 | |HR|Općina Dugopolje|Grad Split (Croatia)|445| 150 | |HR|Osijek|eMobi (Croatia)|734| 151 | |HR|Pitomača|Općina Pitomača (Croatia)|574| 152 | |HR|Poreč|Porec bike share (Croatia)|429| 153 | |HR|Pula|Grad Pula (Croatia)|798| 154 | |HR|Šibenik|Grad Šibenik (Croatia)|248| 155 | |HR|Sisak|Grad Sisak (Croatia)|416| 156 | |HR|Slavonski Brod|Grad Slavonski Brod (Croatia)|308| 157 | |HR|Stari Grad |Hvar (Croatia)|745| 158 | |HR|Velika Gorica|Grad Velika Gorica (Croatia)|415| 159 | |HR|Vinkovci|Grad Vinkovci (Croatia)|739| 160 | |HR|Vukovar|Grad Vukovar (Croatia)|328| 161 | |HR|Zadar|Grad Zadar (Croatia)|327| 162 | |HR|Zagreb|nextbike Croatia|220| 163 | |HR|Zaprešić|Grad Zaprešić (Croatia)|723| 164 | |IE|Limerick|Castletroy Bikes (Ireland)|862| 165 | |IT|Bergamo|nextbike Bergamo|728| 166 | |IT|Gorizia|Nomago Bikes - GO2GO Gorizia|771| 167 | |IT|Scandicci|ARVAL|746| 168 | |LV|Rīga|nextbike LV|128| 169 | |MX|San Luis Potosi|YOY - San Luis Potosi|664| 170 | |PL|Białystok|BIKER Białystok Poland|245| 171 | |PL|Chorzów|Kajteroz - Chorzowski Rower Miejski Poland|557| 172 | |PL|Ciechanów|Ciechanowski Rower Miejski Poland|523| 173 | |PL|Częstochowa|Częstochowski Rower Miejski Poland|450| 174 | |PL|Grodzisk Mazowiecki|GRM Grodzisk Poland|255| 175 | |PL|GZM - Test city|GZM - Poland|872| 176 | |PL|Jastrzębie-Zdrój|JasKółka|509| 177 | |PL|Katowice|Katowice Bike Poland|342| 178 | |PL|Kędzierzyn-Koźle|OK Bike Poland|421| 179 | |PL|Kołobrzeg|Kołobrzeski Rower Miejski Poland|422| 180 | |PL|Koluszki (RL)|Rowerowe Łódzkie Poland (RL)|562| 181 | |PL|Konin|Koniński Rower Miejski Poland|545| 182 | |PL|Koszalin|Koszaliński Rower Miejski Poland|496| 183 | |PL|Łomża|ŁoKeR - Łomża|796| 184 | |PL|Olesno|Oleski Rower Miejski Poland|650| 185 | |PL|Ostrów Wielkopolski|Rower Miejski w Ostrowie Wielkopolskim Poland|452| 186 | |PL|Otwock|Otwocki Rower Miejski Poland|527| 187 | |PL|Piaseczno|Piaseczyński Rower Miejski Poland|461| 188 | |PL|Pielgrzymka|System Roweru Gminnego Poland|593| 189 | |PL|Piotrków Trybunalski|Piotrkowski Rower Miejski Poland|518| 190 | |PL|Pobiedziska|Pobiedziski Rower Gminny Poland|504| 191 | |PL|Pruszków|Pruszkowski Rower Miejski Poland|442| 192 | |PL|Pszczyna|System Rowerów Miejskich w Pszczynie Poland|411| 193 | |PL|Sokołów Podlaski|Rower Powiatowy Sokołów Podlaski|727| 194 | |PL|Sosnowiec|Sosnowiecki Rower Miejski Poland|497| 195 | |PL|Szamotuły|Rower Miejski Szamotuły Poland (RMS) Poland|446| 196 | |PL|Tarnów|Tarnowski Rower Miejski Poland|548| 197 | |PL|Tychy|Tyski Rower Miejski Poland|413| 198 | |PL|Warszawa|VETURILO 3.0|812| 199 | |PL|Wolsztyn|Wolsztyński Rower Miejski|831| 200 | |PL|Wrocław|WRM nextbike Poland|148| 201 | |PL|Zielona Góra|Zielonogórski Rower Miejski Poland|529| 202 | |PL|Żyrardów|Żyrardowski Rower Miejski Poland|551| 203 | |RO|Campia Turzii|BikeCity Campia Turzii|750| 204 | |RO|Drobeta|Drobeta Velopark|693| 205 | |RO|Focșani|nextbike Romania|510| 206 | |RO|Topoloveni|Topoloveni Bike|832| 207 | |SE|Göteborg|Styr & Ställ (Sweden, Göteborg)|658| 208 | |SI|Celje|Nomago Bikes - KOLESCE|531| 209 | |SI|Kranjska Gora|Nomago Bikes - KRANJSKA GORA|780| 210 | |SI|Ljubljana|Nomago Bikes - LJUBLJANA|717| 211 | |SI|Nova Gorica|Nomago Bikes - GO2GO|688| 212 | |SI|Portorož|Nomago Bikes - PORTOROZ|806| 213 | |SI|Zagorje ob Savi|Nomago Bikes - ZANAPREJ|725| 214 | |SK|Senec|Arriva Nitra Slovakia|875| 215 | |SK|Žilina|BikeKIA|538| 216 | |UA|Lviv|nextbike (Ukraine)|280| 217 | |UA|Vinnytsia|nextbike Vinnitsa (Ukraine)|546| 218 | 219 | This table can be generated by: 220 | ```sh 221 | curl https://api.nextbike.net/maps/nextbike-live.json | jq '.countries[] | "|\(.country)|\(.cities[0].name)|\(.name)|\(.cities[0].uid)|"' | tr -d '"' | grep -v null | sort 222 | ``` 223 | -------------------------------------------------------------------------------- /Beam.md: -------------------------------------------------------------------------------- 1 | # Beam 2 | [Beam](https://www.ridebeam.com/) is a E-Scooter sharing service that operates in the Asia-Pacific Region. 3 | 4 | **Base URL**: `https://gateway.ridebeam.com/api` 5 | 6 | ## Login 7 | 8 | + Request an OTP code to be texted to you 9 | + Use the OTP code to get an Auth token 10 | 11 | Make sure to change the `latest-app-version` to the latest Beam app version (e.g. `1.38.0`). After a few updates the API can't pick up any scooters if the `User-Agent` header has an old version in it. The latest version can be found with the endpoint: `/versions` 12 | 13 | ### Request OTP Code 14 | 15 | **Method**: `POST` 16 | 17 | **Path**: `/auth/phoneverification` 18 | 19 | **Headers**: 20 | 21 | | Headers | Value | Mandatory | 22 | | ------------- | ------------------------------------- | :-------: | 23 | | Content-Type | application/json | X | 24 | | User-Agent | escooterapp/latest-app-version; ios | X | 25 | 26 | **The Body**: 27 | 28 | `{"phoneNumber":"","countryCode":"+"}` 29 | 30 | An OTP code should have been texted to you and this should be the response: 31 | 32 | ``` 33 | { 34 | "success": true, 35 | "message": "Text message sent to +XX XX-XXX-XXXX.", 36 | "response": { 37 | "carrier": "", 38 | "is_cellphone": true, 39 | "message": "Text message sent to +XX XX-XXX-XXXX.", 40 | "seconds_to_expire": 599, 41 | "uuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", 42 | "success": true 43 | }, 44 | "error": null, 45 | "data": {} 46 | } 47 | ``` 48 | 49 | ### Send back OTP code 50 | 51 | **Method**: `POST` 52 | 53 | **Path**: `/auth/verifycodeAndCreateUser` 54 | 55 | **Headers**: 56 | 57 | | Headers | Value | Mandatory | 58 | | ------------- | ------------------------------------- | :-------: | 59 | | Content-Type | application/json | X | 60 | | User-Agent | escooterapp/latest-app-version; ios | X | 61 | 62 | **The Body**: 63 | 64 | `{"phoneNumber":"","countryCode":"+","code":"","deviceName":"iPhone8,1(iPhone 6S)"}` 65 | 66 | Repsonse: 67 | 68 | ``` 69 | { 70 | "success": true, 71 | "message": "verify successfully", 72 | "data": { 73 | "jwtAccessToken": "JWT ", 74 | "refreshToken": "", 75 | "userType": null, 76 | "userId": , 77 | "id": , 78 | "firstName": null, 79 | "lastName": null, 80 | "email": null, 81 | "phoneNumber": "", 83 | "dateAcceptedConvenienceFeeTerms": null, 84 | "isNewUser": false, 85 | "isAgeVerified": null 86 | } 87 | } 88 | ``` 89 | Use the `jwtAccessToken` as your Auth Token 90 | 91 | ### Refresh Token 92 | 93 | **Method**: `POST` 94 | 95 | **Path**: `/auth/refreshaccesstoken` 96 | 97 | **Headers**: 98 | 99 | | Headers | Value | Mandatory | 100 | | ------------- | ------------------------------------- | :-------: | 101 | | Content-Type | application/json | X | 102 | | User-Agent | escooterapp/latest-app-version; ios | X | 103 | 104 | **The Body**: 105 | 106 | `{"userId":,"refreshToken":""}` 107 | 108 | Response: 109 | 110 | ``` 111 | { 112 | "success": true, 113 | "response": { 114 | "accessToken": "" 115 | } 116 | } 117 | ``` 118 | 119 | ## Get Scooter Locations 120 | 121 | **Path**: `/vehicles/getForH3/rider/detail` 122 | 123 | **Method**: `GET` 124 | 125 | **Headers**: 126 | 127 | | Headers | Value | Mandatory | 128 | | ------------ | ------------------------------------- | :-------: | 129 | | User-Agent | escooterapp/latest-app-version; ios | X | 130 | | Authorization | access-token | | 131 | 132 | **Parameters**: 133 | 134 | | Parameters | Value | Mandatory | 135 | | ---------- | ------------------------ | :-------: | 136 | | latitude | latitude | X | 137 | | longitude | longitude | X | 138 | 139 | 140 | Response: 141 | 142 | ``` 143 | { 144 | "success": true, 145 | "message": "success", 146 | "response": {}, 147 | "error": "", 148 | "data": { 149 | "vehicles": [ 150 | { 151 | "code": "81004", --- Vehicle ID written on the scooter 152 | "formFactor": "escooter", 153 | "latitude": -41.295956, 154 | "longitude": 174.778755, 155 | "batteryLevel": 49, 156 | "id": 29463, --- Internal ID used for sending commands to the scooter 157 | "status": 0, 158 | "lostAndHiddenFromMaps": false, 159 | "undeployableDamaged": false, 160 | "vehicleModel": "NINEBOT_MODEL_MAX_SWAPPABLE", 161 | "lastPhoneLocationTime": "2021-06-04T07:50:30.419Z", 162 | "lastPhoneLocation": [ 163 | 174.77869774531294, 164 | -41.29596706766972 165 | ], 166 | "latestOmniIotLocation": [ 167 | 174.778755, 168 | -41.295956 169 | ], 170 | "latestUserReportedLocation": [ 171 | 174.77597826176768, 172 | -41.289671394081324 173 | ] 174 | }, 175 | ... 176 | ], 177 | "isPartialResponse": false 178 | } 179 | } 180 | ``` 181 | 182 | ## Get Zones 183 | 184 | **Path**: `/geoRegion/location` 185 | 186 | **Method**: `GET` 187 | 188 | **Headers**: 189 | 190 | | Headers | Value | Mandatory | 191 | | ------------ | ------------------------------------- | :-------: | 192 | | User-Agent | escooterapp/latest-app-version; ios | X | 193 | | Authorization | | | 194 | 195 | **Parameters**: 196 | 197 | | Parameters | Value | Mandatory | 198 | | ---------- | ------------------------ | :-------: | 199 | | latitude | latitude | X | 200 | | longitude | longitude | X | 201 | | userMode | 0 | X | 202 | 203 | 204 | Response: 205 | 206 | ``` 207 | "response": { 208 | "polygon": { 209 | "type": "Polygon", 210 | "coordinates": [ 211 | [ 212 | [ 213 | 171.578979492188, 214 | -44.0026935032532 215 | ], 216 | [ 217 | 173.309326171875, 218 | -44.0026935032532 219 | ], 220 | [ 221 | 173.309326171875, 222 | -43.0588546064345 223 | ], 224 | [ 225 | 171.578979492188, 226 | -43.0588546064345 227 | ], 228 | [ 229 | 171.578979492188, 230 | -44.0026935032532 231 | ] 232 | ] 233 | ], 234 | "crs": { 235 | "type": "name", 236 | "properties": { 237 | "name": "urn:ogc:def:crs:EPSG::4326" 238 | } 239 | } 240 | }, 241 | ``` 242 | 243 | ## Find specific scooter info / Last Parking Photo 244 | 245 | **Path**: `/vehicles//rider` 246 | 247 | **Method**: `GET` 248 | 249 | **Headers**: 250 | 251 | | Headers | Value | Mandatory | 252 | | ------------ | ------------------------------------- | :-------: | 253 | | User-Agent | escooterapp/latest-app-version; ios | X | 254 | | Authorization | | | 255 | 256 | Response: 257 | 258 | ``` 259 | { 260 | "id": 5124, 261 | "vehicleType": 1, 262 | "vehicleModel": "NINEBOT_MODEL_MAX_SWAPPABLE", 263 | "batteryStateOfChargePercentage": 74, 264 | "code": "20224", 265 | "status": 6, 266 | "estimatedBattery": 74, 267 | "lastReportedBattery": 74, 268 | "batteryUpdated": "2020-10-27T21:20:40.138Z", 269 | "totalLifeTimeRides": 120, 270 | "lastPhoneLocation": { 271 | "type": "Point", 272 | "coordinates": [ 273 | 172.6391947, 274 | -43.5451468 275 | ] 276 | }, 277 | "lastPhoneLocationTime": "2020-10-27T06:11:30.750Z", 278 | "lastOysterAfterMotionSingleReport": null, 279 | "lastOysterAfterMotionSingleReportTime": null, 280 | "lastPhoneLocationWasBLEConnected": true, 281 | "lastPhoneLocationBLERSSI": -67, 282 | "locationUpdated": "2020-10-27T04:57:26.777Z", 283 | "lastOysterHeartbeatReport": null, 284 | "lastOysterHeartbeatReportTime": null, 285 | "lastReportedLocationTrackerMotion": null, 286 | "lastReportedTimeTrackerMotion": null, 287 | "lastCollectedTime": "2020-10-27T04:57:26.777Z", 288 | "lastRideStartTime": "2020-10-25T04:18:52.170Z", 289 | "lastActiveTime": "2020-10-25T04:22:06.143Z", 290 | ... 291 | } 292 | ``` 293 | 294 | ## Find Parking Spots 295 | 296 | **Path**: `/idealLocation/latlng` 297 | 298 | **Method**: `GET` 299 | 300 | **Headers**: 301 | 302 | | Headers | Value | Mandatory | 303 | | ------------ | ------------------------------------- | :-------: | 304 | | User-Agent | escooterapp/latest-app-version; ios | X | 305 | | Authorization | jwtAccessToken | | 306 | 307 | **Parameters**: 308 | 309 | | Parameters | Value | Mandatory | 310 | | ---------- | ------------------------ | :-------: | 311 | | latitude | latitude | X | 312 | | longitude | longitude | X | 313 | 314 | 315 | Response: 316 | 317 | ``` 318 | { 319 | "id": 3436, 320 | "pinLocation": { 321 | "type": "Point", 322 | "coordinates": [ 323 | 172.6087478011045, 324 | -43.49812043502988 325 | ] 326 | }, 327 | "currentNumber": 0, 328 | "idealNumber": 3, 329 | "radius": 50, 330 | "deleted": false, 331 | "createdAt": "2020-08-04T10:21:34.242Z", 332 | "updatedAt": "2020-08-04T12:14:59.498Z", 333 | "locationName": "481 Papanui Road, Papanui, Christchurch 8053, New Zealand", 334 | "message": "A BOOSTER OFFER IS AVAILABLE NEAR HERE", 335 | "title": "Papanui Road", 336 | "imageUrl": "https://pin-location-images.s3-ap-southeast-1.amazonaws.com/images/BurgerFuel_Papanui.jpg.jpeg", 337 | "qrCode": "", 338 | "isHiddenFromCharger": false, 339 | "isHiddenFromRider": false, 340 | "isHiddenFromAdmin": false, 341 | "subtitle": "470-466 Papanui Road", 342 | "characteristics": [ 343 | "operation_booster" 344 | ], 345 | "properties": {}, 346 | "cityId": 14, 347 | "geofenceId": 136 348 | }, 349 | ``` 350 | -------------------------------------------------------------------------------- /Emmy.md: -------------------------------------------------------------------------------- 1 | # Emmy 2 | 3 | [Emmy](https://emmy-sharing.de/) is a german rental service for electric 4 | motorbikes 🛵 (also called scooter, the wording is a mess). Currently, Emmy 5 | offers its service in Berlin, Hamburg and Munich. 6 | 7 | ## General stuff 8 | API is based on exchanging json messages. 9 | **Base url**: `https://api.emmy.ninja` 10 | 11 | Below you find most API calls that the official emmy android app uses. However, 12 | some of them are not really tested. Additionally, there are even more API calls 13 | mostly related to the verification process (Upload video etc). 14 | 15 | ## Auth 16 | 17 | ### Login 18 | ```bash 19 | curl --location --request POST 'https://api.emmy.ninja/auth/login' \ 20 | --header 'Content-Type: application/json' \ 21 | --data-raw '{ 22 | "password": "pass", 23 | "username": "email" 24 | }' 25 | ``` 26 | 27 | You'll receive a bunch of information related to your user. For authentication 28 | the `accessToken` is needed. With that, you can create the authorization header 29 | as following: `Authorization: Bearer $ACCESS_TOKEN`. Sometimes you need the 30 | `signupToken` instead of the accessToken. When ever this is needed the 31 | authorization is written that way: `Authorization: Bearer $SIGNUP_TOKEN` 32 | 33 | ### Logout 34 | There is also an option to log out (invalidate the accessToken?) 35 | tbc 36 | 37 | ### Reset Password 38 | 39 | ```bash 40 | curl --location --request POST 'https://api.emmy.ninja/reset-password' \ 41 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 42 | --header 'Content-Type: application/json' \ 43 | --data-raw '{ 44 | "email": "mail@example.org" 45 | }' 46 | ``` 47 | 48 | ### Renew Password 49 | 50 | ```bash 51 | curl --location --request POST 'https://api.emmy.ninja/users/$USER_ID/password' \ 52 | --header 'Authorization: Bearer $SIGNUP_TOKEN' \ 53 | --header 'Content-Type: application/json' \ 54 | --data-raw '{ 55 | "email": "mail@example.org", 56 | "oldPassword": "foo", 57 | "newPassword": "bar" 58 | }' 59 | ``` 60 | 61 | 62 | ## Vehicle related information 63 | Some requests work without suppling authorization as they are public 64 | information. 65 | 66 | ### List vehicles 67 | Lists all vehicles that are available for renting. 68 | 69 | ```bash 70 | curl --location --request GET 'https://api.emmy.ninja/vehicles' 71 | ``` 72 | 73 | ### Show vehicle 74 | 75 | Show information for a specific vehicle by `$VEHICLE_ID`. Get the ID by 76 | calling the general `vehicles` endpoint. Note: The ID seems to be 77 | auto-increment. You'll find more vehicles by simply incrementing the ID - even 78 | those that are currently not available for rental. 79 | 80 | ```bash 81 | curl --location --request GET 'https://api.emmy.ninja/vehicles/$VEHICLE_ID' 82 | ``` 83 | 84 | ### List Vehicle Types 85 | 86 | ```bash 87 | curl --location --request GET 'https://api.emmy.ninja/vehicles/types' 88 | ``` 89 | 90 | ### Send damage report 91 | 92 | When you notice a damage on a vehicle before you go on a ride, you can send a 93 | damage report. Note, that you need a rentalId for it that you obtain when 94 | [creating a rental](#create-rental) on a vehicle. 95 | 96 | ```bash 97 | curl --location --request POST 'https://api.emmy.ninja/vehicles/$VEHICLE_ID/damages' \ 98 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 99 | --data-raw '{ 100 | "description": "A human readable description of the damage", 101 | "damages": [ 102 | { 103 | "position": "foo", 104 | "type": "bar" 105 | } 106 | ], 107 | "rentalId": $RENTAL_ID 108 | }' 109 | ``` 110 | 111 | 112 | ## Rental 113 | 114 | ### Create Rental 115 | In order to create a rental, you need a `$VEHICLE_ID`. 116 | 117 | ```bash 118 | curl --location --request POST 'https://api.emmy.ninja/vehicles/$VEHICLE_ID/rentals' \ 119 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 120 | --header 'Content-Type: application/json' \ 121 | --data-raw '{}' 122 | ``` 123 | This starts a reservation and you have 15 min time to actually start the rental. 124 | In the response you receive a rentalId that you have to store as `$RENTAL_ID` 125 | for later usage. 126 | 127 | ### Start Rental 128 | 129 | :warning: Calling this method successfully will charge your account! You have to 130 | pay the price that is given in the [vehicle information](#show-vehicle) as 131 | `pricingDuringRide`. 132 | 133 | ```bash 134 | curl --location --request POST 'https://api.emmy.ninja/vehicles/$VEHICLE_ID/rentals/$RENTAL_ID/start' \ 135 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 136 | --header 'Content-Type: application/json' \ 137 | --data-raw '{ 138 | "isCarClean": false, 139 | "isCarDamaged": false, 140 | "isDriversLicenceWithUser": false 141 | }' 142 | ``` 143 | 144 | Note: you can set all values to `true` or `false`. The API doesn't care and will 145 | start the rental either way. 146 | 147 | ### Stop Rental 148 | 149 | ```bash 150 | curl --location --request POST 'https://api.emmy.ninja/vehicles/$VEHICLE_ID/rentals/$RENTAL_ID/stop' \ 151 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 152 | --header 'Content-Type: application/json' \ 153 | --data-raw '{}' 154 | ``` 155 | 156 | ### Pause Rental 157 | 158 | You can also pause the rental if you want to park your vehicle and but want to 159 | use it later. This will reduce the price per minute as specified in 160 | `pricingDuringPark` in the [vehicle information](#show-vehicle) 161 | 162 | 163 | ```bash 164 | curl --location --request POST 'https://api.emmy.ninja/vehicles/$VEHICLE_ID/rentals/$RENTAL_ID/park' \ 165 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 166 | --header 'Content-Type: application/json' \ 167 | --data-raw '{}' 168 | ``` 169 | 170 | 171 | ## User relatet stuff 172 | The API exposes some information on the user. 173 | 174 | ### Create User 175 | :warning: This Endpoint is not tested. No valid value for `languageId` is known 176 | so far. 177 | 178 | ```bash 179 | curl --location --request POST 'https://api.emmy.ninja/users' \ 180 | --header 'Content-Type: application/json' \ 181 | --data-raw '{ 182 | "locationId": 1, 183 | "birthDate": "1980-01-01", 184 | "gender": 0, 185 | "firstName": "firstName", 186 | "lastName": "lastName", 187 | "email": "mail@example.org", 188 | "password": "password", 189 | "mobilePhone":"000000", 190 | "newsletterAccepted": false, 191 | "agbChecked": false, 192 | "planId": 1, 193 | "languageId": 0 194 | }' 195 | ``` 196 | 197 | ### User Information 198 | 199 | ```bash 200 | curl --location --request GET 'https://api.emmy.ninja/users/$USER_ID' \ 201 | --header 'Authorization: Bearer $ACCESS_TOKEN' 202 | ``` 203 | 204 | ### User Statistics 205 | Get interesting facts such as yout total number of rides or the total ridden 206 | distance in meters. 207 | 208 | ```bash 209 | curl --location --request GET 'https://api.emmy.ninja/users/$USER_ID/stats' \ 210 | --header 'Authorization: Bearer $ACCESS_TOKEN' 211 | ``` 212 | 213 | ### List Rentals 214 | 215 | List all your previous rentals. 216 | 217 | ```bash 218 | curl --location --request GET 'https://api.emmy.ninja/users/$USER_ID/rentals' \ 219 | --header 'Authorization: Bearer $ACCESS_TOKEN' 220 | ``` 221 | 222 | ### Show Rental 223 | 224 | Show a specific rental by `$RENTAL_ID`. 225 | 226 | ```bash 227 | curl --location --request GET 'https://api.emmy.ninja/users/$USER_ID/rentals/$RENTAL_ID' \ 228 | --header 'Authorization: Bearer $ACCESS_TOKEN' 229 | ``` 230 | 231 | ### Change Address 232 | 233 | When you change you address, the old address is still stored and returnd as part 234 | of the user information. The new address will become the new default. 235 | 236 | ```bash 237 | curl --location --request POST 'https://api.emmy.ninja/users/$USER_ID/addresses' \ 238 | --header 'Authorization: Bearer $SIGNUP_TOKEN' \ 239 | --header 'Content-Type: application/json' \ 240 | --data-raw '{ 241 | "firstName": "John", 242 | "lastName": "Doe", 243 | "street": "street name", 244 | "houseNumber": "99", 245 | "zipCode": "10000", 246 | "city": "city", 247 | "countryCode": "DE" 248 | }' 249 | ``` 250 | 251 | ### Show Payment Methods 252 | 253 | List current payment options. 254 | 255 | ```bash 256 | curl --location --request GET 'https://api.emmy.ninja/users/$USER_ID/payment-methods' \ 257 | --header 'Authorization: Bearer $SIGNUP_TOKEN' 258 | ``` 259 | 260 | ### Payment Credit Card 261 | 262 | :warning: This endpoint is not tested. Correct format of values are not known. 263 | 264 | ```bash 265 | curl --location --request POST 'https://api.emmy.ninja/users/$USER_ID/payment-methods/cc' \ 266 | --header 'Authorization: Bearer $SIGNUP_TOKEN' \ 267 | --header 'Content-Type: application/json' \ 268 | --data-raw '{ 269 | "number": "number", 270 | "expirationDate": "expDate", 271 | "cardHolder": "cardHolder", 272 | "type": "type", 273 | "cvc": "cvc" 274 | }' 275 | ``` 276 | 277 | ### Payment Bank Account 278 | 279 | :warning: This endpoint is not tested. Correct format of values are not known. 280 | 281 | ```bash 282 | curl --location --request POST 'https://api.emmy.ninja/users/$USER_ID/payment-methods/dd' \ 283 | --header 'Authorization: Bearer $SIGNUP_TOKEN' \ 284 | --header 'Content-Type: application/json' \ 285 | --data-raw '{ 286 | "accountHolder": "accountHolder", 287 | "sepaMandateCheck": false, 288 | "iban": "iban", 289 | "bic": "bic" 290 | }' 291 | ``` 292 | 293 | 294 | ## Other stuff 295 | 296 | ### Plans 297 | 298 | List available signup plan. 299 | 300 | ```bash 301 | curl --location --request GET 'https://api.emmy.ninja/plans' 302 | ``` 303 | 304 | ### Notifications 305 | 306 | Sometimes, Emmy notifies its user about recent events such as an increase of 307 | prices, offline fleets because of bad weather condidition, etc. 308 | 309 | ```bash 310 | curl --location --request GET 'https://api.emmy.ninja/notifications' \ 311 | --header 'Authorization: Bearer $ACCESS_TOKEN' 312 | ``` 313 | 314 | ### Locations 315 | 316 | Get the business and non-parking territories. 317 | 318 | ```bash 319 | curl --location --request GET 'https://api.emmy.ninja/locations' 320 | ``` 321 | 322 | ### Buy Credit Packages 323 | 324 | Emmy is offering prepaid credit packages so that you can lower the price per 325 | minute. 326 | 327 | :warning: When submitting this request your account is charged depending on the 328 | value of `$CREDIT_PACKAGE_CODE`. 329 | 330 | :warning: This endpoint is not tested. No valid values for `$CREDIT_PACKAGE_CODE` are known so far. You may try one of the following: 331 | `["Asphalteuphoristen", "Gelegenheitsflaneure"]` cmp. 332 | [https://emmy-sharing.de/preise/](https://emmy-sharing.de/preise/) 333 | 334 | ```bash 335 | curl --location --request POST 'https://api.emmy.ninja/credit-packages/buy' \ 336 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 337 | --header 'Content-Type: application/json' \ 338 | --data-raw '{ 339 | "code": "$CREDIT_PACKAGE_CODE" 340 | }' 341 | ``` 342 | 343 | ### Validate Promotion Codes 344 | 345 | :warning: This endpoint is not tested. 346 | 347 | Check, if a promotion code is valid an can be redeemed. You need to have a 348 | `$PROMOTION_CODE` that you might get from marketing actions. Use `SideMenu` for 349 | `$REDEMPTION_PURPOSE`. If you want to enter a promotion code from a friend, 350 | use `Registration` instead 351 | 352 | ```bash 353 | curl --location --request POST 'https://api.emmy.ninja/promotion-codes/validate' \ 354 | --header 'Content-Type: application/json' \ 355 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 356 | --data-raw '{ 357 | "code":"$PROMOTION_CODE", 358 | "redemptionPurpose": "$REDEMPTION_PURPOSE" 359 | }' 360 | ``` 361 | 362 | ### Redeem Promotion Codes 363 | 364 | :warning: This endpoint is not tested. 365 | 366 | ```bash 367 | curl --location --request POST 'https://api.emmy.ninja/promotion-codes/redeem' \ 368 | --header 'Content-Type: application/json' \ 369 | --header 'Authorization: Bearer $SIGNUP_TOKEN' \ 370 | --data-raw '{ 371 | "code": "$PROMOTION_CODE", 372 | "redemptionPurpose": "$REDEMPTION_PURPOSE" 373 | }' 374 | 375 | ``` 376 | 377 | ### Verification 378 | 379 | :warning: Currently no understanding, what this endpoint is used for. 380 | 381 | Variable `$PLATFORM` must be one of the following: `["android", "ios"]` 382 | 383 | ```bash 384 | curl --location --request GET 'https://api.emmy.ninja/verification/onfido-token' \ 385 | --header 'Emmy-Application-Platform: $PLATFORM' \ 386 | --header 'Authorization: Bearer $SIGNUP_TOKEN' 387 | verification/onfido-token 388 | ``` 389 | 390 | The response contains a JWT. But what is it used for? -------------------------------------------------------------------------------- /Flamingo.md: -------------------------------------------------------------------------------- 1 | # Flamingo 2 | [Flamingo](https://www.flamingoscooters.com/) is an E-Scooter sharing service that operates in New Zealand. 3 | 4 | ## Request OTP Code 5 | 6 | **Method**: `POST` 7 | 8 | **URL**: `https://www.googleapis.com/identitytoolkit/v3/relyingparty/sendVerificationCode` 9 | 10 | **Headers**: 11 | 12 | | Headers | Value | Mandatory | 13 | | ------------------------ | ------------------------------------- | :-------: | 14 | | X-Ios-Bundle-Identifier | com.flamingoscooters.ios | X | 15 | 16 | **Params**: 17 | 18 | | Params | Value | Mandatory | 19 | | ------------- | -------------------------------------- | :-------: | 20 | | key | AIzaSyCrkmrEuGrn9tgWfCY2rcpd-LRAnsE84ik| X | 21 | 22 | **The Body**: 23 | 24 | `{"iosReceipt":"AEFDNu-Htn7DOuqCRwPrpDA7lMKkUHJduCQPNpel6sVYukJIBM6g_ZoJxyKK__G41ND_-uEkoTQvulRec3wZXXuXxEgfpbcOF8Dftg1eLFHRiZWsCfzbezjYe1P7rx2RIBG7c2V_","iosSecret":"WlQ2n-YexIkrNlpZ","phoneNumber":"+64"}` 25 | 26 | As a result an OTP code should have been texted to and the response should include the session id: 27 | 28 | ``` 29 | { 30 | "sessionInfo": "" 31 | } 32 | ``` 33 | 34 | ## Verify OTP code 35 | 36 | **Method**: `POST` 37 | 38 | **URL**: `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhoneNumber` 39 | 40 | **Params**: 41 | 42 | | Params | Value | Mandatory | 43 | | ------------- | --------------------------------------- | :-------: | 44 | | key | AIzaSyCrkmrEuGrn9tgWfCY2rcpd-LRAnsE84ik | X | 45 | 46 | **Headers**: 47 | 48 | | Headers | Value | Mandatory | 49 | | ------------------------ | ------------------------------------- | :-------: | 50 | | X-Ios-Bundle-Identifier | com.flamingoscooters.ios | X | 51 | 52 | **The Body**: 53 | 54 | `{"code":"","operation":"SIGN_UP_OR_IN","sessionInfo":""}` 55 | 56 | This should use the OTP Code and grant you an Access and Refresh Token 57 | 58 | ``` 59 | { 60 | "idToken": "", --- Access token 61 | "refreshToken": "", 62 | "expiresIn": "3600", 63 | "localId": "", 64 | "isNewUser": false, 65 | "phoneNumber": "+64" 66 | } 67 | ``` 68 | 69 | ### Add User Info 70 | 71 | :warning: This only applies to accounts that haven't already been created, but it is recommended that you setup your account in the Flamingo app and not sign up through their API. :warning: 72 | 73 | **Method**: `POST` 74 | 75 | **URL**: `https://api.flamingoscooters.com/sign-up` 76 | 77 | **Headers**: 78 | 79 | | Headers | Value | Mandatory | 80 | | ------------------------ | ------------------------------------- | :-------: | 81 | | X-Ios-Bundle-Identifier | com.flamingoscooters.ios | X | 82 | | Authorization | USER-ID | X | 83 | 84 | **Params**: 85 | 86 | | Params | Value | Mandatory | 87 | | ------------- | -------------------------------------- | :-------: | 88 | | key | AIzaSyCrkmrEuGrn9tgWfCY2rcpd-LRAnsE84ik| X | 89 | 90 | **The Body**: 91 | 92 | `{"lastName":"","email":"","firstName":"","dateOfBirth":"1990-09-20T19:32:00+12:00"}` 93 | 94 | 95 | ## Refresh Token 96 | 97 | A token is valid for 1 hour. Use this endpoint to get a new token after the old one expires. 98 | 99 | **Method**: `POST` 100 | 101 | **URL**: `https://securetoken.googleapis.com/v1/token` 102 | 103 | **Headers**: 104 | 105 | | Headers | Value | Mandatory | 106 | | ------------------------ | ------------------------------------- | :-------: | 107 | | X-Ios-Bundle-Identifier | com.flamingoscooters.ios | X | 108 | 109 | **Params**: 110 | 111 | | Params | Value | Mandatory | 112 | | ------------- | --------------------------------------- | :-------: | 113 | | key | AIzaSyCrkmrEuGrn9tgWfCY2rcpd-LRAnsE84ik | X | 114 | 115 | **The Body**: 116 | 117 | `{"grantType":"refresh_token","refreshToken":""}` 118 | 119 | This should verify the code and grant you a Access and Refresh Token. Here's the output: 120 | 121 | ``` 122 | { 123 | "access_token": "", 124 | "expires_in": "3600", --- 1 HOUR 125 | "token_type": "Bearer", 126 | "refresh_token": "", 127 | "id_token": "", 128 | "user_id": "", 129 | "project_id": "663814096530" 130 | } 131 | ``` 132 | 133 | ## Get Scooter Locations 134 | 135 | After authenticating you can now retreive locations of scooters and zones 136 | 137 | **URL**: `https://production.api.flamingoscooters.com/vehicle/area` 138 | 139 | **Method**: `GET` 140 | 141 | **Headers**: 142 | 143 | | Headers | Description | Mandatory | 144 | | ------------ | ------------------------------------- | :-------: | 145 | | Authorization | Your access token | X | 146 | 147 | **Parameters**: 148 | 149 | | Parameters | Descriptions | Mandatory | 150 | | ------------ | ------------------------ | :-------: | 151 | | neLatitude | latitude | X | 152 | | neLongitude | longitude | X | 153 | | swLatitude | latitude | X | 154 | | swLongitude | longitude | X | 155 | | zoom | zoom integer | X | 156 | 157 | 158 | The output should be something like this: 159 | 160 | ``` 161 | { 162 | "success": true, 163 | "data": [ 164 | { 165 | "id": 23, --- ID to send commands to the scooter 166 | "object": "vehicle", 167 | "type": "SCOOTER", 168 | "model": "SEGWAY_ES", 169 | "registration": "1038", --- ID to find scooter on the street 170 | "status": "AVAILABLE", 171 | "batteryPercent": 0.97, 172 | "latitude": -43.532892, 173 | "longitude": 172.633852, 174 | "statusTime": "2021-02-25T03:50:29.000Z", --- Last Signal with the scooter 175 | "gpsTime": "2021-02-25T03:41:28.000Z", --- Last GPS Ping 176 | "remainingRange": 1856, --- KM Range (18km remaining) 177 | "pricing": { 178 | "id": 13, 179 | "object": "pricingPlan", 180 | "plan": "STANDARD", 181 | "unlock": 100, 182 | "perMin": 38, 183 | "currency": "NZD", 184 | "description": "$1 NZD to unlock, 38c per min" 185 | } 186 | }, 187 | ... 188 | ``` 189 | 190 | ## Get Zones and area info 191 | 192 | **URL**: `https://production.api.flamingoscooters.com/region/all` 193 | 194 | **Method**: `GET` 195 | 196 | **Headers**: 197 | 198 | | Headers | Description | Mandatory | 199 | | ------------ | ------------------------------------- | :-------: | 200 | | Authorization | Your access token | X | 201 | 202 | The output should be something like this: 203 | 204 | ``` 205 | { 206 | "success": true, 207 | "data": [ 208 | { 209 | "id": 4, 210 | "object": "region", 211 | "name": "Christchurch", 212 | "country": "NZ", 213 | "centreLatitude": -43.532038, 214 | "centreLongitude": 172.636572, 215 | "message": null, 216 | "messageUrl": null, 217 | "timezone": "Pacific/Auckland", 218 | "startTime": "00:00", 219 | "endTime": "24:00", 220 | "zones": [ 221 | { 222 | "id": 90, 223 | "object": "zone", 224 | "name": "Botanic Gardens", 225 | "message": "The Botanic Gardens are a no riding or parking zone.", 226 | "type": "NORIDING", 227 | "polygon": [ 228 | { 229 | "latitude": -43.529044, 230 | "longitude": 172.627315 231 | }, 232 | { 233 | "latitude": -43.529519, 234 | "longitude": 172.625196 235 | }, 236 | { 237 | "latitude": -43.529666, 238 | "longitude": 172.623678 239 | }, 240 | { 241 | "latitude": -43.529184, 242 | "longitude": 172.622128 243 | }, 244 | { 245 | "latitude": -43.528274, 246 | "longitude": 172.620159 247 | }, 248 | { 249 | "latitude": -43.527943, 250 | "longitude": 172.620261 251 | }, 252 | { 253 | "latitude": -43.527554, 254 | "longitude": 172.620068 255 | }, 256 | { 257 | "latitude": -43.527329, 258 | "longitude": 172.619607 259 | }, 260 | { 261 | "latitude": -43.527484, 262 | "longitude": 172.618995 263 | }, 264 | { 265 | "latitude": -43.52778, 266 | "longitude": 172.618539 267 | }, 268 | { 269 | "latitude": -43.528609, 270 | "longitude": 172.618153 271 | }, 272 | { 273 | "latitude": -43.52969, 274 | "longitude": 172.617927 275 | }, 276 | { 277 | "latitude": -43.530398, 278 | "longitude": 172.61819 279 | }, 280 | { 281 | "latitude": -43.532035, 282 | "longitude": 172.618603 283 | }, 284 | { 285 | "latitude": -43.532385, 286 | "longitude": 172.619006 287 | }, 288 | { 289 | "latitude": -43.532389, 290 | "longitude": 172.619725 291 | }, 292 | { 293 | "latitude": -43.532233, 294 | "longitude": 172.620309 295 | }, 296 | { 297 | "latitude": -43.531568, 298 | "longitude": 172.623474 299 | }, 300 | { 301 | "latitude": -43.531724, 302 | "longitude": 172.625084 303 | }, 304 | { 305 | "latitude": -43.532179, 306 | "longitude": 172.626012 307 | }, 308 | { 309 | "latitude": -43.532999, 310 | "longitude": 172.62716 311 | }, 312 | { 313 | "latitude": -43.533005, 314 | "longitude": 172.627323 315 | }, 316 | { 317 | "latitude": -43.529044, 318 | "longitude": 172.627315 319 | } 320 | ] 321 | }, 322 | ... 323 | ``` 324 | 325 | ### Get Scooter Parking Photo 326 | 327 | Use the `id` from the scooter output and not the `registration` id. 328 | 329 | **URL**: `https://api.flamingoscooters.com/vehicle//last-photo` 330 | 331 | **Method**: `GET` 332 | 333 | **Headers**: 334 | 335 | | Headers | Description | Mandatory | 336 | | ------------ | ------------------------------------- | :-------: | 337 | | Authorization | Your access token | X | 338 | 339 | This should output the URL for the Parking Photo. Some scooters may not have one, here's an example from a scooter that does have one: 340 | 341 | ``` 342 | { 343 | "success": true, 344 | "data": { 345 | "photoUrl": "https://storage.googleapis.com/flamingo-ugc/JmpviKnyIwwXQQRRmyEJkyEaoasFZpgM.jpg" 346 | } 347 | } 348 | ``` 349 | 350 | ## GBFS 351 | 352 | Flamingo also supports the [GBFS](https://github.com/NABSA/gbfs) system. 353 | 354 | **URL**: `https://api.flamingoscooters.com/gbfs//gbfs.json` 355 | 356 | The output should be something like this: 357 | 358 | ``` 359 | { 360 | "success": true, 361 | "data": { 362 | "bikes": [ 363 | { 364 | "bike_id": "4130", 365 | "lat": -43.528927, 366 | "lon": 172.639989, 367 | "current_range_meters": 21420, 368 | "last_reported": 1629880900 369 | }, 370 | ... 371 | ] 372 | }, 373 | "last_updated": 1629880939, 374 | "ttl": 60 375 | } 376 | ``` 377 | 378 | **Postman Collection**: https://documenter.getpostman.com/view/11220018/TVKD2xdh 379 | 380 | ## Implementations 381 | 382 | https://openscootermap.netlify.app/ 383 | 384 | https://moovitapp.com/ 385 | 386 | https://transitapp.com 387 | -------------------------------------------------------------------------------- /Lime.md: -------------------------------------------------------------------------------- 1 | # Lime 2 | 3 | ### GBFS 4 | 5 | In select cities, a GBFS feed is also available. Louisville is one for example. 6 | 7 | **URL** `https://data.lime.bike/api/partners/v1/gbfs//gbfs.json` 8 | 9 | ## Cities 10 | | United States | Germany, Austria and Switzerland | France | Canada | Israel | Norway and Italy | Australia and New Zealand | Belgium | 11 | |----------------|----------------------------------|-----------|----------|-----------|------------------|---------------------------|-----------| 12 | | baltimore | hamburg | paris | kelowna | tel_aviv | oslo | auckland | antwerp | 13 | | cleveland | oberhausen | marseille | edmonton | | rome | sydney | brussels | 14 | | detroit | opfikon | paris | | | verona | | | 15 | | grand_rapids | reutlingen | | | | | | | 16 | | new_york | solingen | | | | | | | 17 | | norfolk_va | zug | | | | | | | 18 | | washington_dc | | | | | | | | 19 | | colorado_springs| | | | | | | | 20 | | louisville | | | | | | | | 21 | | oakland | | | | | | | | 22 | | san_francisco | | | | | | | | 23 | | san_jose | | | | | | | | 24 | | seattle | | | | | | | | 25 | | chicago | | | | | | | | 26 | 27 | 28 | --- 29 | **Base URL**: `https://web-production.lime.bike/api/rider` 30 | 31 | ## Methods to authenticate 32 | 33 | ### Login with phone number 34 | 35 | #### Request sms code 36 | 37 | :warning: Account must exist :warning: 38 | 39 | **Method**: `GET` 40 | 41 | **Path**: `/v1/login` 42 | 43 | **Parameters**: 44 | 45 | | Parameters | Descriptions | 46 | | ---------- | ------------------------ | 47 | | phone | phone number intl format | 48 | 49 | 50 | When entering a phone number, make sure to include the `%2B` before the number instead of the `+` (Example: %2B12222222222 is +1 (222) 222-2222) 51 | 52 | 53 | **Example** 54 | 55 | ```bash 56 | curl --request GET \ 57 | --url 'https://web-production.lime.bike/api/rider/v1/login?phone=%2B33612345678' 58 | ``` 59 | 60 | #### Send back OTP code 61 | 62 | **Method**: `POST` 63 | 64 | **Path**: `/v1/login` 65 | 66 | **Header**: 67 | 68 | | Header | Value | 69 | | ------------ | ---------------- | 70 | | content-type | application/json | 71 | 72 | **Parameters**: 73 | 74 | | Parameters | Descriptions | 75 | | ---------- | ------------------------ | 76 | | phone | phone number intl format | 77 | | login_code | OTP code (length of 6) | 78 | 79 | 80 | **Example** 81 | 82 | ```bash 83 | curl --request POST \ 84 | --cookie-jar - \ 85 | --url 'https://web-production.lime.bike/api/rider/v1/login' \ 86 | --header 'Content-Type: application/json' \ 87 | --data '{"login_code": "123456", "phone": "+33612345678"}' 88 | ``` 89 | 90 | ```JSON 91 | { 92 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3Rva2VuIjoiRk9PQkFSRUlSWkhBMiIsImxvZ2luX2NvdW50IjoyfQ.K5nmvUu92hYXQyeYG6O0rqo0ef2mkp7PMdtp9NrgwOE", 93 | "user": { 94 | "id": "FOOBAREIRZHA2", 95 | "type": "users", 96 | "attributes": { 97 | "token": "FOOBAREIRZHA2", 98 | "phone_number": "33612345678", 99 | "email_address": null, 100 | "has_verified_email_address": false, 101 | "name": "Lime Rider", 102 | "given_name": "Lime", 103 | "surname": "Rider", 104 | "default_payment_method": null, 105 | "referral_code": "REZHETH", 106 | "num_trips": 0, 107 | "edu": false, 108 | "subscription_item_states": [], 109 | "juicer_profile_status": null, 110 | "juicer_profile_initial_activated_at": null, 111 | "balance_cents": 0, 112 | "pending_balance_cents": 0, 113 | "currency": "USD" 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | ### Login with Email 120 | 121 | #### Send Magic Link 122 | 123 | **Method**: `POST` 124 | 125 | **Path**: `/v2/onboarding/magic-link` 126 | 127 | **Body**: 128 | 129 | | Parameters | Descriptions | 130 | | ---------- | ------------------------ | 131 | | email | your email | 132 | | user_agreement_version | 4 | 133 | | user_agreement_country_code | US | 134 | 135 | **Example** 136 | 137 | ``` 138 | curl 'https://web-production.lime.bike/api/rider/v2/onboarding/magic-link' \ 139 | -X POST \ 140 | -d 'email=&user_agreement_country_code=US&user_agreement_version=4' 141 | ``` 142 | 143 | #### Use Magic Link 144 | 145 | **Method**: `POST` 146 | 147 | **Path**: `/v2/onboarding/login` 148 | 149 | **Header**: 150 | 151 | | Header | Value | 152 | | -------------- | ------------- | 153 | | X-Device-Token | random uuid | 154 | 155 | **Body**: 156 | 157 | `magic_link_token=' 166 | ``` 167 | 168 | ## Use API 169 | 170 | ### Get Vehicles and Zones 171 | 172 | :warning: Auth (bearer AND cookie) are mandatory for this endpoint 173 | 174 | **Method**: `GET` 175 | 176 | **Path**: `/v1/views/map` 177 | 178 | **Header**: 179 | 180 | | Header | Value | Mandatory | 181 | | ------------- | ------------ | :-------: | 182 | | Authorization | Bearer TOKEN | X | 183 | 184 | **Cookie**: 185 | 186 | | Cookie | Mandatory | 187 | | --------------------- | :-------: | 188 | | _limebike-web_session | X | 189 | 190 | **Parameters**: 191 | 192 | | Parameters | Descriptions | Mandatory | Remarks | 193 | | -------------------- | ------------ | :-------: |---------------------------------------------------------------------| 194 | | ne_lat | Latitude | X | Bounding box for map; apparently ignored in favor of zoom parameter | 195 | | ne_lng | Longitude | X | Bounding box for map; apparently ignored in favor of zoom parameter | 196 | | sw_lat | Latitude | X | Bounding box for map; apparently ignored in favor of zoom parameter | 197 | | sw_lng | Longitude | X | Bounding box for map; apparently ignored in favor of zoom parameter | 198 | | user_latitude | Latitude | X | | 199 | | user_longitude | Longitude | X | | 200 | | zoom | Integer | X | When < 15, bikes are clustered | 201 | 202 | **Example** 203 | 204 | ```bash 205 | curl --request GET \ 206 | --url 'https://web-production.lime.bike/api/rider/v1/views/map?ne_lat=52.6&ne_lng=13.5&sw_lat=52.4&sw_lng=13.3&user_latitude=52.5311&user_longitude=13.3849&zoom=16' \ 207 | --header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' \ 208 | --cookie '_limebike-web_session=N0xLYmE5ZytSSkRZa0FwQUdvYk1TalBaVWwzcnRDWUloT1Y1Z2ZNOVZSc0NCd3ZRZTFOVkxaS2lOcHFpemx6Y1pxT3ZudU1Zenk2ODlYRHBFZ1dxRWtaZGQybzRTQm96V09TWVdycENLcUltMHYzRWlUaEZlMDBqOCt4ODJqSWZwR09PSEtuNDdINnF3VGpkR3g2SjRBPT0tLTlJVHhSVFRDOE1CNm14S203VGxRd2c9PQ%253D%253D--9f55d56be64fefc5d5af3daf9e2fe9f7d7408cc0' 209 | ``` 210 | 211 | ``` 212 | { 213 | "data": { 214 | "id": "views::mapview", 215 | "type": "map_view", 216 | "attributes": { 217 | "regions": null, 218 | "zones": [ 219 | { 220 | "id": "LPAIXU6PU2GMR", 221 | "type": "zones", 222 | "attributes": { 223 | "name": "[Christchurch][SZ][CHCH_SERVICED_AREA]", 224 | "polyline": "nnvhG}b_|_@rV|iBdd@pOf_@gr@r}A}bC~F|L|GqIhB_BZaC~CyDRgEdCc@lQs\\xKjMht@euAxJqQ``@ku@VcD{@yi@kk@cqE_`AqdAcTm\\kPat@lFsW~FmgAqr@yh@~Fct@yCiqApt@ucAtA_t@a@mU~FmQ|JcLjLuJnNwGvWoBwD}OuHqJob@wIse@c@_bA_WbSgcBld@m\\zRkb@trA{qAwwAwbDgoBvaDinCvkAumJt}BrFlsGsIr`M_n@tyClo@nbDdfBlsAxtH`sB", 225 | "category": "service_zone", 226 | "icon_latitude": "-43.52882", 227 | "icon_longitude": "172.643155", 228 | "show_icon": false, 229 | "zone_styling_id": "116" 230 | } 231 | }, 232 | ... 233 | ], 234 | "bike_clusters": null, 235 | "bikes": [ 236 | { 237 | "id": "ET-O5RWUHLSWXWTV4FAHYPRUDIJUXXDWKBW4RUDFLQ", 238 | "type": "bikes", 239 | "attributes": { 240 | "status": "locked", 241 | "plate_number": "XXX-338", 242 | "latitude": -43.551564, 243 | "longitude": 172.624372, 244 | "battery_percentage": 27, 245 | "swappable_battery": false, 246 | "last_activity_at": "2021-02-04T04:36:11.000Z", 247 | "type_name": "scooter", 248 | "battery_level": "low", 249 | "meter_range": 1759, 250 | "rate_plan": "NZD $1 to unlock +\nNZD $0.38 / 1 min", 251 | "bike_icon_id": 48, 252 | "last_three": "338", 253 | "license_plate_number": null, 254 | "brand": "lime", 255 | "generation": "2.5" 256 | } 257 | }, 258 | ... 259 | ], 260 | "selected_bike": null, 261 | "bike_pins": [], 262 | "selected_bike_pin": null, 263 | "parking_spots": null, 264 | "icons": [ 265 | { 266 | "id": "50", 267 | "type": "icons", 268 | "attributes": { 269 | "url": "https://d22d5yy1i19g9i.cloudfront.net/icons/scooter_high_battery.png?fingerprint=a14ab0c75f4838b41729eb1ee746dae9", 270 | "description_icon_url": null, 271 | "description_link_url": null, 272 | "description": "translation missing: en.scooter_high_battery" 273 | } 274 | } 275 | } 276 | ``` 277 | 278 | ### Ring Vehicle 279 | 280 | If you run this command, it won't work. It will say the vehicle is too far away. Use the `/v1/views/map` path above and set the User Location to where the bike is. You need to also get the ET-ID from a bike, you can acquire it from the same endpoint that I just mentioned. Ring from anywhere! 281 | 282 | **Method**: `POST` 283 | 284 | **Path**: `/v1/bikes//ring` 285 | 286 | **Header**: 287 | 288 | | Header | Value | Mandatory | 289 | | ------------- | ------------ | :-------: | 290 | | Authorization | Bearer TOKEN | X | 291 | 292 | **Body**: 293 | 294 | `id=` 295 | 296 | If you get a 404 "Resource not found" message, it's most likely because the ET-ID provided is invalid. 297 | 298 | ### View Vehicle info banner 299 | 300 | The ET-ID can be found on the 'Get Vehicles and Zones' endpoint. 301 | 302 | **Method**: `GET` 303 | 304 | **Path**: `/v1/bikes//banner` 305 | 306 | **Header**: 307 | 308 | | Header | Value | Mandatory | 309 | | ------------- | ------------ | :-------: | 310 | | Authorization | Bearer TOKEN | X | 311 | 312 | **Response** 313 | 314 | ``` 315 | { 316 | "id": "ET-UM7V7RYMPDPCUYMTKOTCYMEAFPCJ7NN4NZ2T3CA", 317 | "title": "Available e-bike", 318 | "header": { 319 | "image_url": "https://limebike-web-public-assets.s3-us-west-1.amazonaws.com/image_files/JumpBike.png", 320 | "title": { 321 | "type": "text", 322 | "icon": null, 323 | "value": "Jump IIC262", 324 | "action": null 325 | }, 326 | "subtitle": { 327 | "type": "text", 328 | "icon": 65, 329 | "value": "72 km range", 330 | "action": null 331 | }, 332 | "action": { 333 | "icon": 67, 334 | "type": "ui_flow", 335 | "value": "expand", 336 | "text": null, 337 | "subtext": null 338 | }, 339 | "expansion": { 340 | "title": { 341 | "type": "text", 342 | "icon": null, 343 | "value": "Jump IIC262", 344 | "action": null 345 | }, 346 | "action": null 347 | } 348 | }, 349 | "items": [ 350 | { 351 | "type": "html", 352 | "icon": 389, 353 | "value": "Add payment
NZD $1 to start, then NZD $0.38/min", 354 | "action": { 355 | "icon": 391, 356 | "type": "deeplink", 357 | "value": "limebike://payment_methods", 358 | "text": null, 359 | "subtext": null 360 | } 361 | } 362 | ], 363 | "banner_action": { 364 | "icon": null, 365 | "type": "ui_flow", 366 | "value": "reserve", 367 | "text": "Reserve", 368 | "subtext": "Free for 10 minutes" 369 | }, 370 | "icons": [ 371 | { 372 | "id": 65, 373 | "url": "https://assets.lime.bike/icons/simplify_unlock/png/ic_battery3_40@3x.png?fingerprint=b3a6d67b61b38995d4d0ef2ff7ae3aca" 374 | }, 375 | { 376 | "id": 69, 377 | "url": "https://assets.lime.bike/icons/simplify_unlock/png/ic_time_40@3x.png?fingerprint=66542356edfa1d5a6b26551a4f2dc1df" 378 | }, 379 | { 380 | "id": 389, 381 | "url": "https://assets.lime.bike/icons/simplify_unlock/png/ic_add_payment_40%403x.png?fingerprint=aba1ad9584d90a6bf7118ea1ed0e7be6" 382 | }, 383 | { 384 | "id": 67, 385 | "url": "https://assets.lime.bike/icons/simplify_unlock/png/ic_carrot_down_40@3x.png?fingerprint=2458978c3bade75743b1c7c768b15f45" 386 | }, 387 | { 388 | "id": 296, 389 | "url": "https://assets.lime.bike/icons/simplify_unlock/png/ic_ring_40@3x.png?fingerprint=3d17ce060e2f3b5dd1afb5038c6ee94c" 390 | }, 391 | { 392 | "id": 391, 393 | "url": "https://assets.lime.bike/icons/simplify_unlock/png/ic_carrot_up_40@3x.png?fingerprint=19b7ee91068c326bda0c8216bf9b8b40" 394 | } 395 | ] 396 | } 397 | ``` 398 | 399 | If you get a 404 "Resource not found" message, it's most likely because the ET-ID provided is invalid. 400 | 401 | ### View your ride history 402 | 403 | **Method**: `GET` 404 | 405 | **Path**: `/v1/views/ride_history?page_limit=10` 406 | 407 | **Header**: 408 | 409 | | Header | Value | Mandatory | 410 | | ------------- | ------------ | :-------: | 411 | | Authorization | Bearer TOKEN | X | 412 | 413 | 414 | 415 | The Juicer API can be found here: https://github.com/davidwim/lime-juicer/ 416 | 417 | ## Implementations 418 | 419 | * https://www.npmjs.com/package/@multicycles/lime (JavaScript) 420 | -------------------------------------------------------------------------------- /Jump.md: -------------------------------------------------------------------------------- 1 | # JUMP (USA) (defunct) 2 | 3 | [JUMP](http://jumpbikes.com/) operates electric dockless bikeshares and scooter is various cities around the globe. 4 | To get bike and scooter data, you need to authenticate yourself. 5 | Unfortunately, this is a) rather tedious that requires 5 (yes, five) steps and b) you have to register a phone number using the Jump app. 6 | I assume, that you already have an account, as I don't know how to do this programmatically. 7 | 8 | ## UPDATE 9 | 10 | **JUMP Scooters are now found under the [Lime](Lime.md) API as `"brand": "jump"`** 11 | 12 | ## Getting an API token 13 | This task is quite heavy and requires 5 steps and parsing HTML and JavaScript source code. 14 | 15 | ### 0: Helper 16 | During the process, Uber sends some HTML and JavaScript files, which you need to parse. 17 | I wrote a little Python helper to do this. 18 | Give it the `index.html` file as a parameter, it will store all tokens required from the file in `tokens.txt`. 19 | Basically, this is a [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery)-[token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token) and a session ID. 20 | Both change after each step, therefore, you have to run the Python script after every step (as stated later). 21 | For simplicity, save the Python helper in a file called `parse.py`. 22 | 23 | ```Python 24 | #! /usr/bin/env python3 25 | 26 | import sys 27 | from html.parser import HTMLParser 28 | 29 | token_path = "tokens.txt" 30 | 31 | class MyHTMLParser(HTMLParser): 32 | def handle_starttag(self, tag, attrs): 33 | if tag != 'input': 34 | return 35 | 36 | if len(attrs) != 3: 37 | return 38 | 39 | if attrs[1][1] == 'sess': 40 | with open(token_path, 'a') as token_file: 41 | token_file.write(f"sess={attrs[2][1]}\n") 42 | 43 | if attrs[1][1] == 'x-csrf-token': 44 | with open(token_path, 'a') as token_file: 45 | token_file.write(f"csrf={attrs[2][1]}\n") 46 | 47 | if attrs[1][1] == 'inAuthSessionID': 48 | with open(token_path, 'a') as token_file: 49 | token_file.write(f"auth={attrs[2][1]}\n") 50 | 51 | 52 | tmp = open(token_path, 'w') 53 | tmp.close() 54 | 55 | parser = MyHTMLParser() 56 | with open(sys.argv[1], 'r') as html_file: 57 | parser.feed(html_file.read()) 58 | 59 | ``` 60 | 61 | ### 1: Request Authentication 62 | The first request will ask the Uber backend for authentication and start the process. 63 | This will also create a cookie jar file `cookie.jar`, which is required during the next steps. 64 | 65 | ```sh 66 | curl -X GET \ 67 | -H "Accept-Encoding: gzip, deflate" \ 68 | --cookie-jar "cookie.jar" \ 69 | --url "https://auth.uber.com/login/?uber_client_name=jump" \ 70 | | gzip -dc \ 71 | | tee index.html \ 72 | && python3 parse.py index.html 73 | ``` 74 | 75 | Again, you should have three files now available, `cookie.jar` with the cookie, `tokens.txt` with a CSRF token and a session ID and `index.html`, where this information is from. 76 | 77 | ### 2: Submitting Phone Number 78 | If you have a look in the `index.html` from the previous step, you will see, that Uber is requesting a phone number. 79 | This is what we are gonna do now. 80 | 81 | ```sh 82 | CSRF=$(grep csrf tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 83 | SESS=$(grep sess tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 84 | 85 | COUNTRY_CODE="49" # two digit ISO country code without + or 0 86 | PHONE="1234567" # Phone number without leading 0 or country code. 87 | 88 | curl -X POST \ 89 | -H "Accept-Encoding: gzip, deflate" \ 90 | --data "countryCode=$COUNTRY_CODE" \ 91 | --data "phoneNumber=$PHONE" \ 92 | --data "autoSMSVerificationSupported=false" \ 93 | --data "uberClientName=jump" \ 94 | --data "type=INPUT_MOBILE" \ 95 | --data "x-csrf-token=$CSRF" \ 96 | --data "sess=$SESS" \ 97 | -b "cookie.jar" \ 98 | --cookie-jar "cookie.jar" \ 99 | --url "https://auth.uber.com/login/session" \ 100 | | gzip -dc \ 101 | | tee index.html \ 102 | && python3 parse.py index.html 103 | ``` 104 | 105 | This will a) update the three files (`index.html`, `tokens.txt`, `cookie.jar`) and b) request a 4-digit 2FA code that is sent to your phone number and that you will need in the next step. 106 | 107 | ### 3: Confirm 2FA Code 108 | Now we want to sent the 2FA code to uber. 109 | 110 | ```sh 111 | CSRF=$(grep csrf tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 112 | SESS=$(grep sess tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 113 | AUTH=$(grep auth tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 114 | 115 | CODE="1234" # 4-digit 2FA code 116 | 117 | curl -X POST \ 118 | -H "Accept-Encoding: gzip, deflate" \ 119 | --data "type=SMS_OTP" \ 120 | --data "autoSMSVerificationSupported=false" \ 121 | --data "uberClientName=jump" \ 122 | --data "smsOTP=$CODE" \ 123 | --data "x-csrf-token=$CSRF" \ 124 | --data "sess=$SESS" \ 125 | --data "inAuthSessionID=$AUTH" \ 126 | -b "cookie.jar" \ 127 | --cookie-jar "cookie.jar" \ 128 | --url "https://auth.uber.com/login/session" \ 129 | | gzip -dc \ 130 | | tee index.html \ 131 | && python3 parse.py index.html 132 | ``` 133 | 134 | Alright, almost done. 135 | Again, the three files are updated again. 136 | 137 | ### 4: Password 138 | For this step, you have to provide your password. 139 | 140 | ```sh 141 | CSRF=$(grep csrf tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 142 | SESS=$(grep sess tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 143 | AUTH=$(grep auth tokens.txt | cut -d '=' -f2 | sed 's/ *$//') 144 | 145 | PW="YOUR_PW_HERE" 146 | 147 | curl -X POST \ 148 | -H "Accept-Encoding: gzip, deflate" \ 149 | --data "type=VERIFY_PASSWORD" \ 150 | --data "autoSMSVerificationSupported=false" \ 151 | --data "uberClientName=jump" \ 152 | --data "password=$PW" \ 153 | --data "x-csrf-token=$CSRF" \ 154 | --data "sess=$SESS" \ 155 | --data "inAuthSessionID=$AUTH" \ 156 | -b "cookie.jar" \ 157 | --url "https://auth.uber.com/login/session" \ 158 | | sed -nE 's/.*#code=(.*)&in_auth_session_id=.*/\1/p' 159 | ``` 160 | 161 | We are getting close. 162 | The last `sed` command will strip everything away except a UUID with the usual `123abcde-abcd-01234-abcd-123456789abc` format. 163 | You will need this for the last step. 164 | 165 | ### 5: Confirmation 166 | Basically, you will need the UUID from the previous step to confirm you login: 167 | 168 | ```sh 169 | UUID="123abcde-abcd-01234-abcd-123456789abc" 170 | 171 | curl -X POST \ 172 | -H "Accept-Encoding: gzip, deflate" \ 173 | -H "Connection: close" \ 174 | -H "Content-Type: application/json; charset=UTF-8" \ 175 | --data "{\"formContainerAnswer\":{\"inAuthSessionID\":\"$UUID\",\"formAnswer\":{\"flowType\":\"SIGN_IN\",\"screenAnswers\":[{\"screenType\":\"SESSION_VERIFICATION\",\"fieldAnswers\":[{\"fieldType\":\"SESSION_VERIFICATION_CODE\",\"sessionVerificationCode\":\"$UUID\"}]}]}}}" \ 176 | --url "https://cn-geo1.uber.com/rt/silk-screen/submit-form" \ 177 | | gzip -dc 178 | ``` 179 | 180 | Now, you will get a JSON response including various IDs and tokens. 181 | What you need is the `apiToken`, again, looking a regular UUID `abcde123-abcd-01234-abcd-123456789abc`. 182 | Congratulations. 183 | You did it. 184 | This `apiToken` is what you need to get scooters and bikes. 185 | 186 | ## Requesting vehicles 187 | To request vehicles, there is one known endpoint so far. 188 | 189 | ```sh 190 | API_TOKEN="abcde123-abcd-01234-abcd-123456789abc" # API Token from authentication 191 | 192 | curl -X POST \ 193 | -H "x-uber-token: $API_TOKEN" \ 194 | -H "Content-Type: application/json; charset=UTF-8" \ 195 | -H "Accept-Encoding: gzip, deflate" \ 196 | --data '{"latitude":52.528038680440716,"longitude":13.401972334831953,"radius":1000000}' \ 197 | --url "https://cn-geo1.uber.com/rt/emobility/search-assets" | gzip -dc 198 | ``` 199 | 200 | There are only three parameters: `latitude`, `longitude` of a center point and `radius` around this point. 201 | You can set the radius as you wish, but there seems to be a maximum at about 500 meters, if I see correctly. 202 | You will get a JSON response including all information about vehicles. 203 | 204 | 205 | # How to get the Information (a.k.a. Reverse Engineering an Android App) 206 | Other than for example Lime, Jump uses a series of reverse-engineering mitigations like certificate pinning. 207 | Therefore, I write this little guide to make the process easier in the future. 208 | 209 | ## Requirements 210 | * Android Device. The easiest would be to use an old device with Android 6 installed, since Man-in-the-Middle attacks are easier on older Android versions. Newer versions also work, but you will find how for yourself as I used a Nexus 5 with Android 6.0.1. Your device **must not be rooted**, as Jump (a.k.a. Uber) have a root detection. Android emulator also does not seem to work for some reason. You also have to [enable USB debugging on your device](https://upaae.com/how-to-enable-usb-debugging-in-android-6-0-1-marshmallow/). 211 | * A recent version of [Apktool](https://ibotpeaches.github.io/Apktool/), which is used to decompile and repackage the app. 212 | * [Split APK Installer (SAI)](https://github.com/Aefyr/SAI) on your device, which is used to a) download and b) reinstall the app from an to your device. 213 | * [Android SDK Platform Tools](https://developer.android.com/studio/command-line/adb) or at least `adb` and `zipalign`. 214 | * [Burp Suite Community Edition](https://portswigger.net/burp) for the actual MitM attack. You can use other tools, but again, as I used burp, the guide also relies on burp. 215 | * A [Google Cloud Platform](https://console.cloud.google.com) API key, registered for Android Map SDK. 216 | * I used version `2.39.10000` for this. Chances are, that things change over time. I will try to keep this guide up-to-date, but you know. Upkeep is always hard. 217 | 218 | ## Preparation 219 | ### Burp Suite Proxy Preparation 220 | First, we have to setup Burp Suite's proxy. 221 | Therefore, start Burp and change to the Proxy tab. 222 | First, turn off the HTTP intercept as we do not want to alter the traffic, but only capture it. 223 | Second, change to the Options tab under Proxy. 224 | Here, highlight the localhost proxy, click on Edit. 225 | Change the port to something more generic like `9999`, and chose your computers IP address in the Spcific Address drop down. 226 | Confirm with OK. 227 | Cool, Burp is now waiting for your traffic. 228 | 229 | ### Burp Suite Proxy on Android 230 | Of course we need to tell Android to reroute all traffic to the proxy. 231 | To do so, open the Settings on Android, go to Wi-Fi and long-press on the Wi-Fi network you are connected to. 232 | A dialog will pop up. 233 | Chose Edit Network. 234 | Scroll down and you will see the proxy settings (which are set to none). 235 | Change this to manual. 236 | Now you can add your computers IP and the proxy's port to the respective field. 237 | Apply these changes. 238 | Now, all HTTP traffic is proxy'd through Burp, but HTTPS not yet. 239 | 240 | To enable HTTPS, open a browser on the Android device and go to `http://burp`. 241 | In the top right corner, you can download Burp's certificate. 242 | Download it and rename it to `cert.cer`. 243 | Now, go to Security in the Android Settings. 244 | Here, you will find an option to install certificates from storage. 245 | Open this option and select the downloaded `cert.cer` (not `cert.der`!). 246 | Simply follow the instructions (you can choose what ever name you want). 247 | To confirm that everything works well, open a browser on Android and visit any HTTPS page. 248 | You should see all traffic in the HTTP history tab under the Proxy tab. 249 | 250 | ### Getting the Burp Certificate Fingerprint 251 | When we bypass the certificate pinning, we have to replace Uber's certificate fingerprint with our own. 252 | To do so, go to the Options tab under Proxy in Burp and export the CA certificate in DER format. 253 | You have to specifiy an absolute path, otherwise the certificate won't be saved. 254 | The next step is to convert the DEM certifcate to the PEM format. 255 | This is done with the OpenSSL CLI, which should be available on all Linux and macOS systems anyways. 256 | 257 | ```sh 258 | openssl x509 -inform der -in -out 259 | ``` 260 | 261 | After that, we want to gather the fingerprint. 262 | This requires some more steps. 263 | First, we want to get the public part from the certificate and extract the public RSA key. 264 | This will be hashed and then base64 encoded. 265 | Thanks to Unix' Pipes, this can be done in one command: 266 | 267 | ```sh 268 | openssl x509 -in -pubkey -noout \ 269 | | openssl rsa -pubin -outform der \ 270 | | openssl dgst -sha256 -binary \ 271 | | openssl enc -base64 272 | ``` 273 | 274 | There you have your base64 encoded certificate fingerprint. 275 | 276 | ### APK signing key 277 | Android requires APKs to be signed. 278 | Luckily, we can do this without any hasle ourself. 279 | Java and Android ship everythin required to do this. 280 | Let's create a signing key: 281 | 282 | ```sh 283 | keytool -genkey \ 284 | -v -keystore .keystore \ 285 | -alias \ 286 | -keyalg RSA \ 287 | -keysize 2048 \ 288 | -validity 10000 289 | ``` 290 | 291 | Please remember the `KEYNAME`, as we need it later on. 292 | You can chose what ever you want during the creation. 293 | During the last question you have to type `yes`, otherwise, the process will start again. 294 | 295 | This key will be used later to sign the APKs. 296 | 297 | ## Downloading the App 298 | First, open the Play Store on your device, search for Jump and download the app. 299 | After the download, do not open the app, but start SAI and export the Jump app. 300 | On your computer, run `adb pull `. 301 | This will download the APK from the device to your computer. 302 | As the Jump app is an app bundle, the file is a APKS rather than APK. 303 | The next step is to unpack the APKS simply by running `unzip `. 304 | 305 | ## Decompiling 306 | Now it's time to decompile the app. 307 | This requires multiple steps. 308 | First, decompile all APK files that are not called `base.apk` using `apktool`: 309 | 310 | ```sh 311 | apktool d -r 312 | ``` 313 | 314 | Run this command on all `split_config*` APK files. 315 | The `-r` option hinders `apktool` to decompile resources, which would cause errors during repackaging. 316 | 317 | Now comes the tricky part. 318 | We have to decompile the `base.apk` twice. 319 | First, we have to patch the `AndroidManifest.xml`. 320 | Since the `-r` option also hinders `apktool` to decompile the manifest, we have to do this in a separate step. 321 | Let's get started. 322 | Decompile the `base.apk` including all resource files and rename the resulting folder into something meaningful: 323 | 324 | ```sh 325 | apktool d base.apk -o base.complete/ 326 | ``` 327 | 328 | Now, decompile the `base.apk`, but leave the resource files intact: 329 | 330 | ```sh 331 | apktool d -r base.apk 332 | ``` 333 | 334 | ## Patching 335 | Now comes the actual patching. 336 | What we basically want to do is to bypass Uber's certificate pinning. 337 | Unfortunately, the Google Maps underlay stops to work since we inevitably alter the apps signature. 338 | Therefore, we also have to add our own Google Maps API key to the app. 339 | 340 | ### Google Maps API key 341 | This is what we do first. 342 | Open the `AndroidManifest.xml` in `base.complete/` folder and search for the `com.google.android.geo.API_KEY` string. 343 | Replace the `@string` value with your Google Maps API key. 344 | Additionally, in one of the permissions in the manifest, there is a wrong space in the value string, which has to be removed (somewhere around line 5). 345 | 346 | Now comes a tricky part. 347 | The easy way would be to move the `AndroidManifest.xml` from `base.complete/` to `base/`. 348 | But as we decompiled the `base/` folder with the `-r` flag, `apktool` will not compile the `AndroidManifest.xml` to the binary XML format required. 349 | Therefore, we first recompile `base.complete/`, decompile it again but leave the manifest untouched this time, which then can be used to replace the original file. 350 | Unfortunately, this is not that straight forward as it should be. 351 | First, try to recompile the `base.complete/` folder with 352 | 353 | ```sh 354 | apktool b base.complete/ -o base.tmp.apk 355 | ``` 356 | 357 | This will yield a number of errors denoting that multiple definitions of the same value exist in some resource files. 358 | Unfortunately, you have to fix these errors manually. 359 | Open the file with the error and remove duplicate entries, but leave at least one entry intact. 360 | Now, retry the compilation. 361 | You should now have a `base.tmp.apk` file. 362 | As we need the compiled `AndroidManifest.xml`, we have to decompile the `base.tmp.apk` again, but leave the manifest untouched: 363 | 364 | ```sh 365 | apktool d -r base.tmp.apk -o base.tmp/ 366 | ``` 367 | 368 | Now copy the `AndroidManifest.xml` from `base.tmp/` to `base/`. 369 | 370 | ### Certificate Pinning 371 | To bypass the certificate pinning, we need to find the fingerprint of Uber's certificates and replace them with our own. 372 | `grep` for the string `sha256/` in all `.smali` files. 373 | There should be one file with two base64 encoded SHA256 fingerprints. 374 | Replace both occurences with the base64 encoded SHA256 fingerprint of your certificate. 375 | Now, some lines below, you will see `*.uber.com`, which indicates, that the certificate should be valid for all Uber subdomains. 376 | Simply replace this with `*`, which will tell the certificate pinning implementation to accept all domains. 377 | Finally, replace the date string with the `valid until` field of your certificate. 378 | 379 | Thats it for the patching part. 380 | Let' recompile everything and install the app. 381 | 382 | ## Build and Deploy 383 | ### Build 384 | To recompile the APKs, we just use `apktool` again: 385 | 386 | ```sh 387 | apktool b -f base/ -o base.unaligned.apk 388 | apktool b -f split_config<1>/ -o split_config<1>.unaligned.apk 389 | apktool b -f split_config<2>/ -o split_config<2>.unaligned.apk 390 | ... 391 | ``` 392 | 393 | Note, that you have to use the `-f` flag, because `apktool` would ignore our patched `AndroidManifest.xml`. 394 | As you may notice, the APKs have this `unaligned` in their names. 395 | This will be used later, as we have to align the files. 396 | But first, let's sign the APKs. 397 | 398 | This is obviously done with the signing key and `jarsigner`: 399 | 400 | ```sh 401 | jarsigner -verbose \ 402 | -sigalg MD5withRSA \ 403 | -digestalg SHA1 \ 404 | -keystore \ 405 | -storepass \ 406 | \ 407 | 408 | ``` 409 | 410 | Repeat this process for all the compiled APKs. 411 | 412 | The final step before deployment is now to align the APKs with `zipalign`: 413 | 414 | ```sh 415 | zipalign -v 4 416 | ``` 417 | 418 | Again, repeat this process for all unaligned but signed APKs. 419 | 420 | ### Deploy 421 | Now, use `adb` and push all aligned and signed APKs to your device: 422 | 423 | ```sh 424 | adb push ... /sdcard 425 | ``` 426 | 427 | Now, open the SAI app on your device, go to settings and enable APK signing (the installed app bundle has to signed again, apparently). 428 | Then, go back to the apps home screen and tap on install APKs. 429 | Go the the location, where you pushed the APKs to, and select **all at once** and then tap install. 430 | This will take a while. 431 | After some minutes, you will be asked, if you want to install Jump. 432 | Again, after some minutes, the installation should finish without any further notice. 433 | 434 | ## Final thoughts 435 | Congrats. 436 | You successfully bypassed Uber's anti-sniffing technique. 437 | Unfortunately, the Jump app will only work when the Burp Proxy is enabled, as it now strictly requires Burp's certificate. 438 | Now you can go to Burp and in the Proxy tab, you should see all traffic, including Uber's and Jump's traffic. 439 | 440 | Unfortunately, this is a rather tedious task. 441 | If someone finds an easier or faster way to achieve this, I would be very happy. 442 | --------------------------------------------------------------------------------