Customers

Customers API

Customers API can be used to access Entur and Entur partner organisation’s customers. The features included in the API are: Customer profiles, Customer authentication, Temporary profiles, Consents and Loyalty programs connected to customers. These consepts are explained in the guides.

The Customers API handles GDPR sensitive data. Read more about how it is handled in the guides.

The API use REST principles and all request have to use SSL. All request and response bodies are encoded in JSON

Environments

The API has three environments, dev, staging and production. Dev and staging consists of dummy data, which can be used for testing. 

Authentication

All endpoints in the Customers API requires an authentication header. The authentication header must be an Entur-issued OAuth2 bearer token. If you have access to a client Id and secret, you can use the curl example to retrieve a token. A client Id and secret can be obtained by ….

curl --request POST \
  --url 'https://partner.entur.org/oauth/token' \
  --header 'content-type: application/json' \
  --data '{"grant_type":"client_credentials","client_id": "<clientid>","client_secret": "<clientsecret>","audience": "https://api.staging.en

Get started

When you have an authentication token, you are ready to get started.

Let’s find customer profile for email in staging:

curl -X GET "https://api.entur.io/profiles?email=<email> -H "accept: application/json;charset=UTF-8" -H "Authorization: Bearer <token>"

If you are able to find the customer you are looking for, you are ready to go.

Swagger:

 

 

Use case examples

Note, all endpoints in the customers api requires access tokens.

Look up a customer

curl --request GET \
  -- url 'https://api.entur.io/customers/v2/profiles/<customerIdentifier>' \
  -- header 'Authorization: Bearer <token>'

Create a new customer

curl --request POST \
  --url 'https://api.entur.io/customers/v2/profiles/' \   
  --header 'Authroization: Bearer <token>' \
  --data '{"firstName": "Ola", "surname": "Nordmann", "email": "ola@nordmann.org", "languagePreference": "NO", "createdBy": "test", "createdByChannel": "test", "organisationId": 1, "password":"abcD123"}'

Search for customers

Search will return the profiles that match all search parameters. To see all available search parameters, see the api documentation

curl --request GET \
  -- url = 'https://api.entur.io/customers/v2/profiles?email=<EMAIL>&surname=<NAME>' \
  -- header 'Authorization: Bearer <token>'

 

Consepts within customers

This guide describes basic consepts within the customers/vX/profiles api.

Customers

A customer is a person or legal entity who has created a profile within the Customers api to gain advantages and track journeys within the Entur sales system. Each profile is connected to a specific organisation, and no organisation can gain profile data connected to a different organisation (i.e. NSB cannot view information about Entur profiles).

A profile is uniquely identified with the combination of customerRef and organisationId. This references is used throughout the Entur sales system to connect a profile to its purchases, consents, loyalty programs, contact with customer support, etc.

Temporary profiles

Temporary profiles are used with a subsection of the customers api; /profiles/temporary. In addition, temporary profiles can be included in customer search (/profiles/customers/includeTemporary=true). All domains are allowed to create and manage temporary profiles.

A temporary profiles is used to contain customer information that is necessary for a specific journey and/or ticket, when the customer does not wish to register a permanent profile. A temporary profile must have an expiration date; this is typically the end of the journey the profile is connected to. All provided parameters, including expiration date, are updateable. After the profile is expired, all customer information is deleted and is not recoverable.
Temporary profiles have restricted usage within the Customers api. A temporary profile can have consents and postal addresses, but not ecardspreferences or loyalty programs
A temporary profile can be converted to a permanent profile if done before the profile expires.

GDPR

Customer data

The customer and the organisation the profile is connected to can at any time request to known the data registered about them. At the moment, this includes the customer profile, consents and loyalty programs. This information is accessible through /me/:customerRef

Deletion

Deletion of profiles is available through the customers api; /profiles/:customerRef/delete. The profile will be deleted within 72 hours. Entur has functionality to automatically send an email and/or SMS to inform the customer they will be deleted, and to inform them how to reverse the deletion.

Loyalty programs

A loyalty program is the connection between a customer and a product that gives this customer an entitlement. Examples of such entitlements are Customer cards and Personnel tickets.

For a given customer, the client can ask to find out which products are available to be used in the purchasing process. The client should then allow the user to select which loyalty programs they wish to use for which travellers. The result of this selection is then added to the call to offers, which then applies the products to the result.

 

