Client guide
This is a guide for clients on how to sell shared mobility through their own channels with the shared-mobility API.
Before you start your implementation
Good to know: assetId (TOMP id for a product), vehicleId and bike_id (both GBFS id for a product) is the same id.
Agreement with the transport operator(s) you want to sell products for
- The client must have permission to sell products on behalf of a transport operator
- The client must send the necessary documentation for settlement to Entur
Using the Mobility API from Entur to show available products in a map
To find and show available shared mobility products, use the Mobility API.
Sales Flow
After setting up integration with Entur's Mobility API, you can now use the Shared Mobility API to book products.
You can find the swagger doc for the shared-mobility application here: https://petstore.swagger.io/?url=https://api.dev.entur.io/api-docs/shared-mobility-to-ref.
Note that the endpoints under the "TOMP endpoints" section should only be used by the transport operators to inform us about changes in the journey from their side. These must not be used by the clients.
Booking a product can be done in two ways:
- Selecting a product from the map: By choosing a product from the map, you can use the GBFS data from the Mobility API to display information about the product (typically price, battery status, location and the distance the unit can travel), check if you have the right to sell the product, and use the assetId to create a booking.
- Scanning a QR code: If you want to add functionality for scanning a QR code and then displaying product information, you first need to make a call to "GET /shared-mobility/v1/qr/qr-code" to retrieve the assetId/vehicleId/bike_id from the scanned QR-code. After that, the sales flow continues as usual.
Payment
This section describes the payment flow for shared mobility bookings.
Recurring payment
Before a booking can be made, a recurring payment agreement must be in place. See the Add Recurring Payment guide for more information. This will create a recurring payment, which will be used to pay for the bookings made by the user. Send the recurring payment id when creating a booking.
Deposit
All bookings require a deposit to be made. The deposit amount is set to 10.00,- NOK and is used to ensure that the user has a valid payment method.
Finalize payment
When the booking is finished (booking state FINISHING/FINISH), the final amount will be calculated and the deposit will be deducted from the final amount. The user will be charged the final amount asynchronous when the booking state changes to FINISHING/FINISH.
Failed payments
If the payment fails, this is the current error handling:
- We try to charge the user 3 times with 10 seconds interval between each attempt.
- If the payment still fails, we will try to charge the user every midnight up to 3 days.
- If the payment still fails, we will try to charge the user for unpaid trip the next time the user tries to make a new booking. A new trip will not be possible until the payment is successful. We use the recurring payment used to create a new booking to charge the user for the unpaid trip.
Authentication
The client should authenticate with the shared-mobility API by providing an OAuth2 bearer token in the request header. The token should be issued by Entur and have the necessary scopes to access the API. In addition, a header for a specific distribution channel must be provided in the request when booking a trip with shared mobility. Only distribution channels that have been registered with Entur and have been linked with your client will be accepted.
In order to issue a valid token you must have a client_id and client_secret.
curl--request POST \
--url 'https://<type_of_token>.<environment>.entur.org/oauth/token' \
--header 'content-type: application/json' \
--data '{"grant_type":"client_credentials","client_id": "<clientid>","client_secret": "<clientsecret>","audience": "https://api.<environment>.entur.io"}
Headers
These headers should be used inn all calls towards the shared-mobility API:
Description | Header key | Example value |
---|---|---|
Distribution Channel | Entur-Distribution-Channel | ENT:DistributionChannel:app |
Authorization | Authorization | Bearer xxxxxxx.yyyyyyyy.zzzzzzzzz |
The preferred language code for error messages etc. | Accept-Language | NOB |
Check if you can sell the product chosen from the map or scanned by a QR-code
GET /shared-mobility/v1/operators/availability
Returns a list of transport operators you can sell products on behalf of. If a vehicle's operator id (vehicle.system.operator.id in the Mobility API) is included in this list, you can use the Shared Mobility service to book it.
Get vehicleId (GBFS) given QR-code
GET /shared-mobility/v1/qr/qr-code
Retrive the assetId (TOMP) / vehicleId (GBFS) /bike_id (GBFS) given the scanned QR-code
Create a booking
POST /shared-mobility/v1/bookings/one-stop
Create a one stop booking. If you want to make sure that the trip starts right away you can set autoStart to true.
Change the state of the booking
POST /shared-mobility/v1/bookings/{bookingId}/one-stop/event
Use this endpoint to change the state of a booking:
- START: Start the trip
- PAUSE: Pause the trip
- RESUME: Resume the trip after pause
- START_FINISHING: State usually used when the transport operator want the user to take a picture of the mobility etc. The traveler stops paying more for the journey after this step in the journey.
- FINISH: Finish the trip (if a picture of the product is mandatory when finishing the trip, you have to set fileName, fileType and fileData)
- CANCEL: Cancel the trip
Get bookings given customer ID
GET /shared-mobility/v1/bookings
Get a list of all bookings given customerId. If you set the parameter "active" to true, the endpoint will return all bookings who is not in FINISHED or CANCELLED state. This is usually used when retrieving an active booking if the user closes the app in the middle of a trip.
Get a booking given booking ID
GET /shared-mobility/v1/bookings/{bookingId}
Get a specific booking given bookingId.
Error handling
All error objects look the same and follow this structure:
The message is provided as human-readable text, translated into Norwegian Bokmål, Norwegian Nynorsk, or English. The language is determined by the 'Accept-Language' header in the request.
Here is a list of all error messages:
When | What | Exception | Response code | Response text (Accept-Language: Human readable text) |
---|---|---|---|---|
Scan QR-code | Find info about operator and vehicle given QR-code | CouldNotFindAssetException | 422 | NOB: Kunne ikke finne kjøretøy. NNO: Kunne ikkje finne køyretøy. ENG: Could not find asset. |
Create booking, start trip, finish trip | Check if transport operator support chosen service | EndpointUnavailableException | 422 | NOB: Operatøren tilbyr ikke denne tjenesten. NNO: Operatøren tilbyr ikkje denne tenesta ENG: Operator does not provide this service |
Create booking | Get info about transport operator | UnknownOperatorException | 400 | NOB: Operatør av kjøretøy ukjent. NNO: Operatør av køyretøy ukjent. ENG: Operator of vehicle unknown. |
Create booking | Check access | InsufficientAccessException | 403 | NOB: Din leverandør har ikke tilgang til dette kjøretøyet. NNO: Leverandøren din har ikkje tilgang til dette køyretøyet. ENG: Your provider does not have access to this vehicle. |
Create booking | Check recurring payment agreement | PaymentAgreementMissingException | 402 | NOB: Kunne ikke starte tur. Vi fant ingen gyldig betalingsmetode. Prøv igjen med et annet kort. NNO: Kunne ikkje starte tur. Vi fann ingen gyldig betalingsmetode. Prøv igjen med eit anna kort. ENG: Could not start trip. We could not find a valid payment method. Try again with a different card. |
Create booking | Create one-stop booking with a transport operator | TompOneStopPostException | 500 | NOB: Kunne ikke starte tur. Forsøket ble avslått av {operator}. {406:message} NNO: Kunne ikkje starte tur. Forsøket vart avslått av {operator}. {406: Message} ENG: Could not start trip. The attempt was rejected by {operator}. {406: Message} |
Create booking | Create order | CouldNotStartTripException | 500 | NOB: Kunne ikke starte tur. Prøv igjen eller velg et annet kjøretøy. NNO: Kunne ikkje starte tur. Prøv igjen eller vel eit anna køyretøy. ENG: Could not start trip. Try again or choose another vehicle. |
Create booking | Add a third party product | CouldNotStartTripException | 500 | NOB: Kunne ikke starte tur. Prøv igjen eller velg et annet kjøretøy. NNO: Kunne ikkje starte tur. Prøv igjen eller vel eit anna køyretøy. ENG: Could not start trip. Try again or choose another vehicle. |
Create booking | Create payment | CouldNotStartTripException | 500 | NOB: Kunne ikke starte tur. Prøv igjen eller velg et annet kjøretøy. NNO: Kunne ikkje starte tur. Prøv igjen eller vel eit anna køyretøy. ENG: Could not start trip. Try again or choose another vehicle. |
Create booking | Add transaction | CouldNotStartTripException | 500 | NOB: Kunne ikke starte tur. Prøv igjen eller velg et annet kjøretøy. NNO: Kunne ikkje starte tur. Prøv igjen eller vel eit anna køyretøy. ENG: Could not start trip. Try again or choose another vehicle. |
Create booking | Create terminal | CouldNotStartTripException | 500 | NOB: Kunne ikke starte tur. Prøv igjen eller velg et annet kjøretøy. NNO: Kunne ikkje starte tur. Prøv igjen eller vel eit anna køyretøy. ENG: Could not start trip. Try again or choose another vehicle. |
Create booking | Reserve payment amount | CouldNotReserveAmountException | 402 | NOB: Kunne ikke starte tur. Vi klarte ikke å reservere beløpet. NNO: Kunne ikkje starte tur. Vi klarte ikkje å reservere beløpet ENG: Could not start trip. We could not reserve the amount. |
Create booking | Active booking already exists | ActiveBookingAlreadyExistsException | 409 | NOB: Du har allerede en aktiv tur. Du kan bare ha én aktiv tur om gangen. NNO: Du har allereie ein aktiv tur. Du kan berre ha éin aktiv tur om gongen. ENG: You already have an active trip. You can only have one active trip at a time. |
Start trip | Change state of trip | TompCreateOneStopBookingEventException | 500 | NOB: Kunne ikke endre tilstanden på turen. Leverandøren avslo forsøket. Prøv igjen eller velg et annet kjøretøy. NNO: Kunne ikkje endre tilstanden på turen. Leverandøren avslo forsøket. Prøv igjen eller vel eit anna køyretøy. ENG: Could not change state of trip. The provider refused the attempt. Try again or choose another vehicle. |
Finish trip | Change state of trip | TompCreateOneStopBookingEventException | 500 | NOB: Kunne ikke avslutte tur. Leverandøren avslo forsøket. Prøv igjen eller ta kontakt med {operator} NNO: Kunne ikkje avslutte tur. Leverandøren avslo forsøket. Prøv igjen eller ta kontakt med {operator}. ENG: Could not end trip. The provider declined the attempt. Try again or contact {operator}. |
Finish trip | Unlock order | CouldNotEndTripException | 500 | NOB: Kunne ikke avslutte tur. Prøv igjen eller ta kontakt med {operator}. NNO: Kunne ikkje avslutte tur. Prøv igjen eller ta kontakt med {operator}. ENG: Could not end trip. Try again or contact {operator}. |
Finish trip | Update order | CouldNotEndTripException | 500 | NOB: Kunne ikke avslutte tur. Prøv igjen eller ta kontakt med {operator}. NNO: Kunne ikkje avslutte tur. Prøv igjen eller ta kontakt med {operator}. ENG: Could not end trip. Try again or contact {operator}. |
Finish trip | Adjust payment amount | CouldNotEndTripException | 500 | NOB: Kunne ikke avslutte tur. Prøv igjen eller ta kontakt med {operator}. NNO: Kunne ikkje avslutte tur. Prøv igjen eller ta kontakt med {operator}. ENG: Could not end trip. Try again or contact {operator}. |
Finish trip | Illegal image type | IllegalImageTypeException | 400 | NOB: Ulovlig format på bildet. NNO: Ulovleg format på bildet. ENG: Illegal image type. |
Change state of trip | Illegal event | IllegalEventException | 400 | NOB: Ulovlig hendelse i gjeldende tilstand. NNO: Ulovleg hending i gjeldande tilstand. ENG: Illegal event in current state. |
Kafka Queue: Purpose and Usage
Introduction
This document is intended for clients, particularly those offering mobile applications for ticket sales, who will consume messages from our Kafka queue. The aim is to provide an overview of the queue's purpose, its functional role, and the type of data transmitted through it. This documentation focuses on the functional aspects, explaining the value and context of the queue. Technical details, such as integration steps, authentication, and protocols, will be covered separately.
Purpose of the Kafka Queue
The Kafka queue is a distributed messaging system designed to ensure real-time data exchange between different systems and clients. Its primary purpose is to reliably and efficiently distribute critical updates and event data, enabling client applications to provide accurate and up-to-date information to their end users.
For clients developing ticket-selling apps, the Kafka queue is essential for delivering real-time updates such as battery status, current ticket prices, and other dynamic information directly to users. This allows applications to enhance their user experience by offering timely and actionable insights, improving customer satisfaction and operational efficiency.
Type of Data Transmitted
Structured messages in the form of Avro-serialized objects are sent through the Kafka queue. These messages are self-descriptive and contain essential details regarding events or updates.
Primary Object Type: SharedMobilityKafkaEvent
The main object type transmitted through the queue is the SharedMobilityKafkaEvent
, which encapsulates real-time updates related to mobility services and bookings.
The key fields include:
Timestamp:
- The event's creation time, represented as a
long
in microseconds since January 1, 1970 (UTC). This ensures precision and allows chronological processing of events.
- The event's creation time, represented as a
Booking Updates:
- Details about a specific booking, including:
- Booking ID: A unique identifier for the booking.
- State: The current status of the booking, such as "IN_USE," "FINISHED," or "CANCELLED."
- Pricing Details: Information about current and final ticket prices.
- Operator Information: The mobility service provider associated with the booking.
- Battery Status: Current charge percentage of a vehicle or device.
- Details about a specific booking, including:
Dynamic Pricing and Plans:
- Pricing plans include currency, base price, and per-minute pricing details for transparency in cost estimation.
Metadata and Additional Information:
- Fields like "departureTime," "arrivalTime," "stateOfCharge," and "comments" provide contextual details.
Why Use the Kafka Queue?
1. Real-Time Information for Users
The Kafka queue enables ticket-selling apps to access up-to-date information about bookings and services, allowing them to deliver real-time updates to end users. For example:
- Notifying users of changes in ticket prices.
- Displaying real-time battery status for rented mobility devices.
2. Decentralized Processing
Clients can independently consume and process data from the Kafka queue. This decentralized approach allows each application to focus on its specific needs without relying on centralized processing.
3. Reliability and Scalability
Kafka is designed for high throughput and reliability, ensuring that messages are delivered without loss even during high traffic. This is critical for clients who need consistent data streams for accurate service delivery.
4. Standardized Data
By using Avro serialization, all messages follow a standardized schema. This minimizes compatibility issues and simplifies integration for client applications.
Example Use Case: Real-Time Ticket Updates
A ticket-selling app consuming the Kafka queue can:
- Notify a user if the cost of their ticket changes.
- Provide up-to-date battery status for a shared mobility vehicle, helping users decide whether it's suitable for their journey.
Example Data Structure
Below is an example of a SharedMobilityKafkaEvent
message:
{
"timestamp": 1726748821000,
"bookingUpdatedKafkaEvent": {
"schemaVersion": "1.0",
"bookingId": "123e4567-e89b-12d3-a456-426614174000",
"state": "IN_USE",
"orderId": "ORD123456",
"pricingPlan": {
"currency": "NOK",
"price": 150.0,
"perMinPricing": [
{
"start": 0,
"end": 60,
"interval": 15,
"rate": 2.5
}
]
},
"departureTime": 1726748821000,
"arrivalTime": 1726750800000,
"operator": {
"id": "op123",
"name": "SharedMobility Operator"
},
"stateOfCharge": 80,
"currentRangeKm": 120,
"pricing": {
"currentAmount": 100.0,
"finalAmount": 150.0
},
"comment": "Real-time booking update for the user."
}
}