Table of Contents

Delivery API

How to use our Delivery API

Access

Many endpoints in the delivery API supports both anonymous and authenticated requests. If a user is authenticated permissions and personalisation will take affect on a given endpoint changing returned data like prices, what content is available, discounts, assortments and other user dependent information.

Some endpoints always require authentication, e.g. endpoints related to users, order history and favorites.

Authentication

Authentication works through authenticating with a username and password. This returns a JWT containing the necessary information to identify the user and the validity of the token.

To authenticate a user and obtain a JWT, use the authenticate endpoint.

Using GET:

GET <yourHost>/dwapi/users/authenticate?userName=DemoUser&password=TestPassword123

Using POST:

POST <yourHost>/dwapi/users/authenticate
{
  "username": "DemoUser",
  "password": "TestPassword123"
}

Response would look like the following:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxMyIsIm5iZiI6MTY4NDgyMTQ1MywiZXhwIjoxNjg0ODIyMDUzLCJpYXQiOjE2ODQ4MjE0NTMsImlzcyI6IkR5bmFtaWN3ZWIgQS9TIiwiYXVkIjoiV2ViQVBJIn0.E6qnfrmb2adq3SvFpyatjXsy78xf2SZxuVhCr1EJTXQ"
}

This token has to be stored in the client (your app) and then subsequently used in most of the other endpoints where authentication is required to handle on a specific user or with specific permissions or personalization.

The returned JWT has to be used as a header authorization bearer token:

authorization: Bearer <token>

where <token> is the value returned in the authenticate response, e.g.:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxMyIsIm5iZiI6MTcxMzg4ODk5OCwiZXhwIjoxNzEzOTc1Mzk4LCJpYXQiOjE3MTM4ODg5OTgsImlzcyI6IkR5bmFtaWN3ZWIgQS9TIiwiYXVkIjoiV2ViQVBJIn0.Q1xPxgNqp9ahjexB0nl7xz5iv52q22mTOljj5va1a5U

JWTs are valid for a specific time. Default expiration for JWT in Dynamicweb is 1800 seconds, which is 30 minutes. This can be made shorter or longer when calling the authenticate endpoint. Longer expiration times are more insecure.

Before the token expires, it has to be refreshed, so your app should keep track of when the token was created so it can be refreshed before it expires. If a refresh is not made before the timeout, the login expires and the user has to authenticate again.

Refreshing JWT token

Refreshing the token is done using the refresh endpoint and passing in the Bearer token as well:

authorization: Bearer <token>

GET <yourHost>/dwapi/users/authenticate/refresh?expirationInSeconds=600

Which will provide a new token with a new expiration.

Impersonation

Impersonating lets you act in the role as another specific user, as long as you have the proper permissions to do so. You will simply be granted another token to use, which will have the identity of the user that's being impersonated.

To list all the users that's allowed to be impersonated with the given JWT token in the request, use

GET <yourHost>/dwapi/users/impersonatees

Including a header such as

authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxMyIsIm5iZiI6MTY4NDgyMTQ1MywiZXhwIjoxNjg0ODIyMDUzLCJpYXQiOjE2ODQ4MjE0NTMsImlzcyI6IkR5bmFtaWN3ZWIgQS9TIiwiYXVkIjoiV2ViQVBJIn0.E6qnfrmb2adq3SvFpyatjXsy78xf2SZxuVhCr1EJTXQ

The response is a list of users that can be impersonated

[
  {
    "address": "Vejnavn 1",
    "city": "Aalborg",
    "company": "Company Customer DK",
    "country": "Denmark",
    "countryCode": "DK",
    "email": "noreply@dynamicweb.dk",
    "id": 87,
    "name": "Company Customer DK",
    "phone": "87654321",
    "userName": "CompanyCustomerDK",
    "zip": "9000",
  },
  {
    "email": "noreply@dynamicweb.dk",
    "id": 88,
    "name": "Company Customer UK",
    "userName": "CompanyCustomerUK",
  }
]

Impersonating one of these users is now as simple as calling GET <yourHost>/dwapi/users/impersonate?userId=87 With the authorization header as the previous request.

The response will the contain a new token for the specified user

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiI4NyIsIm5iZiI6MTY4NDgyMzA0MCwiZXhwIjoxNjg0ODIzNjQwLCJpYXQiOjE2ODQ4MjMwNDAsImlzcyI6IkR5bmFtaWN3ZWIgQS9TIiwiYXVkIjoiV2ViQVBJIn0.SpwDL7pq2LjcXSoZiaZ3hqdT3nU3X0JE4ZIBzBPlEnA"
}

