
Quick Start Guide for SJ Reseller API
This guide gives enough technical information for a newcomer to connect to the Reseller API and perform some basic calls. It is not meant to be a complete coverage of the whole API. For that, see the technical reference.
The guide also focuses on the practical steps to get going with the technical stuff and does not cover business rules or indeed any information on how to interpret the responses you get.
Authentication
Whitelisting
Each API client needs to be added to the Reseller API whitelist to be able to call the API.
OAuth 2.0
The authorization type is OAuth 2.0 with a basic auth header. The client authentication method is Client Credentials, using a client id and a client secret.
How to retrieve an access token
Settings received from SJ:
Access token URL
Scope
Client ID
Client Secret
Step-by-step:
- Create the request body: “grant_type=client_credentials&scope=[scope]
- Call POST Access token URL with the above Authorization header and request body
- Extract the access token from the access_token property in the response body
Request:
POST login.microsoftonline.com/e73eebe9-3079-4f3a-90cb-fbadbcd3879b/oauth2/v2.0/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=api%3A%2F%2F1a8e379d-fe64-4fa4-969e-1ebc0fe338fb%2F.default
Response body:
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Inp4ZWcyV09OcFRrd041R21lWWN1VGR0QzZKMCJ9.eyJhdWQiOiIxYThlMzc5ZC1mZTY0LTRmYTQtOTY5ZS0xZWJjMGZlMzM4ZmIiLCJpc3MiOiJodHRwczovL2xvZ2luLm1JeMjrE3X1TkDFSW0891oQnbB7UoN2FxWYtwF8l3XvhKxMlfCEhzYWpdUnyATS4qS7uy-tfgVZdgq737CU1APXhqP4mRPhocBCoyMHsMPg3y3fhuz6-PnBoayx47qSY-Achuoh99i62Hxybcm6_gU5JJCxLLdTjvc3tOQM1YVR3WmjKlgfjUaQ4qLaXy4S-I09llSp95YmYw4YTHgTZR8FNlfRJXRW3amsy-SOxXggWqrEdozrz224EVOwpcjflr3vGfJAhfnsVXiS5Vg58UKzeBhXHaozWtr2f7hkclgsF8RYJC1_aUlhazEDUmH-5qw"
} curl -X POST "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
-d "client_id=$CLIENT_ID" \
-d "grant_type=$GRANT_TYPE" \
-d "client_secret=$CLIENT_SECRET" \
-d "scope=$SCOPE" \
Response
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": <JWT_TOKEN>
} Requestor header
Each sales channel connected to the Reseller API must be manually set up with its organizational attributes in SJ’s internal system to gain access to the API.
Each call must contain a requestor header, identifying the caller:
{
“salesApplication”: “[my sales application code]”,
“salesUnitCode”: “[my sales unit code]”,
“organizationCode”: “[not used by the Reseller API]”
} SalesApplication is the term used within OSDM to represent the sales client that the users are using to purchase journeys.
The Salesapplication only gives you info about what application is used when buying journeys, not who is selling the ticket. Who is selling the ticket you get from the Sales Unit, which represents which specific selling party that sold the journey. In Swedish it is called Säljställe, and could be represented by a physical store, an office, or a virtual sales unit.
The requestor header JSON object should be stringified and converted to Base64 before being sent to the Reseller API.
Example
{
"salesApplication": "MYSALESAPPLICATION"
"salesUnitCode": "XYZ000001",
"organizationCode": "",
}eyAKICAic2FsZXNBcHBsaWNhdGlvbiI6ICJMSU5LT05MSU5FIiAKICAic2FsZXNVbml0Q29kZSI6ICJTVDAwMDA5OCIsICAKICAib3JnYW5pemF0aW9uQ29kZSI6ICJOb3RVc2VkIiwgIAp9IA== Checklist before the first API call
- Send your IP address to SJ
- Ensure that SJ has added your IP address to the Reseller API whitelist.
- Send you organizational information to SJ
- Ensure that SJ has added your organization to its internal systems.
- Get authentication information from SJ: access token URL, scope, client id, client secret
- Get the base URL for the different Reseller API environments from SJ.
First API call
Step-by-step:
- Retrieve an access token (see above)
- Create the authorization header: “Authorization”: “Bearer [access_token]”
- Create the requestor header: “Requestor”: “[Requestor header converted to a Base64 string (see above)]”
- Create the language header: “Accept-language”: “[sv-SE or en-GB]
- Create a request body for a POST /Offer: Below is an example with SEK and swedish stations codes. For international users, please exchange SEK to EUR and x_swe stations codes to uic station codes.
- Call POST [base URL]/offers with the authorization header, the requestor header, the accept-language header, and the request body. The response should contain a list of offers
{
"tripSpecifications": [
{
"externalRef": "myTripRef",
"legs": [
{
"externalRef": "myLegRef",
"timedLeg": {
"start": {
"stopPlaceRef": {
"objectType": "StopPlaceRef",
"stopPlaceRef": "urn:x_swe:stn:740000001"
},
"serviceDeparture": { "timetabledTime": "[date one month from now]T06:33:00+01:00" }
},
"end": {
"stopPlaceRef": {
"objectType": "StopPlaceRef",
"stopPlaceRef": "urn:x_swe:stn:740000002"
},
"serviceArrival": { "timetabledTime": "[date one month from now] T09:35:00+01:00" }
},
"service": {
"productCategory": {
"name": "Snabbtåg",
"shortName": "ST",
"productCategoryRef": "urn:swe:sbc:ST"
},
"publishedServiceName": "421",
"vehicleNumbers": [ "421" ],
"carriers": [
{
"ref": "urn:x_swe:carrier:74",
"name": "SJ"
}
]
}
}
}
],
"isPartOfInternationalTrip": false
}
],
"offerSearchCriteria": { "currency": "SEK" },
"anonymousPassengerSpecifications": [
{
"externalRef": "passenger-1",
"Age": 35,
"type": "PERSON"
}
],
"embed": [ "ALL" ]
}{
"offers": [
{
"offerId": "AAAAAAAAAAAAAAAqfSA-ju8uyeZcBt2_t4-kwb6I_D7rBfH89Jh-7ZTidA8i6iix_d8tVuB6bz4klIGCEzi0hCBp4nMdHZ0pfhqeVm4GDXGAkAgoeYeJV9O_YmWgxMX_2zopvggmg0qdcquULDuZ29G2MmIZ0EZ7pc-C6PQ6P3z7ErgYhMnOumJjcYIYM0rEIBqdaUK1zSNvX64UCDLCmaB6SXVYeLVXdTrj-tKSwptnARIzGYtQ54-C6aU5WU3x3GMr9Dn4H6sfnzLXgKva7rEyAPEyndBQGPZITMEWH_x4pb15CdaQ==",
"offerSummary": {
"minimalPrice": {
"currency": "SEK",
"amount": 7000,
"scale": 2
},
"overallServiceClass": {
"type": "STANDARD",
"name": "STANDARD"
},
"overallTravelClass": "SECOND",
"overallAccommodationType": "SEAT",
"overallAccommodationSubType": "ANY_SEAT"
POST myUrl.se/strict/reseller/v3/offers HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Inp4ZWcyV09OcFRrd041R21lWWN1VGR0QzZKMCJ9.eyJhdWQiOiIzODg5MWU5ZS1jN2M5LTQ4OTEtYTM4Yy00MjViZDE1N2NkN2MiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vMjE0NmM0YWUtNjQ3MS00MGE4LWI4MjEtMjc3NGM4MDU3OTYzL3YyLjAiLCJpYXQiOjE3MzQwNzYxNDIsIm5iZiI6MTczNDA3NjE0MiwiZXdf4545yIsInV0aSI6ImVBZkUtNk52VlVpdENjM2FvYm9LQUEiLCJ2ZXIiOiIyLjAifQ.dN_pt-DOgmPnHCFEfKN64mL41AZvsh5gpzNtLFK8GJrDTnpIskYPWyx__VJ2A6WgVJkIMHOHN7H9FarqKhwfC4Qtzq9GhBOKhqzjlgX7eB6B2K0T6je7wbNUpvIKsKTEiiiYp478rscv6FGlMKrCC55nFRKEW6LPrTURWndlYZ2v2wP7WpdRBtjdBNUE3-BFvzI5oBO-r1oZZU7HxkBA_9kpT15I7q7ANwufmy1HsumIWVF4CNkM79vF2AeVsBiSw4flfXU2OVpcb
Requestor: eyJvcmdhbml6YXRpb25Db2RlIjogIlNhbSIsICJzYWxlc1VuaXRDb2RlIjogIlNUMDAwMDk4IiwgInNhbGVzQXBwbGljYXRpb24iOiAi
accept-language: sv-SE
Content-Type: application/json
Accept: */*
Host: myUrl.se
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 2764Standard API use case – book a journey
Below are examples with SEK and swedish stations codes. For international users, please exchange SEK to EUR and x_swe stations codes to uic station codes.
1. Call POST /Offers to get offers for a certain trip
{
{
"objectType": "OfferCollectionRequest",
//List specifying which journeys/trips you want offers for. Each returned offer will be referring one of these trips.
//Note that there is no timetable search in R-API, you have to get this trip information separately, for example through an regular offline timetable sync between SJ and your system.
"tripSpecifications": [
{
//A reference to this trip in the OSDM client’s system. Can be anything. "externalRef": "trip-8142482b-6b04-48bd-937e-78a73c949628",
//A list of the legs/segments that constitutes this trip
"legs": [
{
//A reference to this leg/segment in the OSDM client’s system. Can be anything.
"externalRef": "leg-b5a19494-b50d-40de-b24f-ae9887c0fc6e",
"timedLeg": {
"start": {
"stopPlaceRef": {
"objectType": "StopPlaceRef",
//Prefix urn:x_swe:stn: for the nine-digit swedish station codes.
"stopPlaceRef": "urn:x_swe:stn:740000001"
},
"serviceDeparture": {
"timetabledTime": "2024-06-11T08:14:00+02:00" //Don’t forget the proper UTC-offset
}
},
"end": {
"stopPlaceRef": {
"objectType": "StopPlaceRef",
"stopPlaceRef": "urn:x_swe:stn:740000002"
},
"serviceArrival": {
"timetabledTime": "2024-06-11T13:03:00+02:00"
}
},
"service": {
//The public Train number, announced on platforms etc
"publishedServiceName": "165",
"vehicleNumbers": [
165
],
//product type/Brand - agreed upon within Sweden // Snabbtåg/ST, Regional/RE, InterCity/IC, Nattåg/NT, Euronight/EN
"productCategory": {
"name": "Regional",
"shortName": "Regional",
"productCategoryRef": "urn:x_swe:sbc:RE"
},
"carriers": [
{
//Swedish carrier code 74 for SJ
"ref": "urn:x_swe:carrier:74",
"name": "SJ"
}
]
}
}
}
],
//true if the trip crosses an international border
"isPartOfInternationalTrip": false
}
],
//limit the resulst to only contain a certain flexibility or class. Exclude flexibilities and travelClass completely to get offers of all types. "offerSearchCriteria": {
//Allowed values: NON_FLEXIBLE, FULL_FLEXIBLE
"flexibilities": [
"NON_FLEXIBLE"
],
"travelClasses": [
"SECOND"
],
"currency": "SEK"
},
//The minimum amount of passenger information needed to get the correct price – discount cards, age, etc. No identifying information here. "anonymousPassengerSpecifications": [
{
//Referens till en passagerare i OSDM-klientens system.
"externalRef": "passenger-1",
"Age": 35,
"type": "PERSON", //Always set to PERSON. To specify other passenger types, use a combination of age and reduction cards.
"cards": [
// //include this card if the passenger is a student
// {
// "type": "REDUCTION_CARD",
// "code": "SWE_STUDENT"
// },
// //include this card if the passenger is a pensioner
// {
// "type": "REDUCTION_CARD",
// "code": "SWE_PENSIONER"
// },
// //include this card for rebook/refund during a traffic disturbance
// {
// "type": "REDUCTION_CARD",
// "code": "SWE_TDT"
// },
// //Prio card(note that the prio level is not needed)
// {
// "type": "LOYALTY_CARD",
// "code": "SJ_PRIO",
// "number": "9752210230122059",
// "issuer": "urn:x_swe:carrier:74"
// },
// //Annual card
// {
// "type": "TRAVEL_PASS",
// "code": "SJ_ANNUAL_GOLD",
// "number": "9752209709109276",
// "issuer": "urn:x_swe:carrier:74"
// }
]
}
],
// //Biz
// "corporateCodes": [
// {
// "code": "800039",
// "issuer": "urn:x_swe:carrier:74"
// }
// ],
"embed": [
"ALL"
]
}
2. Call POST /Booking to create a provisional booking for the trip
{
//Should contain the id/number of this booking in the OSDM client’s system.
//SJ will store this to facilitate troubleshooting or customer service
"externalRef": "ABC123",
"purchaser": {
"externalRef": "purchExternal_1",
//all four purchaser properties are mandatory
"detail": {
"firstName": "Carl",
"lastName": "Stenberg",
"email": "testare.testsson.nosmsmfa@sjmail.se",
"phoneNumber": "+55506382870"
}
},
"offers": [
{
//A list of offer ids from the offer response that you have selected and want to book.
"offerId": "{{offerId}}",
//A list of passenger references. This should contain all anonymousPassengerSpecifications.externalRef from the offer request.
"passengerRefs": [
"passenger-1"
]
}],
//A list of passenger specifications. Must contain the same number of passengers as the offer request and the passengerRefs array above. Also, the externalRefs must match the anonymousPassengerSpecifications.externalRef from the offer request.
"passengerSpecifications": [
{
//same reference like in the offer request
"externalRef": "passenger-1",
"age": 35,
"type": "PERSON", //always set to PERSON
//Only firstname and lastname is mandatory, but the other two should be encouraged so the passenger gets realtime traffic information updates
"detail": {
"firstName": "Ulysses",
"lastName": "Grant",
"eMail": "testare.testsson.nosmsmfa@sjmail.se",
"phoneNumber": "+55506382870"
}
}],
"embed": ["ALL"]
}