Client guide

This is a guide for clients on how to sell shared mobility through their own channels with the shared-mobility API.

  1. Before you start your implementation
    1. Agreement with the transport operator(s)
    2. Using the Mobility API from Entur to show available products in a map
  2. Sales flow
    1. Payment
      1. Recurring payment
      2. Deposit
      3. Finalize payment
      4. Failed payments
    2. Authentication
    3. Headers
    4. Get vehicleId (GBFS) given QR-code
    5. Create a booking
    6. Change the state of the booking
    7. Get bookings given customer ID
    8. Get a booking given booking ID
    9. Error handling
  3. Kafka Queue: Purpose and Usage
    1. Introduction
    2. Purpose of the Kafka Queue
    3. Type of Data Transmitted
    4. Why Use the Kafka Queue?
    5. Example Use Case: Real-Time Ticket Updates
    6. Example Data Structure

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:

  1. We try to charge the user 3 times with 10 seconds interval between each attempt.
  2. If the payment still fails, we will try to charge the user every midnight up to 3 days.
  3. 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:

DescriptionHeader keyExample value
Distribution ChannelEntur-Distribution-ChannelENT:DistributionChannel:app
AuthorizationAuthorizationBearer xxxxxxx.yyyyyyyy.zzzzzzzzz
The preferred language code for error messages etc.Accept-LanguageNOB

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:

{
  "timestamp": "2024-09-17T08:49:33.814422231+02:00",
  "status": 409,
  "error": "Conflict",
  "exception": "VehicleNotFoundException",
  "message": "Could not start trip. This vehicle is not available right now.",
  "path": "/bookings/one-stop"
}

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:

WhenWhatExceptionResponse codeResponse text (Accept-Language: Human readable text)
Scan QR-codeFind info about operator and vehicle given QR-codeCouldNotFindAssetException422NOB: Kunne ikke finne kjøretøy.

NNO: Kunne ikkje finne køyretøy.

ENG: Could not find asset.
Create booking, start trip, finish tripCheck if transport operator support chosen serviceEndpointUnavailableException422NOB: Operatøren tilbyr ikke denne tjenesten.

NNO: Operatøren tilbyr ikkje denne tenesta

ENG: Operator does not provide this service
Create bookingGet info about transport operatorUnknownOperatorException400NOB: Operatør av kjøretøy ukjent.

NNO: Operatør av køyretøy ukjent.

ENG: Operator of vehicle unknown.
Create bookingCheck accessInsufficientAccessException403NOB: 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 bookingCheck recurring payment agreementPaymentAgreementMissingException402NOB: 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 bookingCreate one-stop booking with a transport operatorTompOneStopPostException500NOB: 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 bookingCreate orderCouldNotStartTripException500NOB: 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 bookingAdd a third party productCouldNotStartTripException500NOB: 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 bookingCreate paymentCouldNotStartTripException500NOB: 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 bookingAdd transactionCouldNotStartTripException500NOB: 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 bookingCreate terminalCouldNotStartTripException500NOB: 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 bookingReserve payment amountCouldNotReserveAmountException402NOB: 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 bookingActive booking already existsActiveBookingAlreadyExistsException409NOB: 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 tripChange state of tripTompCreateOneStopBookingEventException500NOB: 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 tripChange state of tripTompCreateOneStopBookingEventException500NOB: 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 tripUnlock orderCouldNotEndTripException500NOB: 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 tripUpdate orderCouldNotEndTripException500NOB: 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 tripAdjust payment amountCouldNotEndTripException500NOB: 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 tripIllegal image typeIllegalImageTypeException400NOB: Ulovlig format på bildet.

NNO: Ulovleg format på bildet.

ENG: Illegal image type.
Change state of tripIllegal eventIllegalEventException400NOB: 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:

  1. 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.
  2. 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.
  3. Dynamic Pricing and Plans:

    • Pricing plans include currency, base price, and per-minute pricing details for transparency in cost estimation.
  4. 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."
  }
}