Content

The content part of the Delivery API enables access to a range of endpoints that will deliver data and content to websites. The endpoints available falls into the following categories;

  • Areas
  • GridRows
  • Navigations
  • Pages
  • PageViews
  • Paragraphs
  • Translations

Areas

Getting all the areas for a given DynamicWeb solution is possible through the area endpoints. It will provide the base for the website depending on which area is being used

GET <yourHost>/dwapi/content/areas/{id}

Returns the specified area with all of its items

{
  "id": 3,
  "name": "Swift B2C demo shop",
  "createdDate": "2021-01-04T15:53:06.073",
  "updatedDate": "2023-04-04T14:10:56.26",
  "item": {
    "fields": [
      {
        "name": "Title",
        "systemName": "Title",
        "value": "Swift"
      },
      {
        "name": "Header desktop",
        "systemName": "HeaderDesktop",
        "value": {
          "pageId": 121,
          "paragraphId": 0,
          "url": "/swift-tools/layout/header/desktop",
          "isExternal": false
        }
      },
      {
        "name": "Header mobile",
        "systemName": "HeaderMobile",
        "value": {
          "pageId": 122,
          "paragraphId": 0,
          "url": "/swift-tools/layout/header/mobile",
          "isExternal": false
        }
      },
      ...
    ],
    "id": "6",
    "systemName": "Swift_Master",
    "pageID": 0,
    "paragraphID": 0
  },
  "languages": [
    {
      "culture": "en-GB",
      "firstPage": {
        "id": 150,
        "name": "Home"
      },
      "id": 3,
      "isCurrent": true,
      "isMaster": true,
      "name": "Swift B2C demo shop",
      "primaryDomain": ""
    },
    ...
  ],
  "ecomCountryCode": "DE",
  "ecomCurrencyCode": "EUR",
  "ecomLanguageId": "LANG1",
  "ecomPricesWithVat": true,
  "ecomShopId": "SHOP1"
}

GridRows

Getting GridRows for specific pages is possible, providing the possibility for all the information to render a page with its columns and paragraphs, it will be provided.

GET <yourHost>/dwapi/content/rows/{pageId}/{device}

Returns the structure of all the rows for the given page based on the device

[
  {
    "id": 192,
    "definition": {
      "id": "1Column",
      "name": "1 Column",
      "description": "1 row with 1 column",
      "template": "1Column.cshtml",
      "columnCount": 1,
      "itemType": "Swift_1ColumnFull",
      "thumbnail": "/Files/Templates/Designs/Swift/Assets/Images/VisualEditor/DW_Row_Full.svg"
    },
    "columns": [
      {
        "columnNumber": 1,
        "rowColumnCount": 1,
        "paragraph": {
          "id": 2269,
          "name": "MyColumn",
          "createdDate": "2021-05-17T08:39:56.04",
          "updatedDate": "2023-02-24T09:06:46.717",
          "pageID": 149,
          "globalID": 0,
          "text": "",
          "item": {
            "fields": [
              {
                "name": "Title",
                "systemName": "Title",
                "value": "MyColumn"
              }
            ],
            "id": "4",
            "systemName": "Swift_ProductCatalogApp",
            "pageID": 149,
            "paragraphID": 2269,
            "link": "/products#2269"
          },
          "image": null,
          "imageFocalX": 0,
          "imageFocalY": 0,
          "imageHAlign": "left",
          "imageVAlign": "top",
        }
      }
    ],
    "templatePath": "\\Designs\\Swift\\Grid\\Page\\RowTemplates\\1Column.cshtml",
    ...
  },
  ...
]

A navigation is a list of links pointing to important areas of a website, often presented as a horizontal bar at the top of every page on a website – but you can have several different types of navigation on a website.

The navigations for specific areas and pages can be fetched from the navigation endpoint

GET <yourHost>/dwapi/frontend/navigations/{areaId}

The areaId is required and the rest of the properties can enable more layers of navigations being returned, such as the full tree. A request such as

GET <yourHost>/dwapi/frontend/navigations/3?ExpandMode=all&StartLevel=1&StopLevel=3&IncludeFoldersAndHidden=true

Will return all level 1 and level 2 navigations regardless of them being hidden or folders.