Authentication and authorization

Access controls

All of the customer endpoints are restricted by Auth0. This means that all requests must have an Authorization: Bearer <JWT-token> header.

This header is aquired from auth0, using service-to-service authentication. This will generate an access token with your organisation identifier:

curl --request POST \
  --url 'https://partner.entur.org/oauth/token' \   
  --header 'content-type: application/json' \
  --data '{"grant_type":"client_credentials","client_id": "yourClientCredentials","client_secret": "yourClientSecret","audience": "https://api.entur.io"}'

Access restrictions

We expect communication via a service, not directly from a front-end application. To this end, we've set the CORS restrictions so you can't connect directly to the API from a browser (except from these pages).

The Customers APIs all check that the current client has the necessary rights before allowing access to customer data. In practice, this means that a client with a Partner-JWT-token for an organisation will only ever have access to customers in that organisation. If you try to access a customer you're not entitled to see, you will get a 404 response, as if the customer didn't exist.

 

API Versioning

Deprecation

The current version of the Customers API (at time of writing, v2) supports the features of the previous version (with a caveat, see below).

Methods and endpoints marked as deprecated in this version will be removed in the next version and should not be used in new code.

Migration from customers-v1 to customers-v2:

While we have strived to maintain backwards compatibility, the handling of consents have changed quite a bit, primarily due to GDPR. To make sure this is noted by the clients, we've moved the legacy consent model from /customers/v1/consents to /customers/v2/consents/v1 .

We recommend using the customers/v2/consents API going forward, as this is actively being developed.

Errors

We use standard HTTP response codes for success and failure. In general, 200 HTTP codes correspond to success, 40X codes are for developer- or user-related failures, and 50X codes are for internal API issues. The customer API has two different error responsens dependent on the URI used. We are working on returning the same response for all our endpoints, but this will not happen before next version because we need to keep the API backward compatible.

Errors in profiles and consents

You can expect this error for URI starting with:
/profiles
/consents

{ 
     "errorCode": 0,
     "shortNorwegian": "string",
     "longNorwegian": "string", 
     "shortEnglish": "string", 
     "longEnglish": "string" 
}

The errorCode are custom API error codes explained her:

400 Invalid API usage, Resource exists
401 Invalid credentials
403 Forbidden
404 Invalid URI
405 Resource not found
406 Failed to save resoruce
410 Resource deprecated
500 Server error
501 SQL error
603 Invalid email
605 Invalid telephone number
610 Unauthorized user
611 UnAuthenticated

 

Errors for the rest of the API 

The other endpoint will be returning errors in the following format. As mentioned, this is the format we will use going forward with the API(new major versions).

{ 
    "timestamp": "string" //When the error occured
     "status" : "string", //The http status code
     "error": "string", // The http status reason
     "path": "string", // The request URI
     "message": "string",//The error message 
     "errors": "list" //Optional list of error specifications 
}

Consents

The Consent API allows you to create, delete, update and manage all your consents. Get started quickly by using our predefined consent bases or create your own custom consents.

With the consent API you can:

  • Manage all your customers consents

  • Use pre-defined consents without worrying about GDPR

  • Create custom consents

  • Integrate with your CRM

You can find the complete reference to the Consents API here: https://developer.entur.org/consents/apis

Data model

To be able to register a consent there several things that you need to be aware of. The consent service contains three models:

  • Consent Base: The consent base contains the legal information of a consent. A consent base can be shared across multiple organisation because the terms can be generic and reusable. Furthermore the consent base has a field called “consentOwnerOrgId”. If set, the organisation which owns the consent, and can use it exclusively. Keep this field blank for the consents that are shared between multiple organisations.
  • Consent: While the consent base holds information about what the consent entails, consent provides the link to each organisation and the type of communication that is supported. We say that we instantiate a consent base with a consent.
  • Given Consent: A given consent is the connection between the customer and what consent the customer consented to.

Quickstart:

