API Reference

The Salling Group API is 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

Integrating the Salling Group APIs in your app or website can begin as soon as you have acquired yourself a bearer token or an API secret following these steps:

  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 (Remember that it takes approximately 5 minutes 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 either your bearer token or create a JWT for your request.

Bearer token example request:

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

JWT example request:

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

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

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.

Food Waste

Read the Open API Specification here.

Salling Group has a lot of food on sale or about to expire 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 on sale or soon to be expired in stores.

Stores include Foetex, Netto and Bilka in Denmark.

The 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 Foetex, Netto, or Bilka, you can either query by zip code or based on your coordinates. Please note that at most we return 20 stores, 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 will lead to an incomplete store list.

Please note that authentication is needed. We provide Auth SDK to make it easy for you.

An example of query by zip:

curl -X GET \
  'https://api.sallinggroup.com/v1/food-waste/?zip=8000' \
  -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
    ...
  }
]

What's in the return data?

  1. Clearances is a list of price reduced food. 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.
  • We only serve 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.

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 cartridges 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, Germany and Sweden. The Stores API contains information about Netto, føtex, Bilka, Carl's Jr. and Salling.

Stores data include:

  • Name
  • Brand
  • Address
  • Coordinates
  • Distance (for proximity searches)
  • Opening hours

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"
  },
  // …
]

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>'

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", "se", "de", "pl")
  • street (exact match only)
  • brand ("netto", "bilka", "foetex", "salling", "carlsjr")
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>'

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, Germany and Sweden for Netto, føtex, Bilka, Salling, BR, Starbucks, Carl's Jr. and Salling Group HQ.

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": "2018-04-16",
    "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": "2018-05-28T08:49:20.304Z",
    "modified": "2018-05-28T08:49:20.304Z",
    "id": "3c5058f6-960b-440b-9535-18e1c1df7405",
    "published": "2018-04-11T22:48:45.000Z",
    "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",
    "jobLevel": "employeeUnder18"
  }
]

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. The following filters are supported:

  • zip
  • city
  • country ("dk", "se", "de", "pl")
  • brand ("netto", "bilka", "foetex", "salling", "br", "carlsjr", "starbucks", "sallinggroup")
  • category
  • region
  • jobLevel
  • employmentType
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>'

Holidays

Read the Open API Specification here.

Our Holidays API allows you to get Danish 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.

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

Authentication

We support multiple types of authentication, some APIs may only allow one or the other. As a general rule, all APIs support JWT authentication while bearer token is only allowed for some select APIs.

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

JWT Authentication

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': '/v1/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

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.

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.

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 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

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/v1/stores?page=2&per_page=10

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

Link Header

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/v1/stores?page=1&per_page=10>; rel="first",
      <https://api.sallinggroup.com/v1/stores?page=3&per_page=10>; rel="next",
      <https://api.sallinggroup.com/v1/stores?page=1&per_page=10>; rel="previous",
      <https://api.sallinggroup.com/v1/stores?page=134&per_page=10>; rel="last"

The possible rel values are:

Name Description
next Shows the URL of the immediate next page of results.
last Shows the URL of the last page of results.
first Shows the URL of the first page of results.
prev Shows the URL of the immediate previous page of results.

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 two types of authentication, JSON Web Token (JWT) and Bearer tokens, and they are each described in the Authentication section.

In general, the preferred authentication type is JWT, but in an environment that can not be trusted to keep a secret the entire encoding and signature of a JWT is cumbersome and essentially unnecessary, so we also offer Bearer tokens, with a much simpler model that is then less secure.

Bearer tokens

We use 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.

This is probably the best we can do in an environment with no secrets. If a token is systematically abused we reserve the right to revoke it.

JSON Web Tokens

We use 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

Go with the JWT solution if that's possible and convenient for you. We offer some SDKs to take care of the signing for you.

Go with Bearer tokens if secrecy is impossible or too cumbersome for your app.

JWT Authentication in Postman

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.

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

Support

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

About

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

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

Read more on our corporate website.

Other Programs

Wupti Marketplace

Salling Group owns and operates the Wupti Marketplace platform which enables you to sell your products on Wupti.com.

Read more about the program here

Wupti Affiliate Program

Salling Group also offers an affiliate program for Wupti.com where you can earn a commision by advertising for Wupti.com.

Read more here (in Danish)