{
  "nodes": [
    {
      "pageId": 150,
      "groupId": null,
      "name": "Home",
      "link": "/home",
      "level": 1,
      "isClickable": true,
      "inPath": false,
      "isActive": false,
      "showInSitemap": true,
      "showInBreadcrumb": true,
      "showInMenu": true,
      "nodes": []
    },
    {
      "pageId": 149,
      "groupId": null,
      "name": "Products",
      "link": "/products",
      "level": 1,
      "isClickable": true,
      "inPath": false,
      "isActive": false,
      "showInSitemap": true,
      "showInBreadcrumb": true,
      "showInMenu": true,
      "nodes": [
        {
          "pageId": 169,
          "groupId": null,
          "name": "Details",
          "link": "/details",
          "level": 2,
          "isClickable": true,
          "inPath": false,
          "isActive": false,
          "showInSitemap": true,
          "showInBreadcrumb": true,
          "showInMenu": true,
          "nodes": []
        },
        {
          "pageId": 1279,
          "groupId": null,
          "name": "Clothing",
          "link": "/clothing-1",
          "level": 2,
          "isClickable": true,
          "inPath": false,
          "isActive": false,
          "showInSitemap": true,
          "showInBreadcrumb": true,
          "showInMenu": true,
          "nodes": []
        },
        ...
      ]
    },
    ...
  ]
}

Pages

Being able to list all pages out for a given area or all child pages for a page is possible with the pages endpoints. They provide all the specific data about a page, both the option to get a collection of pages for a given area and all their items and additional information. It defines the content structure of a website in backend, which in turn can be used to generate dynamic navigation menus in frontend and as containers for content coming from a paragraph or an item.

GET <yourHost>/dwapi/content/pages/{id}

Returns the specifics of the requested page given the id

{
  "id": 150,
  "name": "Home",
  "createdDate": "2021-01-05T15:07:55.703",
  "updatedDate": "2023-04-17T14:50:28.807",
  "title": "",
  "description": "Description of a page.",
  "keywords": "",
  "areaID": 3,
  "path": [
    {
      "id": 150,
      "name": "Home"
    }
  ],
  "languages": [
    {
      "id": 3,
      "name": "Swift B2C demo shop",
      "culture": "en-GB",
      "primaryDomain": "",
      "isCurrent": true,
      "isMaster": true,
      "page": {
        "id": 150,
        "name": "Home"
      },
      "pageIsHidden": false,
      "pageIsPublished": true,
      "firstPage": {
        "id": 150,
        "name": "Home"
      },
      "firstActivePage": {
        "id": 150,
        "name": "Home"
      }
    },
    ...
  ],
  "item": {
    "fields": [
      {
        "name": "Title",
        "systemName": "Title",
        "value": "Home"
      },
      ...
    ]
  },
  "propertyItem": {
    "fields": [
      {
        "name": "Header position",
        "systemName": "MoveThisPageBehindTheHeader",
        "value": {
          "options": [
            {
              "value": "sticky-top",
              "name": "Normal",
              "icon": "/FilesTemplates/Designs/Swift/Assets/Images/ItemTypes/HeaderFixed.svg",
              "isSelected": true,
              "sortIndex": 0
            },
            ...
          ]
        }
      },
      ...
    ]
  }
}

PageViews

A PageView gives you information about the current page that your module is used in. It gives you access to properties like the page, the areaId, information like style sheets and templates that are effective and it provides access to the current user of the page.

Paragraphs

Paragraphs can be fetched based on pageId, areaId, itemType, and a specific paragraphs id. They're used to define the content structure internally on a page and as containers for content coming from the standard paragraph fields.

GET <yourHost>/dwapi/content/paragraphs/{id}

Returns all the information about a specific paragraph including its items and specific details about the paragraph itself.

{
  "id": 6511,
  "name": "Free Shipping",
  "createdDate": "2022-01-06T15:32:47.323",
  "updatedDate": "2022-09-01T11:05:43.373",
  "pageID": 150,
  "globalID": 0,
  "text": "",
  "item": {
    "fields": [
      {
        "name": "Upload new icon",
        "systemName": "Image",
        "value": [
          {
            "extension": ".svg",
            "name": "truck.svg",
            "path": "/Files/Images/Swift Demo Content/Icons/truck.svg",
            "focalX": 0,
            "focalPositionFromLeft": 50,
            "focalY": 0,
            "focalPositionFromTop": 50,
            "pathUrlEncoded": "%2FFiles%2FImages%2FSwift+Demo+Content%2FIcons%2Ftruck.svg"
          }
        ]
      },
      {
        "name": "Title",
        "systemName": "Title",
        "value": "Free Shipping"
      },
      {
        "name": "Text",
        "systemName": "Text",
        "value": "From all orders over $100"
      },
      ...
    ],
    "id": "1260",
    "systemName": "Swift_Feature",
    "pageID": 150,
    "paragraphID": 6511,
    "link": "/home#6511"
  },
  "image": null,
  "imageFocalX": 0,
  "imageFocalY": 0,
  "imageHAlign": "left",
  "imageVAlign": "top",
}