Useful keywords:

  • ConsentOwnerOrgId describes which partner that owns the consent base. In the case that the value is null (empty), it is available for all partners.
  • OrganisationId describes the partner that instantiated the consent base into a consent.
  • CreatedByOrgId describes the partner that obtained the givenConsent from the customer.
  • CustomerRef (customer reference) is either the unique identifier of a customer in Entur's services, or the unique identifier of a partners customer that is not stored in Entur's services.
  • ConsentCode is both used as a unique identifier of a consent base and used among administrators to quickly know what kind of consent it is without having to read the terms and description.

Step 1: Find a Consent Base that fit your requirements. To list all current Consent Bases:

curl -X GET \
  https://api.entur.io/customers/v2/consents/admin/consent-base

For the sake of this guide, we will use the Consent Base: “Line Deviations”. Users that consent to this allows organisations to send line specific deviation info.
If you are unable to find a fitting consent base, you can always create a new one. This is done via a POST-request to the same endpoint.

Step 2: Create a consent associated to your organisation and the consent base. To connect your organisation with the consent base:

curl -X POST \
  https://api.entur.io/customers/v2/consents/admin/consent \
  -d '{
	"consentCode": "LINE_DEVIATIONS",
	"organisationId" : {your_org_id},
	"isEmailSupported" : true,
	"isSmsSupported" : true,
	"isActive" : true
}'

Step 3: Collect consents from customers:

curl -X POST \
  https://api.entur.io/customers/v2/consents/given-consent \
  -d '{
	"customerRef" : "{your_customer_reference}",
	"consentId" : {your_consent_id},
	"consentChoice" : true,
	"email" : "test.testesen@entur.org",
	"telephoneNumber" : "+4700000000",
	"expirationDate" : "2029-11-01T12:00:00+0100"
}'

Important

Warning! Because of GDPR you are not allowed to change a consent without informing the customers. Therefore, if you do a PUT on a consent base, there will be created a new version of the consent base, all consents will then become invalid and you will have to obtain new consents from your users.

But don’t worry. The given consent (the decision the customer have made) won’t be deleted unless the user has consented to the new version. When the customer has registered a new given consent and the consent is valid, the previous given consent will be deleted automatically.

Loyalty programs

How to use the API when interacting with the offers interface

You can fetch the contracts currently available for a customer via the contracts search. Note that, depending on the rights of your logged in user, the organisationId and customerRef parameters may have no effect.

Example output:

curl https://api.staging.entur.io/customers/v2/benefits/contracts\?organisationId\=20\&customerRef\=3517949  \ 
 -H'Content-Type:application/json' -H"Authorization: Bearer $TOKEN" | jq . 

{
  "items": [
    {
      "uuid": "e2cb03b5-edd6-4560-804b-28bf1ddeb04a",
      "organisationId": 20,
      "acceptanceDate": "2018-11-16T11:00:18Z",
      "consumableFrom": "2018-11-16T00:00:00Z",
      "expirationDate": "2019-11-16T00:00:00Z",
      "loyaltyProgram": {
        "id": 1,
        "organisationId": 20,
        "internalDescription": "NSB Kundekort",
        "productId": "NSB:SaleDiscountRight:CustomerCard",
        "productVersion": "XXX:Version:V1",
        "startDate": "2012-01-01T00:00:00Z",
        "endDate": "2028-01-01T00:00:00Z",
        "usageValidityPeriod": "PT8760H",
        "versionNumber": 1,
        "status": "CURRENT",
        "descriptions": []
      },
      "createdAt": "2018-11-16T10:59:08Z",
      "createdBy": "kruyter",
      "lastChangedAt": "2018-11-16T11:00:19Z",
      "lastChangedBy": "kruyter",
      "orderLineEvents": [
        {
          "orderId": "101430516",
          "orderLineId": "30512446",
          "orderLineVersion": "1",
          "price": "750.00",
          "organisationId": 20,
          "timestamp": "2018-11-16T11:00:19Z",
          "isPurchased": true,
          "isCancelled": false
        }
      ],
      "policies": [],
      "contractConsumers": [
        {
          "customerOrganisationId": 20,
          "customerRef": "3517949",
          "isBlocked": false,
          "isContractHolder": true,
          "createdAt": "2018-11-16T10:59:08Z",
          "createdBy": "kruyter",
          "lastChangedAt": "2018-11-16T11:00:19Z",
          "lastChangedBy": "kruyter"
        }
      ]
    }
  ],
  "totalItems": 1,
  "totalPages": 1
}
 

