API Reference

The Salling Group APIs are organized around REST. Our API has resource-oriented URLs, and uses HTTP response codes for indicating API errors. We use built-in HTTP features, like HTTP authentication and verbs, which any HTTP client understands.

We support cross-origin resource sharing allowing you to interact securely with our API from a client-side web application (never share your secret API key in a public website's client-side code). All API endpoint responses return JSON including errors.

Quick Start

Get started using our APIs in a couple of minutes by following the steps below.

Integrate the Salling Group APIs in your app or website immediately by signing up and acquiring a token:

  1. Register or sign in and create your project
  2. Select the APIs your project needs access to
  3. Make API requests using the credentials for the project to confirm that everything is up and running (Be aware that it takes a minute or two before you can use your new credentials after creating a project)

Making a Request

To make sure that your credentials are working make an API request using your token.

Bearer token example request for stores data:

curl -X GET https://api.sallinggroup.com/v2/stores -H 'Authorization: Bearer <YOUR-TOKEN>'

If you get back a HTTP 200 status and a response with stores data, then you are ready to start your integration with the Salling Group API.

Next up is reading the API documentation and have a look at our SDKs for fast and easy access.

Postman

Get all the APIs as a Postman collection.

Run in Postman

All you need is to set the Postman environment variables with your credentials.

Anti Food Waste

Read the Open API Specification here.

Salling Group has more than 10.000 food products on sale - about to expire in local stores - every day while a lot of customers may not be aware of it. To help reduce food waste in Denmark, our food waste API provides information about food heavily discounted in stores due to short expiry.

Stores include Føtex, Netto, Basalt and Bilka in Denmark.

The Anti Food Waste API has two endpoints, each supporting different use cases.

Food Waste in Nearby Stores

If you want to get food waste information from nearby Føtex, Netto, Basalt, or Bilka, you can either query by zip code or based on your coordinates. Please note that at most 20 stores are returned, which will include all stores by any zip code. please be aware of the number of store limitations when you are using your coordinates with radius. A large area search may lead to an incomplete store list.

Please note that authentication is needed. There's an Auth SDK available to make it easy for you.

The API has a traffic management policy for spike protection to prevent abusive/bursts of requests. More information is available in spike protection section.

An example of query by zip code:

curl -X GET \  'https://api.sallinggroup.com/v1/food-waste/?zip=8000' \  -H 'Authorization: Bearer <your token>'

An example of query by geolocation:

curl -X GET \  'https://api.sallinggroup.com/v1/food-waste/?geo=56.154459,10.206777&radius=5' \  -H 'Authorization: Bearer <your token>'

will give you something like this

[    {      "clearances": [          {              "offer": {                  "currency": "DKK",                  "discount": 7,                  "ean": "20028992",                  "endTime": "2019-11-15T22:23:23.000Z",                  "lastUpdate": "2019-11-02T13:50:35.000Z",                  "newPrice": 15,                  "originalPrice": 22,                  "percentDiscount": 31.82,                  "startTime": "2019-10-31T07:19:19.000Z",                  "stock": 4,                  "stockUnit": "each"              },              "product": {                  "description": "SF HØNSESALAT 250G",                  "ean": "5704000474742",                  "image": "https://dam.dsg.dk/services/assets.img/id/f8f240ff-5e83-435a-a55a-477a6540b89b/size/WEB1024x1024.jpg"              }          },      ],      "store": {          "address": {              "city": "Århus C",              "country": "DK",              "extra": null,              "street": "Jægergårdsgade 64-70",              "zip": "8000"          },          "brand": "netto",          "coordinates": [              10.200813,              56.148021          ],          "hours": [              {                  "date": "2019-11-04",                  "type": "store",                  "open": "2019-11-04T07:00:00",                  "close": "2019-11-04T23:00:00",                  "closed": false              },              {                  "date": "2019-11-05",                  "type": "store",                  "open": "2019-11-05T07:00:00",                  "close": "2019-11-05T23:00:00",                  "closed": false              }          ],          "name": "Netto Jægergårdsgade",          "id": "da2957d5-67ec-4f24-9c49-235b6712e063",          "type": "Point"      }  },  {    // more from another store    ...  }]

Read the Open API Specification here.

What's in the return data?

  1. Clearances is a list of price reduced food products. The offer section describe discount together with valid time and pieces(weight) in stock. The product section describe food with (optional) image.
  • Please note that stock is not real-time data, therefore you can only use it as an indicator. It's updated as products are sold, but some products break or are sold before consumers arrive at stores
  • The API only serves data about clearances that were created or had their stock count changed yesterday or today.
  1. Store gives detail information about the queried store, including the opening hours in two days.

Food Waste in a Store

If you only want to get food waste information from a store, you can get it by using a store ID. Store IDs can be fetched from our stores API. query as follow:

curl -X GET \  'https://api.sallinggroup.com/v1/food-waste/da2957d5-67ec-4f24-9c49-235b6712e063' \  -H 'Authorization: Bearer <your token>'

Above query will give you back something like

{    "clearances": [        ...    ],    "store": {        ...        "name": "Netto Jægergårdsgade",    }}

Product Suggestions

Read the Open API Specification here.

Salling Group has a lot of products for sale, and so finding the ones relevant for you or your users can be a daunting task. Our Product Suggestions API helps make this easier by utilizing various machine learning techniques.

All products returned are those on Bilka ToGo.

The Product Suggestions API has three different endpoints, each supporting different use cases.

Please note that we have spike protection on this service.

Relevant Products

Sometimes you have a small string of text and you need to find products that match this string. Maybe your user entered "mælk", and you'd like to present them with a selection of actual milk products they can buy. If so, this endpoint can help you out.

curl -X GET \  'https://api.sallinggroup.com/v1-beta/product-suggestions/relevant-products?query=m%C3%A6lk' \  -H 'Authorization: Bearer <your token>'

will give you back something like

{  "suggestions": [    {      "title": "Naturmælk Bio Minimælk 0,5% 1 L",      "id": "93008500001",      "prod_id": "19680",      "price": 11.95,      "description": "",      "link": "https://www.bilkatogo.dk/s?query=19680",      "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=19680&imgType=jpeg"    },    {      "title": "Arla Øko Skummetmælk",      "id": "93005600001",      "prod_id": "19688",      "price": 9.95,      "description": "",      "link": "https://www.bilkatogo.dk/s?query=19688",      "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=19688&imgType=jpeg"    },    {      "title": "Arla øko minimælk",      "id": "93005500001",      "prod_id": "19687",      "price": 9.95,      "description": "",      "link": "https://www.bilkatogo.dk/s?query=19687",      "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=19687&imgType=jpeg"    },    {      "title": "Naturmælk Letmælk m. Jersey",      "id": "39003701",      "prod_id": "39445",      "price": 12.95,      "description": "naturmælk org letmælk",      "link": "https://www.bilkatogo.dk/s?query=39445",      "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=39445&imgType=jpeg"    },    // …  ]}

Similar Products

If you're already found a Bilka ToGo product, you might want a selection of similar products. Maybe the one you want is out of stock, or you're just curious about alternatives. If so, you can use its ID to ask for similar products:

curl -X GET \  'https://api.sallinggroup.com/v1-beta/product-suggestions/similar-products?productId=31802' \  -H 'Authorization: Bearer <your token>'

asks for products similar to a bag of hot'n'spicy peanuts and will give you back something like

[  {    "id": "15155101",    "prod_id": "43414",    "title": "TROPE CHILI NØDDER 275 G",    "description": "trope chili nødder 275 g",    "price": 22.98,    "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=43414&imgType=jpeg",    "link": "https://www.bilkatogo.dk/s?query=43414"  },  {    "id": "73099601",    "prod_id": "62126",    "title": "KIMS CRUNCH NUTS ",    "description": "kims crunch nuts  18x135g",    "price": 23.34,    "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=62126&imgType=jpeg",    "link": "https://www.bilkatogo.dk/s?query=62126"  },  {    "id": "27072101",    "prod_id": "59923",    "title": "HOT CHILI PEANUTS 210G",    "description": "hot chili peanuts 210g",    "price": 21,    "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=59923&imgType=jpeg",    "link": "https://www.bilkatogo.dk/s?query=59923"  },  {    "id": "81367900031",    "prod_id": "16425",    "title": "Duyvis Nødder Oven Roasted Sweet Paprika",    "description": "sweet paprika duyvis",    "price": 23.18,    "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=16425&imgType=jpeg",    "link": "https://www.bilkatogo.dk/s?query=16425"  },  // …]

Frequently Bought Together

Perhaps you already know which product you want, and you'd like a recommendation to go with that particular product. The Frequently Bought Together endpoint helps suggest a list of products that are often bought alongside the product ID you supply.

Let's continue our snack example from above and see what's frequently bought together with our hot'n'spicy peanutes:

curl -X GET \  'https://api.sallinggroup.com/v1-beta/product-suggestions/frequently-bought-together?productId=31802' \  -H 'Authorization: Bearer <your token>'

asks for products frequently bought together with a bag of hot'n'spicy peanuts and will give you back something like

[  {    "id": "33111401",    "prod_id": "39099",    "title": "Levevis Øko Appelsinjuice",    "description": "appelsinjuice levevis øko",    "price": 9.5,    "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=39099&imgType=jpeg",    "link": "https://www.bilkatogo.dk/s?query=39099"  },  {    "id": "33111501",    "prod_id": "39100",    "title": "Levevis Øko Æblejuice",    "description": "æblejuice levevis øko",    "price": 8.95,    "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=39100&imgType=jpeg",    "link": "https://www.bilkatogo.dk/s?query=39100"  },  {    "id": "84337300100",    "prod_id": "26014",    "title": "Coca Cola 1.5 liter",    "description": "",    "price": 15.5,    "img": "https://image.prod.iposeninfra.com/bilkaimg.php?pid=26014&imgType=jpeg",    "link": "https://www.bilkatogo.dk/s?query=26014"  }]

Stores

Read the Open API Specification here.

The Stores API gives you access to information about store locations and opening hours on all of Salling Group's ~1.500 stores in Denmark, Poland, and Germany. The Stores API contains information about physical stores for the brands Basalt, Bilka, BR, Carl's Jr, føtex, Netto, Salling, and Starbucks.

Stores data include:

  • Name
  • Brand
  • Address
  • Coordinates
  • Distance (for proximity searches)
  • Opening hours
  • Various attributes (Open during holidays? Implements Scan&Go? Etc.) See the Attributes section.

Request

curl -X GET https://api.sallinggroup.com/v2/stores -H 'Authorization: Bearer <YOUR-TOKEN>'

Partial Response

[  {    "address": {      "city": "Skanderborg",      "country": "DK",      "street": "Gasværksvej 5",      "zip": "8660"    },    "brand": "foetex",    "coordinates": [      9.9312455,      56.0325836    ],    "created": "2019-04-03T13:30:18.761",    "distance_km": null,    "hours": [      {        "date": "2019-04-03",        "type": "store",        "open": "2019-04-03T08:00:00",        "close": "2019-04-03T21:00:00",        "closed": false      },      {        "date": "2019-04-03",        "type": "bakery",        "open": "2019-04-03T07:00:00",        "close": "2019-04-03T21:00:00",        "closed": false      },      // …    ],    "modified": "2019-04-03T13:30:18.761",    "name": "føtex Skanderborg",    "phoneNumber": "87272000",    "sapSiteId": "1362",    "type": "Point",    "vikingStoreId": "2262",    "attributes": {      // …    },    "id": "496e19b1-4655-43aa-95dd-c2ad289d09b4"  },  // …]

Read the full Open API Specification here.

Opening Hours

Note that opening hours have a type. Some stores have different opening hours for e.g. the store, the gardening section, the bakery, etc. The "hours" attribute contains one array with all types of opening hours. You can ask for a specific type of opening hours by using the parameter "hourType". For example, you can get a list of stores' opening hours by querying:

curl -X GET https://api.sallinggroup.com/v2/stores/?hourType=store -H 'Authorization: Bearer <YOUR-TOKEN>'

For some stores, we support a new field in "hours" called "customerFlow". It is an array of floating numbers in the range of 0, 1 which predicts customer flows of a shop in a day. Each number represents the predicted customer flow per hour. The prediction time series starts at midnight. The higher the number, the busier the shop. Here is a visual example of "customerFlow" data.

viz_customer_flow

Specific Store

You can get a single store by its ID.

curl -X GET https://api.sallinggroup.com/v2/stores/496e19b1-4655-43aa-95dd-c2ad289d09b4 -H 'Authorization: Bearer <YOUR-TOKEN>'

Fields

By default you get all fields / attributes back when asking for your stores. If you're only interested in parts of the objects, you can use the fields parameter to specify exactly the fields your interested in. The fields parameter can be used in combination with all other parameters and also work when getting a store by its ID.

curl -X GET https://api.sallinggroup.com/v2/stores?fields=name,address&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'
[    {        "name": "Bilka Tilst",        "address": {            "city": "Tilst",            "country": "DK",            "street": "Agerøvej 7",            "zip": "8381"        }    },    // …]

Proximity Requests

You can make proximity requests by giving a geocode and a radius to get stores nearby. Radius is in kilometers and defaults to 10 kilometers if not provided.

curl -X GET https://api.sallinggroup.com/v2/stores?geo=55.1,10.2&radius=20 -H 'Authorization: Bearer <YOUR-TOKEN>'

Filters

The Stores API supports request filters on a lot of the attributes. The following filters are supported:

  • zip
  • city
  • country ("dk", "de", "pl")
  • street (exact match only)
  • brand ("br", "bilka", "carlsjr", "foetex", "netto", "salling", "starbucks")
curl -X GET https://api.sallinggroup.com/v2/stores?brand=netto&country=de&city=berlin&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'curl -X GET https://api.sallinggroup.com/v2/stores?brand=foetex&country=dk&zip=2000&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'

Attributes

These are all the possible attributes. Not all attributes make sense for all brands of stores, and not all stores have a value for all attributes.

Each store will expose zero or more of the attributes. When you supply attribute filters, only those stores matching your query exactly will be returned, so a store that doesn't specify whether it has scanAndGo or not will not be returned for a query like ?scanAndGo=true and will not be returned for the opposite query, ?scanAndGo=false. There is currently no way to express a filter like "a store that either has holidayOpen=true or doesn't specify holidayOpen".

AttributeTypeDescription
babyChangingBooleanDoes the store have baby changing facilities?
bakeryBooleanDoes the store have a bakery?
carlsJuniorBooleanDoes the store have a Carls' Jr. restaurant?
clickAndCollectBooleanDoes the store support ClickAndCollect?
enablingFacilitiesBooleanDoes the store have enabling facilities?
flowersBooleanDoes the store have a flower section?
foodClickAndCollectBooleanDoes the store support ClickAndCollect for food items?
gardenBooleanDoes the store have a garden section?
holidayOpenBooleanIs the store open during holidays?
nonFoodBoolean
open247BooleanIs the store open 24/7 (all hours of the day, all days of the week)?
parkingBooleanDoes the store have a parking lot for customers?
parkingRestrictionsBooleanDoes the store's parking lot have parking restrictions?
petFoodBooleanDoes the store have a pet food section?
pharmacyBooleanDoes the store sell pharmaceuticals?
scanAndGoBooleanDoes the store implement Scan&Go?
smileyschemeStringThe ID of the store in the government smiley scheme (https://www.findsmiley.dk).
spotFoodBooleanDoes the store have Spot Food?
spotNonfoodBooleanDoes the store have Spot Nonfood?
starbucksBooleanDoes the store have a Starbucks?
swipBoxBooleanDoes the store have a SwipBox?
wcBooleanDoes the store have toilets?
wifiBooleanDoes the store have open WiFi for customers?

Each attribute can be specified in the query. Examples:

  • https://api.sallinggroup.com/v2/stores?holidayOpen=true returns only stores that are open during holidays.
  • https://api.sallinggroup.com/v2/stores?scanAndGo=true returns only stores that implement Scan&Go.

Holidays

Read the Open API Specification here.

The Holidays API allows you to get Danish, German and Polish holidays in a given timeframe in years both in the future and in the past. It can also help you check if a given date is a holiday. The API contains both public and non-public holidays and each type is clearly marked in the response. There are some non-public holidays like Black Friday that are particular relevant for retail and consumers.

Dates used in this API should be in the following format YYYY-MM-DD.

Note: Multiple holidays may occur on the same date.

Holidays - The next 12 months

You can get all holidays within the upcoming year from today like so:

curl -X GET https://api.sallinggroup.com/v1/holidays -H 'Authorization: Bearer <YOUR-TOKEN>'

Holidays - The next 12 months from a date

You can get all holidays within the upcoming year from a given date like so:

curl -X GET https://api.sallinggroup.com/v1/holidays?startDate=2018-01-01 -H 'Authorization: Bearer <YOUR-TOKEN>'

Holidays - Until a given date

You can get all holidays between today and a given date like so:

curl -X GET https://api.sallinggroup.com/v1/holidays?endDate=2020-01-01 -H 'Authorization: Bearer <YOUR-TOKEN>'

Holidays - Specific period in time

You can get all holidays between two dates like so:

curl -X GET https://api.sallinggroup.com/v1/holidays?startDate=2018-01-01&endDate=2018-04-30 -H 'Authorization: Bearer <YOUR-TOKEN>'
[    {        "date": "2018-01-01",        "name": "Nytårsdag",        "nationalHoliday": true    },    {        "date": "2018-01-06",        "name": "Helligtrekongersdag",        "nationalHoliday": false    },    {        "date": "2018-02-11",        "name": "Fastelavn",        "nationalHoliday": false    },    {        "date": "2018-02-14",        "name": "Valentinsdag",        "nationalHoliday": false    },    {        "date": "2018-03-25",        "name": "Palmesøndag",        "nationalHoliday": false    },    {        "date": "2018-03-29",        "name": "Skærtorsdag",        "nationalHoliday": true    },    {        "date": "2018-03-30",        "name": "Langfredag",        "nationalHoliday": true    },    {        "date": "2018-04-01",        "name": "Påskedag",        "nationalHoliday": true    },    {        "date": "2018-04-02",        "name": "2. påskedag",        "nationalHoliday": true    },    {        "date": "2018-04-27",        "name": "Store bededag",        "nationalHoliday": true    }]

Holidays - Check if a single date is a holiday

You can check if a given date, in this case Christmas Eve, is a holiday like so:

curl -X GET https://api.sallinggroup.com/v1/holidays/is-holiday?date=2018-12-24 -H 'Authorization: Bearer <YOUR-TOKEN>'
true

Holidays - Holidays in a Poland (country defaults to Denmark if not present)

You can get all holidays in Poland like this:

curl -X GET https://api.sallinggroup.com/v1/holidays?country=pl -H 'Authorization: Bearer <YOUR-TOKEN>'

Read the Open API Specification here.

Jobs

Read the Open API Specification here.

The Jobs API gives you access to all of Salling Group's available job postings in Denmark, Poland and Germany for Netto, Basalt, føtex, Bilka, Salling, BR, Starbucks, Carl's Jr. and Salling Group.

Job data include:

  • Title
  • Description
  • Brand
  • Job details such as hours, start date etc.
  • Address
  • Application and public posting link
  • employmentType (full time, part time)
  • Job Level (Manager, graduate, under 18 etc.)
  • Job Categories and more...
curl -X GET https://api.sallinggroup.com/v1/jobs -H 'Authorization: Bearer <YOUR-TOKEN>'

Partial Response Example

[  {    "address": {        "city": "Aarhus C",        "country": "DK",        "street": "Thorvaldsensgade 22",        "zip": "8000"    },    "brand": "netto",    "title": "Salgsassistent",    "description": "<h3 align=\"left\">Salgsassistent</h3>\r<br><p>Vi s&oslash;ger en salgsassistent til Netto</p>\r<br><p>Som salgsassistent er du med til at sikre, at vores kunder f&aring;r en god indk&oslash;bsoplevelse. Din hverdag er varieret, og ikke to dage er ens.</p>\r<br><p><strong>Dine opgaver er blandt andet</strong></p>\r<br><ul>\r<br><li>Kassebetjening</li>\r<br><li>Vareopfyldning</li>\r<br><li>Ansvarlig for eget omr&aring;de - eksempelvis frugt- og gr&oslash;ntafdelingen</li>\r<br><li>Andet forefaldende butiksarbejde</li>\r<br></ul>\r<br><p><strong>Kvalifikationer</strong></p>\r<br><p>Som salgsassistent er det vigtigt, at du er positiv og serviceminded, s&aring; b&aring;de kolleger og kunder oplever butikken som et rart sted at v&aelig;re, n&aring;r du er p&aring; arbejde.</p>\r<br><p>Du er udadvendt og god til at kommunikere med alle typer af kunder. Naturligvis er det en fordel, hvis du har kendskab til butiksarbejde, men vi s&oslash;rger for grundig opl&aelig;ring.</p>\r<br><p><strong>Personalegoder </strong>Som ansat i Netto har du mulighed for at benytte en lang r&aelig;kke personalegoder.</p>\r<br><ul>\r<br><li>Personalerabat i Bilka, f&oslash;tex, Netto og&nbsp;Salling</li>\r<br><li>Medarbejderrabat p&aring; Nettos eget mobilabonnement - Nettalk</li>\r<br><li>Frisk frugt og gr&oslash;nt p&aring; arbejdspladsen hver dag</li>\r<br><li>Rabat p&aring; fitnessmedlemsskab</li>\r<br><li>... i alt omkring 100 - interne og eksterne - rabatordninger</li>\r<br></ul>\r<br><p><strong> Ans&oslash;gningen </strong></p>\r<br><p>Kan du se dig selv i stillingen som salgsassistent i Netto, s&aring; s&oslash;g jobbet via linket her p&aring; siden. Vi behandler ans&oslash;gningerne l&oslash;bende.</p>\r<br>",    "start": "2022-06-01",    "hours": "8",    "jobType": null,    "applicationLink": "https://career5.successfactors.eu/career?career_ns=job_listing&company=DSG&navBarLevel=JOB_SEARCH&rcm_site_locale=da_DK&career_job_req_id=5922",    "created": "2022-05-28T08:49:20.304Z",    "modified": "2022-05-28T08:49:20.304Z",    "id": "3c5058f6-960b-440b-9535-18e1c1df7405",    "published": "2022-04-11T22:48:45.000Z",    "requisitionId": "126328",    "premium": false,    "unsolicited": false,    "trainee": false,    "country": "DK",    "region": "midtjylland",    "categories": [        "salesGeneral"    ],    "url": "https://sallinggroup.com/job/ledige-stillinger/job/?id=3c5058f6-960b-440b-9535-18e1c1df7405",    "employmentType": "partTime",    "end": null,    "jobLevel": "employeeUnder18"  }]

Read the Open API Specification here.

Specific Job

You can get a single job by its id.

curl -X GET https://api.sallinggroup.com/v1/jobs/3c5058f6-960b-440b-9535-18e1c1df7405 -H 'Authorization: Bearer <YOUR-TOKEN>'

Fields

By default you get all fields / attributes back when asking for jobs. If you're only interested in parts of the objects, you can use the fields parameter to specify exactly the fields your interested in. The fields parameter can be used in combination with all other parameters and also work when getting a job by its id.

curl -X GET https://api.sallinggroup.com/v1/jobs?fields=title,url,start&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'
[  {    "title": "Salgsassistent",    "url": "https://sallinggroup.com/job/ledige-stillinger/job/?id=3c5058f6-960b-440b-9535-18e1c1df7405",    "start": "2018-04-16"  }]

Filters

The Jobs API supports request filters on a lot of the attributes, see our OAS documentation for details.

curl -X GET https://api.sallinggroup.com/v1/jobs?brand=netto&country=de&city=berlin&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'curl -X GET https://api.sallinggroup.com/v1/jobs?brand=foetex&country=dk&zip=2000&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'curl -X GET https://api.sallinggroup.com/v1/jobs?country=dk&region=hovedstaden&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'curl -X GET https://api.sallinggroup.com/v1/jobs?country=de&category=businessDevelopment&per_page=1 -H 'Authorization: Bearer <YOUR-TOKEN>'

Supporting Lists

Lists for filtering are also available.

Categories

Categories can be filtered by language ("en", "da", "sv", "de", "pl") and are available under ../jobs/categories.

curl -X GET https://api.sallinggroup.com/v1/jobs/categories?language=en -H 'Authorization: Bearer <YOUR-TOKEN>'

Regions

Regions can be filtered by country ("dk", "se", "de", "pl") and are available under ../jobs/regions.

curl -X GET https://api.sallinggroup.com/v1/jobs/regions?country=dk -H 'Authorization: Bearer <YOUR-TOKEN>'

Employment Types

Employment types can be filtered by language ("en", "da", "sv", "de", "pl") and are available under ../jobs/types.

curl -X GET https://api.sallinggroup.com/v1/jobs/types?language=en -H 'Authorization: Bearer <YOUR-TOKEN>'

Job Levels

Job levels can be filtered by language ("en", "da", "sv", "de", "pl") and are available under ../jobs/levels.

curl -X GET https://api.sallinggroup.com/v1/jobs/levels?language=dk -H 'Authorization: Bearer <YOUR-TOKEN>'

Authentication

We support multiple types of authentication, some APIs may only allow one or the other. Bearer tokens are generally supported for most of our APIs, with API Keys being supported for some other APIs. JWTs are supported for legacy purposes but deprecated so we encourage you to not pick it for new projects.

To decide which type of authentication you should use, you can read our guide .

JWT Authentication

Deprecation note
This authentication type is deprecated. Please pick a different type for your project whenever possible.

We use JSON Web Tokens(JWT) for authentication based on the JWT spec. You can learn more about JWT on the official website.

Generating the JWT on your side

To authenticate the request, include an Authorization HTTP header containing the string JWT and the generated JSON Web Token.

The token should consist of the three following strings, combined with a .(dot) separator.

Header:

The header is a JSON object with metadata about how to calculate the signature.

  • alg (algorithm) – The algorihm used for signing the token. We only support HS256, HS384 and HS512.
  • typ (token type) – The type of the token. We only support JWT.

Payload:

The payload is a JSON object with information authenticating the request to be sent.

  • iss (issuer) – The auto-generated issuer token you have been supplied with when creating a project. The issuer token is unique for each project and can be found by navigating to Your projects after you've signed in.
  • sub (subject) – The path and query of the request being authenticated. It is important that the subject is not url encoded. E.g. use /v2/stores?brand=foetex&city=Køge instead of %2Fv2%2Fstores%3Fbrand%3Dfoetex%26city%3DK%F8ge.
  • exp (expiration time) – Current time or time of expiration for authentication of the current request. (Formatted as a unix timestamp, seconds since 1970-01-01T00:00:00Z in the UTC timezone)
  • mth (method) – The HTTP Method of the request.

Signature

The signature is generated based on the header and payload following this pseudo-code algorithm:

encodedHeader = BASE64URLENCODE(header) // Not the same as regular base64 read the note after this code block.encodedPayload = BASE64URLENCODE(payload) // Not the same as regular base64 read the note after this code block.unencodedToken = JOIN_STRING(encodedHeader, '.', encodedPayload)signature = HASH_FUNCTION(unencodedToken) // Use the hash function defined by the name in header.alg

NOTE: base64url is NOT the same thing as base64 encoding, to read more go to RFC4648.

This signature is then appended to the unencodedToken to validate that it has not been tampered with, to generate a full JWT of the form:

encodedHeader.encodedPayload.signature

based on the values generated in the pseudocode above.

Example

For example a token with the following fields:

header = {  'alg': 'HS256',  'typ': 'JWT',};payload = {  'exp': 1451635200,  'iss': '<your issuer token goes here>',  'mth': 'GET',  'sub': '/v2/stores?brand=foetex',};

would turn into the following HTTP header using secret as the signature key.

Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NTE2MzUyMDAsImlzcyI6InVzZXJAZG9tYWluLmNvbSIsIm10aCI6IkdFVCIsInN1YiI6Ii92MS9zdG9yZXM_YnJhbmQ9Zm9ldGV4In0.-Gb5XKWd4xMrS412Y7B8m6jO-aH4Hik0pLdOPA294Vg

Bearer Authentication

The Bearer authentication model is very simple, but less secure.

Simply include the token/key you have been given from us in the HTTP Authorization header along with the string Bearer.

Example

If your token is 4b8c9202-9bae-11e8-abfa-ff5bd6029dd8, simply include this HTTP header with your request:

Authorization: Bearer 4b8c9202-9bae-11e8-abfa-ff5bd6029dd8

API Key Authentication

The API Key authentication model is also very simple, but even less secure, because it will be present in the URL of all requests.

Simply include the token/key you have been given from us in the URL as a query parameter with the name apiKey.

Example

If your token is 4b8c9202-9bae-11e8-abfa-ff5bd6029dd8 and you are requesting stores, simply include it in the URL like this:

https://api.sallinggroup.com/v2/stores/?apiKey=4b8c9202-9bae-11e8-abfa-ff5bd6029dd8

SDKs

SDKs have been created in order to simplifiy your workflow. These packages make it possible to quickly start using our APIs without much preliminary work.

Node.js

The Node.js SDKs are distributed through NPM. Their documentation can be seen on their respective NPM pages. You will need access to the respective APIs used by the SDKs.

Here is a list of the currently available SDKs:

Auth

The auth library creates an Axios instance that handles authentication for you. This is ideal if you want to use an API, which has no SDK, or just prefer more control. It is used internally by the other SDKs.

Stores SDK

The Stores SDK makes it easy to query Salling Group's stores through the Stores API. Through this you can query stores in a lot of ways and fetch information such as opening hours, address, and more.

Jobs SDK

The Jobs SDK makes it easy to query Salling Group's open job positions through the Jobs API. Through this you can query open job positions in a lot of ways and fetch information such as hours per week, title, description, and more.

Holidays SDK

The Holidays SDK makes it easy to query Danish, German and Polish holidays through the Holidays API. Through this you can get all holidays within a range, and check if a given date is a holiday.

Pagination Traverser

The pagination traverser library helps you traverse through paginated resources. This is used internally by SDKs such as Stores that often return large datasets.

Pagination

Requests that return multiple items can be paginated by default. You can specify further pages with the page parameter. For some resources, you can also set a custom page size with the per_page parameter. Note that not all endpoints support the page and per_page parameters, see events for example.

Also note that the page numbering is 1-based and that omitting the ?page parameter will return the first page.

curl https://api.sallinggroup.com/v2/stores?page=2&per_page=10

Requesting pages out of range will give you a HTTP 400 response.

The pagination info can be included in the response Link header. It is important to follow the link header values instead of constructing your own URLs to ensure you follow API pagination standard.

Example from the stores API:

Link: <https://api.sallinggroup.com/v2/stores?page=1&per_page=10>; rel="first",
      <https://api.sallinggroup.com/v2/stores?page=3&per_page=10>; rel="next",
      <https://api.sallinggroup.com/v2/stores?page=1&per_page=10>; rel="previous",
      <https://api.sallinggroup.com/v2/stores?page=134&per_page=10>; rel="last"

The possible rel values are:

NameDescription
nextShows the URL of the immediate next page of results.
lastShows the URL of the last page of results.
firstShows the URL of the first page of results.
prevShows the URL of the immediate previous page of results.

X-Total-Count Header

The X-Total-Count header will display the total amount of entites available from a query while ignoring the pagination. For example, when quering stores API for the BR brand /v2/stores?page=1&per_page=10&brand=BR, the X-Total-Count may be 250, showing the total amount of stores given the query. Whereas the query will only return the first ten due to the pagination.

This can be used to see if there is still content left to fetch and make it possible to calculate the total amount of pages available based on the pagination parameters.

totalPages = Math.ceil(x-total-count / per_page)
entitesRemaining = x-total-count - (per_page * page)

Spike Protection

To protect our APIs from malicious attack or abusive usage, we apply spike protection in some of our APIs. Spike protection will detect unreasonable traffic from an IP. That is, an IP sends a lot of requests in a short time which causes a huge spike in traffic. Those IPs will be quarantined for a certain amount of time. Our APIs will not respond to quarantined IPs until they serve their time.

APIs with spike protection are: anti Food waste, product suggestions

What should I do?

If you want to avoid being quarantined, or if you are quarantined and want to know how long will it last, you can check the Retry-After header in our response.

  • Case 1: You are not quarantined. Retry-After header will tell you how many seconds to wait until it's 'safe' to send another request. If you send another request before that time, you're moving closer to getting quarantined. We grant a small "buffer" of seconds you're allowed to be ahead of the Retry-After time, but if you get too far ahead, you'll be quarantined. The specific buffer varies between services.
  • Case 2: You are quarantined. Retry-After header will tell you when your quarantine ends.

Quota

To protect our APIs from excessive volumes of request, each user is given a certain quota of requests for certain APIs. When a user exceeds their quota for an API, all requests to that API will be rejected with status code 429 and a Retry-After header, which specifies how many seconds are left before the quota resets. Quotas have different numbers of requests and reset with different intervals. This varies per API. See the table below for details.

APIs with quota are: anti food waste, product suggestions

APIQuota
Anti food waste10,000 requests per day
Product suggestions10,000 requests per month

Client errors

Errors are returned using proper HTTP error codes. Error responses will contain a API specific error code as well as a description on how to solve the error and a suggested user message.

Example:

{  "errorCode": 1234,  "developerMessage": "Your API rate limit exceeded. See http://developer.sallinggroup.com/api-reference/#topics-rate-limiting for details.",  "userMessage": "The service is currently busy. Try again in 156 seconds",  "moreInfo": "https://developer.sallinggroup.com/api-reference/#topics-client-errors"}

Resource specific error codes follows the same convention, but can return resource specific errors. These are mentioned in the resource documentation.

Choosing authentication type

We offer three types of authentication: bearer tokens, API keys and custom JSON Web Tokens (JWT)

and they are each described in the Authentication section.

In general, the preferred authentication type is bearer tokens which is the simplest for you to use.

If you use the Salling Group SDKs the authentication is handled for you and you can get started right away.

Note: Some APIs only support select authentication types. If they don't support the selected type they won't show up when creating projects

Bearer tokens

We support bearer tokens for all environments that can not be trusted to keep a secret. If people get hold of such a bearer token, they can use it in all the ways you can use it, but at least they don't learn anything about you. No emails, names, or anything else is visible to the attacker apart from the opaque token.

If a token is systematically abused we reserve the right to revoke it. If you suspect your token is being misused by others you should go to your projects and create a new one and delete the old project once replaced.

API Keys

API keys are sent in URLs and are primarily for the purpose of avoiding preflight request for CORS in browsers.

JSON Web Tokens

Deprecation note
This authentication type is deprecated. Please pick a different type for your project whenever possible.

We support custom JSON Web Tokens in environments where secrets can be kept, even where network traffic might be compromised. If you have a backend server, that backend server can sign a JWT which includes precisely the resource you're trying to access and the point in time you're accessing it, and this token can then either be used directly by your backend or given to an end user, which will make the request. This requires the ability for your server to sign the JWT in secrecy, and then give the signed JWT to a potentially untrusted party.

What happens if such a JWT falls into the hands of a malicious user? They now have a token, which is valid for ~15 minutes, and which grants access to precisely the resource you signed off on. If you signed a JWT for requesting all Danish Netto stores, then this is what the attacker will be able to do for a short time.

This model significantly reduces the possible abuse resulting from a leaked JWT compared to a leaked Bearer token.

Conclusion

We offer SDKs to take care of the signing for you - so you are free to choose authentication method.

Setup cross-origin resource sharing

It is easy to setup cross-origin resource sharing for your projects in the developer portal. Cross-origin resource sharing works on a project to project basis. To allow cross-origin resource sharing simply navigate to the project where you want to allow CORS and press the Add origin button under the Origin whitelist.

You must specify a valid origin with protocol (http / https) and host. We whitelist on an origin basis so there is no need to whitelist your specific paths or queries.

We adhere to nodejs.org's URL string definitions.

To whitelist https://sallinggroup.com add the following origin to the whitelist:

https://sallinggroup.com

To whitelist localhost on port 3000 add the following origin to the whitelist:

http://localhost:3000

JWT Authentication in Postman

Deprecation note
This authentication type is deprecated. Please pick a different type for your project whenever possible.

If you are using Postman, to interact with our API, you can use the following script for authenticating towards the API using JWT.

var removeIllegalCharacters = function(input) {    return input        .replace(/=/g, '')        .replace(/\+/g, '-')        .replace(/\//g, '_');};var base64object = function(input) {    var inputWords = CryptoJS.enc.Utf8.parse(JSON.stringify(input));    var base64 = CryptoJS.enc.Base64.stringify(inputWords);    var output = removeIllegalCharacters(base64);    return output;};var url = request.url;var slashIndex = url.toLowerCase().startsWith('http') ? 8 : 0;var path = decodeURIComponent(url.substring(url.indexOf('/', slashIndex), url.length));var exp = Date.now() / 1000 | 0;var iss = '<your issuer token goes here>';var mth = request.method;var sub = path;var header = { 'alg': 'HS256', 'typ': 'JWT' };var payload = { 'exp': exp, 'iss': iss, 'mth': mth, 'sub': sub };var unsignedToken = base64object(header) + "." + base64object(payload);var signatureHash = CryptoJS.HmacSHA256(unsignedToken, '<your secret goes here>');var signature = CryptoJS.enc.Base64.stringify(signatureHash);var token = unsignedToken + '.' + signature;postman.setGlobalVariable('authToken', removeIllegalCharacters(token));

Add the script in the Pre-request Script section for your request.

Remember to replace the place-holder values:

  • <your issuer token goes here>: The issuer token you have been supplied with when creating a project
  • <your secret goes here>: The shared secret you have been supplied from us, when given access to services requiring JWT authentication.

To actually send the generated token, you should then add the following HTTP header to your request.

Authorization: JWT {{authToken}}

If you are implementing JWT outside of Postman, we recommend taking a look at the many great libraries available to make working with JWT much easier.

You can also use our SDKs to get started quickly.

Support

We're here to help you. Contact us at API support.

Developer Program Terms and Conditions

Please read the Developer Program Terms and Conditions before creating your first project.

Here is our Developer Program Terms and Conditions.

About

Salling Group is an international retailer with stores in three countries: Denmark, Poland, and Germany.

The group operates discounters, supermarkets, hypermarkets, restaurants, coffee shops, webshops and meal boxes across a variety of brands such as Basalt, Bilka, BR, Carl's Jr, føtex, Netto, Salling, and Starbucks.

Read more on sallinggroup.com.