Translations

Getting translations will return all the basic elements such as labels, buttons, etc. translated to the approrpriate language depending on the culture used. When requesting for the translations, an areaId which will map to a designName can be used, this will default to the areas culture and give the translations as such. There's no authentication required to access these endpoints.

GET <yourHost>/dwapi/translations/area/{areaid}

Response contains a list of all the translations for this area

[
  {
    "key": "Contact",
    "defaultValue": "",
    "value": "Contact"
  },
  {
    "key": "Request for quote",
    "defaultValue": "Request for quote",
    "value": "Request for quote"
  },
  ...
]

If they need to be translated to a specific language, the culture can be sent in to a different endpoint

GET <yourHost>/dwapi/translations/{designname}/{culture}

Using a culture such as da-DK will have the translations as danish

[
  {
    "key": "Contact",
    "defaultValue": "",
    "value": "Kontakt"
  },
  {
    "key": "Request for quote",
    "defaultValue": "Request for quote",
    "value": "Opret en tilbudsførespørgsel"
  },
  ...
]

Some scenarios would require getting a full list of all translation keys and the translations for each. In that case the endpoint needing the designname will provide the data necessary

GET <yourHost>/dwapi/translations/{designname}

Every property in the response object will be a translation key and contain all the data necessary with all the translations it has with each culture.

{
  "Contact": {
    "name": "Contact",
    "scope": "designsLocal",
    "defaultValue": "",
    "translations": {
      "en-GB": {
        "cultureName": "en-GB",
        "value": "Contact"
      },
      "da-DK": {
        "cultureName": "da-DK",
        "value": "Kontakt"
      },
      "nb-NO": {
        "cultureName": "nb-NO",
        "value": "Kontakt"
      },
      "en-US": {
        "cultureName": "en-US",
        "value": "Contact"
      },
      "nl-NL": {
        "cultureName": "nl-NL",
        "value": "Contact"
      }
    },
    "tagDefinitions": []
  },
  "Request for quote": {
    "name": "Request for quote",
    "scope": "designsLocal",
    "defaultValue": "Request for quote",
    "translations": {
      "en-GB": {
        "cultureName": "en-GB",
        "value": "Request for quote"
      },
      "da-DK": {
        "cultureName": "da-DK",
        "value": "Opret en tilbudsførespørgsel"
      },
      "en-US": {
        "cultureName": "en-US",
        "value": "Request for quote"
      },
      "nl-NL": {
        "cultureName": "nl-NL",
        "value": "Vraag om citaat"
      }
    },
    "tagDefinitions": []
  },
  ...
}

Commerce

When using the Commerce endpoints there are some context related information to consider.

Most of the endpoints can return the same data in different contexts that control e.g. prices and languages why most of the endpoints accepts additional parameters - the most important ones being the following:

  • CurrencyCode
  • CountryCode
  • LanguageId

E.g.

GET /dwapi/ecommerce/products/search?GroupId=group5&CurrencyCode=DKK&CountryCode=DK&LanguageId=LANG1

  • CurrencyCode parameter controls the currency of returned prices
  • CountryCode controls the VAT or sales taxes used in price calculations
  • LanguageId controls the language of the returned data, e.g. the name and description of products or name shipping methods

There are other parameters that can affect the price of a product and the calculation of orderline sub totals.

Depending on the setup and prices in the price matrix, prices can differ on the following additional parameters:

  • OrderDate if prices change in periods in time, this parameter can be used
  • StockLocationId when prices change depending on stock location or when creating carts with stock checks in the specified stock location
  • ShopId when prices differs in different channels

User and personalised prices

The logged in user can also affect the prices and discounts applied to products and orders and can affect which products are returned if assortments are used.

Use a valid JWT in a bearer token, see the JWTs section here.

Products

The POST /dwapi/ecommerce/products and GET /dwapi/ecommerce/products/search are essentially the same except one is POST and the other is a GET.

The endpoint has the following search methods:

  • GroupID to return products from a specific product group
  • QueryName to use a pre made query to return products (together with RepositoryName parameter)
  • ProductIds to find a range of products from the specific product ids

Get products by group id

To return products from a group, specify the group id parameter and use a valid group id as the value:

GET /dwapi/ecommerce/products/search?GroupId=group5&CurrencyCode=DKK&CountryCode=DK&LanguageId=LANG1

Get products using a query

To return products using a query, specify the QueryName parameter and use a valid name of a query and also specify the RepositoryName parameter and use the name of the repository of the query:

GET /dwapi/ecommerce/products/search?RepositoryName=Products&QueryName=Products&CurrencyCode=DKK&CountryCode=DK&LanguageId=LANG1

The query is defined in the admin under Settings > System > Repositories. The parameters defined in the query can be used as additional parameters on the endpoint for searching as well.

Say the query has a parameter called ShowActive that is used in an expression on the query:

GET /dwapi/ecommerce/products/search?RepositoryName=Products&QueryName=Products&CurrencyCode=DKK&CountryCode=DK&LanguageId=LANG1&ShowActive=True

Get products using product ids

The product ids is sent in as a list, so the request can look like the following:

GET /dwapi/ecommerce/products/search?ProductIds=10003&ProductIds=10004&CurrencyCode=DKK&CountryCode=DK&LanguageId=LANG1

Result from products endpoint

The return of this endpoint is a product list viewmodel. The list viewmodel contains information about the group, number of products and pages and the product items themselves.

{
  "group": {
    "id": "GROUP5",
    "name": "Road bikes",
    "title": "Road bikes",
    "number": "",
    "description": "",
    "metaDescription": "",
    "sorting": null,
  },
  "subGroups": [],
  "products": [
    {
      "id": "10003",
      "languageId": "LANG1",
      "name": "Scattante CFR Elite",
      ...
    },
    {
      "id": "10004",
      "languageId": "LANG1",
      "name": "Scattante CFR Max",
      ...
    }
    ...
   ],
  "pageSize": 10,
  "pageCount": 2,
  "currentPage": 1,
  "totalProductsCount": 16,
  "sortBy": null,
  "sortOrder": null,
  "spellCheckerSuggestions": null,
  "facetGroups": null
}

These 2 endpoints can take a lot of parameters to control which products to return, which properties of the product view model to return.

Refer to the Swagger docs on /dwapi/docs for options.

Get a single product

To retrieve a single product, add the product id as part of the URL and also specify currency code, country code and language id to get it in the right context:

GET /dwapi/ecommerce/products/10003?CurrencyCode=DKK&CountryCode=DK

Getting a variant of a product also add the variant id as part of the URL:

GET /dwapi/ecommerce/products/10003/VO1.VO2?CurrencyCode=DKK&CountryCode=DK

The product returned:

{
  "id": "10003",
  "variantId": "",
  "languageId": "LANG1",
  "name": "Scattante CFR Elite ",
  "title": "Scattante CFR Elite ",
  "shortDescription": "<p>Representing the upper crust of cycling society, the CFR Elite flaunts a beautiful carbon frame and sophisticated components that&rsquo;ll have you accelerating with speed, power and agility.</p>",
  "longDescription": "<ul>\n<li>Representing the upper crust of cycling society, the CFR Elite flaunts a beautiful carbon frame and sophisticated components that&rsquo;ll have you accelerating with speed, power and agility. Lightweight carbon monocoque frame with carbon/aluminum fork offers a stiff and efficient ride with less vibration</li>\n<li>Shimano Ultegra drivetrain blends race-proven technology with ergonomic perfection to give you a new level of shifting performance</li>\n<li>FSA SLK Light Carbon cranks with MegaExo bottom bracket deliver super-stiff, no-flex pedaling power for the road ahead</li>\n<li>Fort&eacute; Precision handlebar and stem with carbon seatpost supply core-level strength, style and vibration damping performance</li>\n<li>Tektro R740 dual pivot calipers deliver strong, progressive stopping power and good modulation</li>\n<li>Continental Ultra Sport tires provide comfort and exceptional handling, especially on rough road surfaces</li>\n<li>Mavic Aksium wheels are lightweight, responsive and ensure a stable, reliable ride</li>\n</ul>",
  "metaDescription": "",
  "metaTitle": "",
  "metaKeywords": "",
  "number": "10003",
  "created": "2021-05-11T11:07:39",
  "updated": "2024-03-06T13:57:27.773",
  "keywords": "",
  "stockLevel": -2,
  "stockStatus": null,
  "stockDeliveryText": null,
  "stockDeliveryValue": null,
  "weight": 2,
  "productType": "stock",
  "width": 2,
  "height": 2,
  "depth": 2,
  "purchaseMinimumQuantity": 0,
  "purchaseQuantityStep": 0,
  "cost": 1254,
  "ean": "9998877652764353674",
  "expectedDelivery": null,
  "discontinued": false,
  "discontinuedAction": 0,
  "pointPrice": 0,
  "defaultVariantId": "",
  "defaultUnitId": "",
  "variantName": "",
  "active": true,
  "rating": 0,
  "replacementProduct": {
    "productId": "",
    "variantId": ""
  },
  "price": {
    "showPricesWithVat": false,
    "price": 11993.33,
    "priceFormatted": "11.993,33 kr.",
    "priceWithoutVat": 11993.33,
    "priceWithoutVatFormatted": "11.993,33 kr.",
    "priceWithVat": 14991.67,
    "priceWithVatFormatted": "14.991,67 kr.",
    "vat": 2998.34,
    "vatFormatted": "2.998,34 kr.",
    "vatPercent": 25,
    "vatPercentFormatted": "25%",
    "currencyCode": "DKK"
  },
  "priceInformative": {
    "showPricesWithVat": false,
    "price": 0,
    "priceFormatted": null,
    "priceWithoutVat": 0,
    "priceWithoutVatFormatted": null,
    "priceWithVat": 0,
    "priceWithVatFormatted": null,
    "vat": 0,
    "vatFormatted": null,
    "vatPercent": 0,
    "vatPercentFormatted": null,
    "currencyCode": null
  },
  "priceBeforeDiscount": {
    "showPricesWithVat": false,
    "price": 11993.33,
    "priceFormatted": "11.993,33 kr.",
    "priceWithoutVat": 11993.33,
    "priceWithoutVatFormatted": "11.993,33 kr.",
    "priceWithVat": 14991.67,
    "priceWithVatFormatted": "14.991,67 kr.",
    "vat": 2998.34,
    "vatFormatted": "2.998,34 kr.",
    "vatPercent": 25,
    "vatPercentFormatted": "25%",
    "currencyCode": "DKK"
  },
  "discount": {
    "showPricesWithVat": false,
    "price": 0,
    "priceFormatted": "0,00 kr.",
    "priceWithoutVat": 0,
    "priceWithoutVatFormatted": "0,00 kr.",
    "priceWithVat": 0,
    "priceWithVatFormatted": "0,00 kr.",
    "vat": 0,
    "vatFormatted": "0,00 kr.",
    "vatPercent": 0,
    "vatPercentFormatted": "0%",
    "currencyCode": "DKK"
  },
  "productDiscounts": [],
  "prices": [],
  "productFields": {
    "Total_height": {
      "systemName": "Total_height",
      "name": "Total height",
      "type": "Double",
      "value": 0,
      "listType": 0
    }
  },
  "productCategories": {
    "brand_information": {
      "id": "brand_information",
      "name": "Brand information",
      "fields": {
        "Brand_name": {
          "systemName": "Brand_name",
          "name": "Brand name",
          "type": "Text",
          "value": "Scattante",
          "listType": 0
        },
        "Gear_no_of": {
          "systemName": "Gear_no_of",
          "name": "Gear (# of)",
          "type": "Integer",
          "value": 21,
          "listType": 0
        }
      }
    }
  }}
  "groups": [],
  "primaryOrDefaultGroup": {
    "id": "GROUP155",
    "name": "All bikes",
    "sorting": null,
    "primaryPageId": 0
  },
  "variantInfo": {
    "productID": "10003",
    "variantID": "",
    "optionID": null,
    "optionName": null,
    "optionColor": null,
    "optionSort": 0,
    "productName": "Scattante CFR Elite ",
    "productNumber": "10003",
    "productStock": -2,
    "variantInfoGroupId": null,
    "variantInfoGroupName": null,
    "variantInfoGroupDescription": null,
    "variantGroupDisplayType": "nothingSelected",
    "optionImage": null,
    "image": {
      "value": "/Files/Images/missing_image.jpg",
      "name": "Default",
      "keywords": null,
      "displayName": null
    },
    "price": {
      "showPricesWithVat": false,
      "price": 11993.33,
      "priceFormatted": "11.993,33 kr.",
      "priceWithoutVat": 11993.33,
      "priceWithoutVatFormatted": "11.993,33 kr.",
      "priceWithVat": 14991.67,
      "priceWithVatFormatted": "14.991,67 kr.",
      "vat": 2998.34,
      "vatFormatted": "2.998,34 kr.",
      "vatPercent": 25,
      "vatPercentFormatted": "25%",
      "currencyCode": "DKK"
    },
    "priceMin": {
      "showPricesWithVat": false,
      "price": 11993.33,
      "priceFormatted": "11.993,33 kr.",
      "priceWithoutVat": 11993.33,
      "priceWithoutVatFormatted": "11.993,33 kr.",
      "priceWithVat": 14991.67,
      "priceWithVatFormatted": "14.991,67 kr.",
      "vat": 2998.34,
      "vatFormatted": "2.998,34 kr.",
      "vatPercent": 25,
      "vatPercentFormatted": "25%",
      "currencyCode": "DKK"
    },
    "priceMax": {
      "showPricesWithVat": false,
      "price": 11993.33,
      "priceFormatted": "11.993,33 kr.",
      "priceWithoutVat": 11993.33,
      "priceWithoutVatFormatted": "11.993,33 kr.",
      "priceWithVat": 14991.67,
      "priceWithVatFormatted": "14.991,67 kr.",
      "vat": 2998.34,
      "vatFormatted": "2.998,34 kr.",
      "vatPercent": 25,
      "vatPercentFormatted": "25%",
      "currencyCode": "DKK"
    },
    "stock": -2,
    "variantInfo": null
  },
  "defaultImage": {
    "value": "/Files/Images/missing_image.jpg",
    "name": "Default",
    "keywords": null,
    "displayName": null
  },
  "groupPaths": [],
  "imagePatternImages": [],
  "manufacturer": {
    "id": null,
    "name": null,
    "address": null,
    "zipCode": null,
    "city": null,
    "country": null,
    "phone": null,
    "fax": null,
    "email": null,
    "web": null,
    "logo": null,
    "description": null
  },
  "assetCategories": [],
  "neverOutOfstock": false,
  "stockUnits": [],
  "unitOptions": [],
  "relatedGroups": []
}