From the output, you can pick out the items[].loyaltyProgram.productId for all available loyaltyprograms, and offer them up for the end user to choose which to apply, and to which travellers.

Now, to use those in a sale, add the relevant products to the relevant travellers in the call to the offers API:

 

Body when calling the offers endpoint with rights applied:

{
  "trip": {
    "fromPlace": {...},
    "toPlace": {...},
    "dateTime": "...",
    "tripPatterns": [...]
  },
  "travelers": [
    {
      "id": "1",
      "products": [
        {
          "ref": "NSB:SaleDiscountRight:CustomerCard"
        }
      ],
      "age": 40
    },
    {
      "id": "2",
      "products": [
        {
          "ref": "NSB:SaleDiscountRight:CustomerCard"
        }
      ],
      "age": 6
    }
  ]
}

You may also send in an optional product version. If it isn't present, the latest product version is used:

Example with product version:
{
  "trip": {
    "fromPlace": {...},
    "toPlace": {...},
    "dateTime": "...",
    "tripPatterns": [...]
  },
  "travelers": [
    {
      "id": "1",
      "products": [
        {
          "ref": "NSB:SaleDiscountRight:CustomerCard",
          "version": "XXX:Version:V1"
        }
      ],
      "age": 40
    },
    {
      "id": "2",
      "products": [
        {
          "ref": "NSB:SaleDiscountRight:CustomerCard",
          "version": "XXX:Version:V1"
        }
      ],
      "age": 6
    }
  ]
}

 

 

 

How to stay up-to-date with the Customers and Consents services

Problem

You have a local service you need to keep synchronized with the Entur services.

Solution

Synchronization from your service to Entur services is handled via the API. You can update directly on the customer or the given consent.

Synchronization from Entur services to your systems happens via topic subscription.

Entur exposes several different topics related to customers:

Environment Customer changes Given consent changes
Staging customer-changed-staging-$Org given-consent-changed-staging-$Org
Production customer-changed-production-$Org given-consent-changed-production-$Org

 

 

 

($Org is replaced with your assigned organisation Id, so the topic becomes for instance given-consent-changed-staging-35)

The elements you fetch from these queues are slightly different:

 
 GivenConsentChanged
 
 CustomerChange

The change objects are as small and GDPR friendly as we can make them. Use the customerNumbers to look up in the API after receiving them.

An example client for consuming these events:

        val properties = Properties()
        properties[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = "https://kafka-0.entur.io:9094,https://kafka-1.entur.io:9094,https://kafka-2.entur.io:9094"
        properties[ConsumerConfig.GROUP_ID_CONFIG] = "teamculp-test-1"
        properties[ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG] = false
        properties[ConsumerConfig.AUTO_OFFSET_RESET_CONFIG] = "earliest"
        properties[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        properties[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = KafkaAvroDeserializer::class.java
        properties["specific.avro.reader"] = true
        properties["schema.registry.url"] = "http://schema-registry.entur.io:8001"

        properties[CommonClientConfigs.SECURITY_PROTOCOL_CONFIG] = "SASL_SSL"

        properties[SaslConfigs.SASL_MECHANISM] = "SCRAM-SHA-512"
        properties[SaslConfigs.SASL_JAAS_CONFIG] = String.format("org.apache.kafka.common.security.scram.ScramLoginModule required\nusername=\"%s\"\npassword=\"%s\";", "username", "password")

        properties[SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG] = "...../local.truststore.jks"
        properties[SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG] = "changeit"

        val consumer: Consumer<String, CustomerChange> = KafkaConsumer(properties)

        consumer.subscribe(asList("customer-changed-staging-...."))
        val records = consumer.poll(Duration.of(10, ChronoUnit.SECONDS))

        log.info("records fetched: {}", records.count())

        if (!records.isEmpty)
            for (record in records.records("customer-changed-staging-....")) {
                log.info("record={}", record)
            }

 

Note:

  • for the customer services, we are using StringDeserializer for the keys.
  • the local.truststore.jks needs to be primed with a CA certificate so you'll trust our kafka server that you'll get when you receive the username and password. Likewise with Urls.
  • GROUP_ID_CONFIG must be the same on all clients in a cluster. It should be set explicitly. Also, use another group Id when testing.
The API reference(s) can be found here: