Entur's developer portal has moved to developer.entur.no. Use the new portal for the latest documentation. This old version will be retired once its content has moved, but API changes are still published here for now, under News and Notifications.
Recommendations Guide
When searching for public transport tickets, the API returns numerous offers for the same journey, varying by flexibility, fare class, accommodation, and other properties. Recommendations group these offers into categories and select the best offer in each category. For journeys with multiple legs, recommendations can also combine offers across legs to cover the entire journey.
The API does not guarantee recommendations for every requested category. A category may be empty if, for example, the product does not exist for that departure (e.g., sleeper is only available on night trains), capacity is sold out, or your organisation is not authorised to sell a particular operator's products.
Any search endpoint that takes geographical input supports recommendations (e.g., POST /trip-patterns, POST /stop-places, POST /zones). Add a recommendationConfig to your search request to receive recommendations organised by the requested criteria.
To get started, add a recommendationConfig to your search request. Without this config, the API returns offers but no recommendations.
This returns the cheapest offer for the entire journey. All other settings use defaults.
Production configuration
For production use, enable the recommended flags. This configuration covers most use cases.
These are the recommended flag values for most integrations. See CategorySpec and RuleSpec for details on each field, and Examples for behaviour illustrations.
If you sell long-distance train tickets, also add fareClasses and facilitySet to the categorySpec to get recommendations split by fare class (e.g., standard vs. premium) and accommodation type (e.g., sleeper, couchette). See CategorySpec for available values.
Response structure
The API response contains two related arrays:
offers[] — All offer objects with full details (price, conditions, validity)
recommendations[] — Categories pointing to offers via offersToBuy
In this example, the recommendation selects offer-1 as the cheapest option in the FLEXIBLE + STANDARD_CLASS category. Both offers cover the same journey with the same flexibility, but differ in price.
Key fields in a recommendation:
typeOfRecommendation — The flexibility category (CHEAPEST, NON_FLEXIBLE, etc.)
geographicalValidityCovered — Which part of the journey this covers:
coversEntireJourney — Boolean indicating if this recommendation covers the full journey
serviceJourneys — List of service journey IDs covered, only applicable when requesting offers for a specific TripPattern
pointToPointValidity — Start and end stop places, for tickets valid between specific stops
zonalValidity — Zone information, for tickets valid in specific zones
offersToBuy — The offers to reserve in the Sales API, with quantity and possible traveller assignments.
selectableProductIds — When populated, include these selectable IDs in the reservation request. They refer to optional products (found in the response under optionalProducts) that are available with the recommended offers.
possibleTravellerIds — Who can be assigned to each purchase. See Traveller Assignment.
combineWithToCoverEntireJourney — When this recommendation doesn't cover the entire journey, other recommendations are provided that can be combined with this one to cover the full journey. Select one item from the list, then purchase all OffersToBuy from all items in recommendationsToCoverEntireJourney, together with the parent recommendation. Only populated when onlyIncludeMostCompleteInCategorySpec: true. More details can be found in Partial journey coverage.
To display a recommendation, look up each offersToBuy[].id in the offers[] array to get price and details.
The ruleSpec controls how recommendations are calculated and filtered.
Setting
Default
Recommended
Description
journeyOrganizeAlgorithm
SUBSEQUENT_COMBINATIONS
SUBSEQUENT_COMBINATIONS
How multi-leg journeys are split into recommendation groups.
priceComparisonAlgorithm
BEFORE_SDR
TOTAL_PRICE
How offers are compared when selecting the best offer.
onlyIncludeRecommendedOffers
false
true
Only return offers referenced by at least one recommendation.
onlyIncludeRecommendationsWithOffersToBuy
true
true
Omit recommendation categories with no matching offers. When true, every returned recommendation is guaranteed to have a non-empty offersToBuy.
mixinOffersWithHigherFlexibility
false
true
Allow higher-flexibility offers in lower categories if cheaper or the requested flexibility is unavailable on some legs. At least one offer in the recommendation must match the requested level. See example.
mixInOffersWithOtherFacilitySets
false
true
Allow mixing facility types to cover the full journey. For example, a SLEEPER recommendation may include a SEATING offer for legs where sleeper isn't available. At least one offer must match the requested facility. See example.
removeSplitRailReplacementOffers
false
true
When a rail ServiceJourney has been partially replaced by alternative transport, recommendations will only be provided for the full ServiceJourney.
onlyKeepUpgradeProductSalesPackages
false
false
Only include upgrade products. Use when client has an entitlement (e.g., monthly pass) and searches for upgrades.
onlyIncludeMostCompleteInCategorySpec
false
false
BETA: Filter out subset recommendations. See details below.
Why do defaults differ from recommended values?
Several flags default to false because they were introduced incrementally — enabling new behaviour requires an explicit opt-in to avoid breaking existing integrations. In a future major version of the API, the recommended values will become the new defaults, and some flags may be removed entirely.
Algorithm Details
Journey Organize Algorithms
Recommendation
Use SUBSEQUENT_COMBINATIONS — it enables full-journey recommendations for multi-operator journeys where no single offer covers all legs.
The algorithm controlling how recommendations should be combined spanning multiple legs.
Example: For a journey with three legs (SJ1 → SJ2 → SJ3):
The choice of algorithm matters most on multi-operator journeys. Usually, there exists no tickets covering all operators, hence no single offer covers the full journey.
Consider a two-leg journey: A → B with Operator X, then B → C with Operator Y.
With COMBINATIONS_FROM_OFFERS, the API only creates groups matching existing offers — one per leg, but no full-journey group:
The client receives two separate CHEAPEST recommendations but must figure out on its own that buying both covers the full journey. With onlyIncludeMostCompleteInCategorySpec (BETA), each recommendation gets combineWithToCoverEntireJourney links pointing to the other — but the client still does the composition.
With SUBSEQUENT_COMBINATIONS, the API also creates a group for the full journey and fills it with the cheapest combination of offers:
The third recommendation has coversEntireJourney: true and lists both offers — the client gets a ready-made full-journey recommendation without combining offers itself.
Key behaviours:
SUBSEQUENT_COMBINATIONS only creates consecutive leg groups — non-adjacent combinations like [SJ1, SJ3] are never generated
FOR_EACH_AND_GROUPED_COMBINATIONS may miss optimal price combinations (no intermediate groups like [SJ1, SJ2])
COMBINATIONS_FROM_OFFERS only creates groups matching actual product scopes — no full-journey group is created unless a single product covers it. This is useful when your client handles journey composition itself. Combined with onlyIncludeMostCompleteInCategorySpec (BETA), partial recommendations get combineWithToCoverEntireJourney links that help clients find complementary offers
Price Comparison Algorithms
Algorithm
Compares
Use case
TOTAL_PRICE
What the user actually pays (after discounts)
Recommended for most use cases
BEFORE_SDR
Original price before discounts
Employee tickets (personalbillett)
For both algorithms, the price comparison is done per traveller.
BEFORE_SDR is useful for employee tickets where all offers cost the same after discount. It selects the cheapest original product for a lower tax basis.
Example: A customer with an employee ticket searches Oslo → Bergen.
Offer
Original price
After discount
Offer A
kr 200
kr 0
Offer B
kr 100
kr 0
TOTAL_PRICE: Both are kr 0 — tie, so selection is arbitrary
This feature may change or be removed in future versions.
When onlyIncludeMostCompleteInCategorySpec: true, the API filters out recommendations that are strict subsets of other recommendations within the same category specification.
For a multi-leg journey with recommendations for [A], [B], and [A, B] (all with identical typeOfRecommendation, durationType, fareClass, and facilitySet), only the recommendation for [A, B] is returned.
Key behaviours:
Only applies to multi-leg journeys; single-leg journeys are unaffected
Subset filtering only considers recommendations with offersToBuy
Recommendations without offers are preserved when onlyIncludeRecommendationsWithOffersToBuy: false
Enables combineWithToCoverEntireJourney on incomplete recommendations to help clients find complementary offers (see Partial journey coverage)
When a field is omitted from categorySpec, it acts as an implicit wildcard — recommendations will include all offers regardless of that attribute. You can also use explicit wildcard values (ANY, ANY_FACILITY_SET) to include a wildcard alongside specific values.
For example, requesting fareClasses: ["STANDARD_CLASS", "ANY"] generates separate recommendations for STANDARD_CLASS offers and for the cheapest across all fare classes. typesOfRecommendation is the only required field and must contain at least one value.
These two requests are functionally equivalent — omitting a field has the same effect as specifying its wildcard value:
Flexibility Levels
Level
Refundable
Exchangeable
Description
CHEAPEST
—
—
Lowest price regardless of flexibility
NON_FLEXIBLE
No
No
Cannot be refunded or exchanged
SEMI_FLEXIBLE
Either
Either
Either refundable or exchangeable, but not both
FLEXIBLE
Yes
Yes
Both refundable and exchangeable
Mixing flexibility levels
When mixinOffersWithHigherFlexibility: true, a lower-flexibility category may include offers with higher flexibility if they are cheaper or the requested flexibility is unavailable. At least one offer must match the requested level. See RuleSpec for details and Examples for an illustration.
The possibleTravellerIds field in offersToBuy tells you who to assign to each purchase. It is a list of lists — each inner list represents one purchase, and the number of inner lists always equals numberToBuy.
Individual tickets — each inner list contains one traveller:
Buy offer-a twice: once for traveller-1, once for traveller-2.
Group tickets — the inner list contains all travellers sharing one ticket (e.g. a sleeper compartment):
Buy offer-b once for all four travellers together. The underlying product may have per-user-type constraints (e.g. minimum 1 adult, up to 3 children) — the recommendation system has already solved the assignment for you.
Mixed — the system may combine individual and group tickets when that is cheapest:
Summary:
possibleTravellerIds
numberToBuy
Meaning
[["A"], ["B"]]
2
Two individual purchases
[["A", "B"]]
1
One group purchase
[["A"], ["B", "C", "D"]]
2
One individual + one group
You do not need to inspect the offer's travellerMapping to use the recommendation — possibleTravellerIds is the ready-made grouping.
Each categorySpec dimension multiplies the number of recommendation slots. The API generates one recommendation per unique combination and filters out empty slots (when onlyIncludeRecommendationsWithOffersToBuy: true). Note that the same offer can appear in multiple recommendations — for example, the cheapest NON_FLEXIBLE offer may also be the overall CHEAPEST.
This creates 4 × 4 = 16 possible slots. Only 13 have matching offers:
typeOfRecommendation
facilitySet
Offer recommended
CHEAPEST
ANY_FACILITY_SET
NonFlexibleSeating
CHEAPEST
SEATING
NonFlexibleSeating
CHEAPEST
COUCHETTE
NonFlexibleCouchette
CHEAPEST
SLEEPER
SemiFlexibleSleeper
NON_FLEXIBLE
ANY_FACILITY_SET
NonFlexibleSeating
NON_FLEXIBLE
SEATING
NonFlexibleSeating
NON_FLEXIBLE
COUCHETTE
NonFlexibleCouchette
SEMI_FLEXIBLE
ANY_FACILITY_SET
SemiFlexibleSeating
SEMI_FLEXIBLE
SEATING
SemiFlexibleSeating
SEMI_FLEXIBLE
SLEEPER
SemiFlexibleSleeper
FLEXIBLE
ANY_FACILITY_SET
FlexibleCouchette
FLEXIBLE
COUCHETTE
FlexibleCouchette
FLEXIBLE
SLEEPER
FlexibleSleeper
Three combinations have no matching offers and are omitted: NON_FLEXIBLE + SLEEPER, SEMI_FLEXIBLE + COUCHETTE, and FLEXIBLE + SEATING. With onlyIncludeRecommendationsWithOffersToBuy: false, these would appear as empty recommendations.
Adding a third dimension (e.g., durationTypes: [SINGLE_TRIP, WEEKLY_PASS, MONTHLY_PASS]) would multiply the slots further: 4 × 4 × 3 = 48 possible slots, with only the non-empty ones returned.
Scenario: User searches Oslo → Bodø with typesOfRecommendation: ["NON_FLEXIBLE"], but a FLEXIBLE offer is cheaper than the NON_FLEXIBLE alternative on one leg.
Offer
Flexibility
Price
Leg
Offer A
NON_FLEXIBLE
kr 200
Oslo → Trondheim
Offer B
FLEXIBLE
kr 150
Oslo → Trondheim
Offer C
NON_FLEXIBLE
kr 300
Trondheim → Bodø
Result: NON_FLEXIBLE uses Offer A (kr 200) + Offer C (kr 300) = kr 500. Only NON_FLEXIBLE offers are considered.
Result: NON_FLEXIBLE uses the cheaper FLEXIBLE Offer B (kr 150) for leg 1 + NON_FLEXIBLE Offer C (kr 300) for leg 2 = kr 450. The user saves kr 50 and gets higher flexibility on one leg.
Higher-flexibility offers can replace lower on individual legs, but not vice versa
At least one offer in offersToBuy must match the requested flexibility level — if no NON_FLEXIBLE offers exist for any leg, no NON_FLEXIBLE recommendation is created. This prevents recommendations labelled NON_FLEXIBLE that contain only flexible offers
The CHEAPEST category is not affected by this flag
The API returns one recommendation per duration type, each pointing to the cheapest offer of that duration. This lets clients present options like "Single trip: kr 39", "Day pass: kr 109", "Monthly pass: kr 790" side by side.
You can combine durationTypes with other categorySpec dimensions. For example, adding typesOfRecommendation: [CHEAPEST, NON_FLEXIBLE, FLEXIBLE] generates up to 3 × 3 = 9 recommendations (one per flexibility level per duration type). Empty combinations (where no matching offers exist) are filtered out when onlyIncludeRecommendationsWithOffersToBuy: true.
Rail replacement filtering
Scenario: Service journey A is partially replaced by bus (BFT A), followed by another rail leg B.
Leg
Type
Note
A
Rail
Remaining rail portion of original SJ
BFT A
Rail replacement
Bus replacing part of A
B
Rail
Separate service journey
With removeSplitRailReplacementOffers: false:
Recommendations for: [A], [BFT A], [B], [A, BFT A], [BFT A, B], [A, BFT A, B]
Both [BFT A] and [BFT A, B] are removed because they contain the rail-replacement leg without its corresponding rail leg (A).
Key behaviour: When a service journey is partially replaced by bus (i.e., both rail and rail-replacement portions exist), any recommendation group that contains a rail-replacement leg without its corresponding rail leg is removed. If the entire service journey is replaced by bus, no filtering occurs.
Partial journey coverage
Requires
onlyIncludeMostCompleteInCategorySpec: true in ruleSpec (BETA feature)
When a recommendation doesn't cover the entire journey (coversEntireJourney: false), the combineWithToCoverEntireJourney field lists other recommendations that can be combined to cover the full journey.
Scenario: A two-leg journey with two operators. No single ticket covers both legs.
How to use:
Check if coversEntireJourney is false
If so, look at combineWithToCoverEntireJourney for complementary recommendations
Select one item from the list — each item covers a specific geographical gap
Purchase offers from all recommendations in the selected item's recommendationsToCoverEntireJourney, together with the parent recommendation, to cover the full journey
Check if mixinOffersWithHigherFlexibility is enabled. This may place FLEXIBLE offers in NON_FLEXIBLE categories
Check if mixInOffersWithOtherFacilitySets is enabled. This may mix SEATING into SLEEPER categories
Verify priceComparisonAlgorithm—BEFORE_SDR may select different offers than TOTAL_PRICE
Missing flexibility levels
Not all products support all flexibility options. If no FLEXIBLE offers exist for a route, no FLEXIBLE recommendation is returned.
NON_FLEXIBLE recommendation contains a mix of flexible and non-flexible offers
This happens when mixinOffersWithHigherFlexibility: true and a flexible offer is cheaper on one or more legs. This is expected behaviour; the system found a better deal by using a higher-flexibility offer where it's cheaper, while keeping at least one non-flexible offer in the recommendation. See example.
Large response size
Enable onlyIncludeRecommendedOffers: true to exclude offers not part of any recommendation.