The amount of fields can be limited by using the FilledProperties option`:

GET /dwapi/ecommerce/products/10003?CurrencyCode=DKK&CountryCode=DK&FilledProperties=Id,Name,Price

Which will make the returned document smaller:

{
  "id": "10003",
  "name": "Scattante CFR Elite ",
  "price": {
    "showPricesWithVat": false,
    "price": 11993.33,
    "priceFormatted": "11.993,33 kr.",
    "priceWithoutVat": 11993.33,
    "priceWithoutVatFormatted": "11.993,33 kr.",
    "priceWithVat": 14991.67,
    "priceWithVatFormatted": "14.991,67 kr.",
    "vat": 2998.34,
    "vatFormatted": "2.998,34 kr.",
    "vatPercent": 25,
    "vatPercentFormatted": "25%",
    "currencyCode": "DKK"
  }
}

One of the returned properties is Price, which is a view model it self with a number of properties. The return of those properties can also be limited by adding an additional parameter.

The amount of fields on the price viewmodel can be limited by using the PriceSettings.FilledProperties option:

GET /dwapi/ecommerce/products/10003?CurrencyCode=DKK&CountryCode=DK&FilledProperties=Id,Name,Price&PriceSettings.FilledProperties=priceWithVatFormatted,priceWithoutVatFormatted

Which will make the returned document even smaller:

{
  "id": "10003",
  "name": "Scattante CFR Elite ",
  "price": {
    "priceWithoutVatFormatted": "11.993,33 kr.",
    "priceWithVatFormatted": "14.991,67 kr.",
  }
}

Carts

Carts are created by using POST /dwapi/ecommerce/carts/create endpoint. The endpoint takes parameters to control currency, VAT and language as described here.

  • CurrencyCode
  • CountryCode
  • LanguageId

A body is required in the POST of the create endpoint, but can be an empty document.

The cart can be created in the following contexts:

  • For anonymous user
  • For the current user
  • For users that the current user can impersonate

For anonymous users, make an anonymous post.

POST /dwapi/ecommerce/carts/create?CurrencyCode=DKK&=DK&LanguageId=LANG1
{
  "customerName": "John Doe"
}

For the current user, pass the JWT as a bearer token to create the cart in the context of the user to respect user specific prices and relate the cart to the user for later retrieval of stored carts. The id of the current user will be set on the cart automatically.

Authorization: Bearer <token>
POST /dwapi/ecommerce/carts/create?CurrencyCode=DKK&=DK&LanguageId=LANG1
{
}

For users that the current user can impersonate, pass the current user JWT as bearer and in the POST body document add the CustomerUserId with the id of user the cart should belong to. The CustomerUserId has to be a user that the current user can impersonate or a 403 will be returned.

Authorization: Bearer <token>
POST /dwapi/ecommerce/carts/create?CurrencyCode=DKK&=DK&LanguageId=LANG1
{
  "CustomerUserId" : 123
}

In the above example the user id that is connected to the JWT is e.g. 100 and that user can impersonate user with id 123 and will create a cart on behalf of user 123.

The return of the POST is an order viewmodel of the newly created cart with its id and its secret which has to be stored in your JS app and used in subsequent requests to the cart endpoints where {secret} is used as identifier.

{
  "id": "CART123",
  "secret": "a480a17e0d9e40a9bd3499be74db1b8f",
  "customerUserId" : 123,
  "deliveryCountryCode": "DK",
  "createdAt": "2024-04-23T18:26:03.2794348+02:00",
  "modified": "2024-04-23T18:26:03.286099+02:00",
  ...
}

To retrieve the cart at any point in time, use the secret in the get:

GET /dwapi/ecommerce/carts/a480a17e0d9e40a9bd3499be74db1b8f

Adding products to cart

To add a product to the cart, a new orderline has to be created using the POST /dwapi/ecommerce/carts/{secret}/items endpoint.

POST /dwapi/ecommerce/carts/a480a17e0d9e40a9bd3499be74db1b8f/items
{
  "ProductId": "10100",
  "ProductVariantId": "",
  "Quantity": 2
}

The endpoint returns 200 when the product is added. To get an updated cart with the new prices, call the GET /dwapi/ecommerce/carts/{secret} endpoint to get the updated instance of the cart.

Update quantity on orderline

To update an orderline and change i.e. the quantity, use the PATCH /dwapi/ecommerce/carts/{secret}/items/{itemId} endpoint:

PATCH /dwapi/ecommerce/carts/a480a17e0d9e40a9bd3499be74db1b8f/items/OL12
{
  "quantity": 12
}

The endpoint returns 200 when the product is added. To get an updated cart with the new prices, call the GET /dwapi/ecommerce/carts/{secret} endpoint to get the updated instance of the cart.

Set shipping method

To set the shipping method of the order, call the PATCH /dwapi/ecommerce/carts/{secret}/shipping/{shippingId} endpoint with a valid cart secret and a valid shipping id. The list of available shippings can be retrieved on the /shippings endpoint.

PATCH /dwapi/ecommerce/carts/a480a17e0d9e40a9bd3499be74db1b8f/shipping/SHIP1

When the shipping method is applied, the price of the cart is updated to reflect the shipping fee.

Set payment method

To set the payment method of the order, call the PATCH /dwapi/ecommerce/carts/{secret}/payment/{paymentId} endpoint with a valid cart secret and a valid payment id. The list of available payments can be retrieved on the /payments endpoint.

PATCH /dwapi/ecommerce/carts/a480a17e0d9e40a9bd3499be74db1b8f/payment/PAY1

When the payment method is applied, the price of the cart is updated to reflect the payment fee.

Complete order

To convert a cart to an order there are the following options:

  • Complete an order after handling payment gateway in your app or if the user pays by invoice
  • Start checkout flow to collect payment from payment gateway

To convert a cart to an order without starting a payment flow make a POST to the createorder endpoint using the cart secret.

POST /dwapi/ecommerce/carts/a480a17e0d9e40a9bd3499be74db1b8f/createorder

After this post, the cart is converted to an order with an order id instead of cart id and complete is set to true - the cart secret remains the same. The cart can no longer be modified and retrieved on the other cart endpoints. The order can now be retrieved on the orders endpoints instead for authenticated users.

To start a checkout flow using a payment gateway, call the checkout endpoint using the cart secret.

The cart has to have a payment method set that has a checkouthandler defined and setup. The checkout handler also has to support headless checkouts.

GET /dwapi/ecommerce/carts/a480a17e0d9e40a9bd3499be74db1b8f/checkout

When this endpoint is called, the cart is converted to an order with an order id (and no longer a cart id), but its complete state is still false. The return of this call is not a regular JSON document. It is the result of passing the order to the checkout handler which will create a payment and return some markup that needs to be injected in your app to start the payment window.

<!--/checkout response-->
<div>
....
<script>//logic to start payment gateway</script>
....
</div>

When the payment flow in the payment window is successfully completed, the payment gateway will make server-to-server callback to the /callback endpoint (you do not have to do this) which will set the order as complete.

TBD: When and how to show receipt

To top