Emergency situation

In case of emergencies or breakdowns, you can send an SMS to our emergency hotline

On-call phone (SMS only)

+45 29 70 15 95

Send an SMS with the following information:

  • Your name and webshop
  • Description of the problem

Notes: This service is only for critical situations where your webshop is down or has serious problems. For regular support, please use our normal support channels.

Headless API

Build your own storefront on Shoporama

Catalog, shopping cart, discount codes, checkout, payment, and customer account. The store's own policies regarding prices, inventory, shipping, and payment apply throughout the entire process.

Base URL

https://din-shop.dk/api

If the store sells in multiple countries, enter the address for that country, for example https://din-shop.dk/en/api. Next are the language, currency, prices, and countries of delivery.

Session and Login

The shopping cart, checkout, and customer login use cookies, just like in the online store. Calls from the browser using credentials: 'include'. The customer is logged in to both the API and the store at the same time.

The shopping cart, checkout, and login only work when your storefront is running on the store’s primary domain (Web Store → Domain) or a country-specific domain. The domains listed under "Multiple Domains" forward all requests to the primary domain, including calls to /api. The catalog, pages, and menus can be accessed from all domains.

Answers and Errors

All answers follow the same format. If there is an error, the reason is stated in error.details.reason, so you can show your own text to the customer.

{
  "success": false,
  "error": {
    "code": 400,
    "message": "Payment method not selected",
    "details": { "reason": "payment_missing" }
  }
}

Every error has a cause. The ones you'll typically need to handle are: empty_basket, stock, sold_out, requires_variant, product_unavailable, max_per_order, customer_input, min_order_value, delivery_country, address_missing, shipping_invalid, payment_missing, payment_not_available, payment_link_failed, order_in_progress, voucher_not_found, voucher_expired, points_invalid, login_failed, login_required and not_found.

The limit is 300 calls per minute per session. After that, the API returns a 429 response. The store also has a limit per IP address for all requests. If this limit is exceeded, the IP address is blocked for one hour with a 429 response as plain text rather than JSON, so cache the directory and content if your server retrieves them.

The API does not include a version number in the URL. New fields may be added, so ignore any fields that your code does not recognize.

Products and Content

List products

GET /api/products?limit=20&offset=0&category_id=5

Only items that are available online and whose brand the customer is allowed to see. With category_id The items will appear in the category's own sorting order. limit is at most 250, and if it is not specified, the shop's page width is used. ?gtin= looks up a barcode.

{
  "success": true,
  "data": {
    "products": [
      {
        "id": 232167,
        "name": "Terra Nova Etiopien Guji, hele bønner 1 kg",
        "price": 249,
        "old_price": null,
        "buyable": true,
        "url": "https://din-shop.dk/terra-nova-etiopien-guji-hele-boenner-1-kg",
        "rewrite_url": "terra-nova-etiopien-guji-hele-boenner-1-kg",
        "images": [...],
        "categories": [...],
        "brand": { "id": 445, "name": "Terra Nova", "url": "https://dinbutik.dk/brands/terra-nova" }
      }
    ],
    "pagination": {
      "total": 32,
      "limit": 20,
      "offset": 0,
      "pages": 2
    }
  }
}

Prices include sales tax. Names, texts, and addresses are in the local language.

brand.url is the brand's page (Link to or the brand's page), if the store has enabled brand pages; otherwise null.

Single product

GET /api/products/232167
{
  "success": true,
  "data": {
    "id": 232167,
    "name": "Terra Nova Etiopien Guji, hele bønner 1 kg",
    "description": "<p>En let og blomstret kaffe ...</p>",
    "sku": "TN-BON-01",
    "price": 249,
    "vat_rate": 25,
    "buyable": true,
    "images": [
      {
        "id": 63924,
        "alt": "Pose med Terra Nova Etiopien Guji kaffebønner",
        "formats": {
          "original": "https://...",
          "large": "https://...",
          "medium": "https://...",
          "small": "https://...",
          "thumbnail": "https://..."
        }
      }
    ],
    "attributes": [...],
    "variants": [...],
    "meta": { "title": "...", "description": "..." }
  }
}

Sold-out variants are not included in variants. Each variant has options with the attribute name and value in the country's language, e.g., {"Farve": "Stål"}. Fields that the customer must fill out themselves (e.g., text for engraving) are listed in attributes with "input_mode": "customer".

Product images have full URLs. Images in categories and blog posts have relative URLs, e.g., /cache/6/5/…/kaffe-fit-800x800x90.png, So, put the store's domain first if you're displaying them from a different domain.

Look up an address

GET /api/resolve/kaffeboenner

Explains what a "path" in the shop is, so your storefront can use the shop's own addresses. type is home, category, product, page, blog, blog_list, landing, brand_list, brand, redirect or not_found.

{
  "success": true,
  "data": {
    "type": "category",
    "data": { "id": 19112, "name": "Kaffebønner", ... },
    "products": [...],
    "pagination": {...},
    "blocks": {
      "top": "<section ...>...</section>",
      "bottom": "..."
    }
  }
}

brand_list and brand is available when the store has enabled brand pages and the theme has "supports_brand_pages": true. /brands provides brand_list with the brands (id, name, path, logo, product_count), the letters in letters and the number of occurrences per letter in groups. In more than 24 brands, index sand, and featured has up to 12 brands with logos. If there are more than 300, paged sand, and you pick up one letter at a time with ?letter=a. Without ?letter= is brands and groups empty. /brands/<adresse> provides brand with an introduction (description_a), body text (description_b), meta and breadcrumbs as well as the goods in products with ?p= (0 is the first page, 1 is the second), ?sort= and ?sort_order=. /brand/<adresse> and a brand with a link to the provider redirect. Just like in the store, the winner is /brands and a brand that is available through the store's own redirects.

blocks The Page Builder's blocks for the page are rendered as finished HTML using the theme's own templates. Without blocks, the field is null. If you already have page blocks, add them ?blocks=0.

Menus

GET /api/menus
{
  "menus": [
    {
      "name": "Hovedmenu",
      "location": "top",
      "items": [
        {
          "title": "Kaffe",
          "url": "",
          "type": "menu",
          "children": [
            { "title": "Kaffebønner", "url": "/kaffeboenner", "type": "category", "children": [] }
          ]
        }
      ]
    }
  ]
}

A point of the type menu is a heading for a submenu and has no address.

Shopping Cart

Add to cart

POST /api/basket
Request body. Variants and the customer's own fields are sent in attributes (attribute-id → value):
{
  "product_id": 232156,
  "amount": 1,
  "attributes": { "4301": 33385 }
}

View cart

GET /api/basket
{
  "success": true,
  "data": {
    "items": [
      {
        "line_id": "232156:4301+33385",
        "product_id": "232156",
        "name": "Nordic Roast Stempelkande 1 liter",
        "variant": "Farve: Stål",
        "amount": 1,
        "price": 299,
        "subtotal": 299,
        "in_stock": true,
        "image": "https://..."
      }
    ],
    "subtotal": 299,
    "shipping": 39,
    "total": 338,
    "vat": 67.6,
    "currency": "DKK",
    "shipping_chosen": false
  }
}

These amounts are the store's own calculations, including promotions, discount codes, deposits, and shipping. Display them as they are so the customer sees the price they'll actually pay. The checkout results and discount codes are reflected in the updated shopping cart basket.

If an item is on layaway, the amount is listed in deposit_total (and by mortgage type in deposits). Panten is part of total, but not in subtotal, Then display it as a separate line. The item lists the deposit per unit in deposit (name, amount).

Correct the number or remove a line

PUT /api/basket/{line_id}   DELETE /api/basket/{line_id}
{
  "amount": 3
}

Use the line's line_id, so that the correct variant is selected. Entering 0 removes the row. A product ID selects the first row containing that item.

Discount code

POST /api/voucher/apply   POST /api/voucher/remove
{
  "code": "SOMMER10"
}

The answer is in the shopping cart with the discount. A code that doesn't exist or has expired results in voucher_not_found or voucher_expired.

Pay with points

GET /api/points   POST /api/points/apply   POST /api/points/remove
{
  "points": 500
}

Requires a login and a loyalty program. If the store has one, the shopping cart will have the field points:

"points": {
  "balance": 1000,
  "available": 1000,
  "requested": 500,
  "used": 500,
  "discount": 50,
  "earns": 1290,
  "rate": { "points": 10, "amount": 1 }
}

balance is the customer's balance, and available the number of points the basket can hold according to the store's rules. For a guest, they are null. discount has been removed from the basket total and vat, Then display it as a separate line. earns are the points earned by the order, and rate how many points are worth what amount in the cart's currency. Too many points result in 400 points_invalid with available.

Points are capped at the maximum the cart can hold before the order is placed, and the order deducts them from the balance. Points are earned on the order when it is approved or submitted, depending on the store’s settings.

Checkout

Select the options available to the customer

GET /api/checkout

Displays the shopping cart, shipping methods, payment methods, and countries all at once, along with the addresses the customer has already entered. Only options the customer can actually select: online payment methods available in the customer’s country, and shipping options that match the cart’s weight and the destination country. When logged in, the customer also sees their B2B options, such as invoice.

Step 1: Address

POST /api/checkout
{
  "step": "address",
  "email": "kunde@example.com",
  "phone": "12345678",
  "name": "Karen Holm",
  "company_name": "",
  "address": "Vestergade 12",
  "zipcode": "8000",
  "city": "Aarhus C",
  "country_id": 188,
  "comments": "",
  "newsletter": 0
}

Business customers can send vat_number and ean_number. A different shipping address is sent as delivery_name, delivery_company, delivery_address, delivery_zipcode, delivery_city and delivery_country_id. The country IDs are listed in GET /api/countries.

The answer is missing_fields (missing fields) and delivers_to_country, so you can display it right away. The order is not rejected until it is completed.

Send the full address every time. This step replaces all fields with what you send, including those you omit. If the customer is logged in, the checkout process uses the account’s address and email, and this step does not save a new address to the account until missing_fields is empty.

Step 2: Delivery

POST /api/checkout
{
  "step": "shipping",
  "shipping_id": 227,
  "delivery_shop_id": "2386"
}

After the address step, shipping methods that include a package pickup location have a list in delivery_shops (number, name, street, zip, city). Send the package shop's number such as delivery_shop_id. Any shipping method not on the list will be rejected. If you do not select a pickup location, the store will choose the first one on the list.

Step 3: Payment Method

POST /api/checkout
{
  "step": "payment",
  "payment_gateway_id": 258
}

Step 4: Complete the order

POST /api/checkout
{
  "step": "complete",
  "payment_flow": "storefront"
}

The payment method is the one from step 3. You can also include it here as payment_id. Before the order is created, the API checks the same things as the store's own checkout process: inventory, minimum order amount, delivery country, address, shipping, and payment method. There are two types of responses.

Pay Now (credit/debit card, MobilePay, etc.):
{
  "success": true,
  "data": {
    "payment_redirect": true,
    "payment_url": "/payment",
    "basket_id": 4809
  }
}

Refer the customer to payment_url. With "payment_flow": "storefront" This is the store's own payment page, which redirects to the payment window and works with all of the store's payment methods. Without it, the API provides the payment window’s address for QuickPay, OnPay, Pensopay, Flatpay, Stripe, Mollie, and Nets Easy. Other methods always go through the store’s payment page. The customer is then redirected back to the store’s /thanks.

The order has been created (bank transfer and invoice, and—without the storefront workflow—also a free order):
{
  "success": true,
  "data": {
    "redirect": "/thanks?basket_id=4809&chk=8ab02e10b1a9ec5db4f1fcee7f49fb05",
    "order_id": 15505,
    "webshop_order_id": 4522,
    "chk": "8ab02e10b1a9ec5db4f1fcee7f49fb05",
    "payment_info": "<p>Overfør beløbet til ...</p>"
  }
}

payment_info is the store's own description of the payment method, such as the account number for a bank transfer. webshop_order_id is the order number that the customer sees.

Double-clicking and Payment Failures

  • Calling the customer complete Again, you'll get the same answer as the first time. A new order will not be created.
  • If another request arrives while the order is being created, it waits up to six seconds and then receives the same response. If the order is not ready by then, the API responds 409 order_in_progress.
  • If the payment cannot be initiated, the API responds 502 payment_link_failed, and no order is created.

Receipt and Order

View an order

GET /api/order?basket_id=4809&chk=8ab02e10b1a9ec5db4f1fcee7f49fb05

With the receipt's basket_id or the order number (order_id). chk is an MD5 hash of the order's email address, so that only the customer can view the order. The response includes the prices the customer paid, not the current prices of the items.

{
  "success": true,
  "data": {
    "order": {
      "order_id": "4522",
      "status": "new",
      "paid": false,
      "total": 308,
      "currency": "DKK",
      "customer": { "name": "Karen Holm", "email": "kunde@example.com" },
      "products": [
        { "name": "Terra Nova Etiopien Guji, hele bønner 1 kg", "price": 249, "quantity": 1, "total": 249 }
      ],
      "shipping": { "method": "Postnord omdeling", "price": 59 },
      "payment": { "method": "Bankoverførsel", "info": "..." },
      "totals": {
        "subtotal": 249,
        "shipping": 59,
        "voucher_discount": 0,
        "vat": 61.6,
        "total": 308
      }
    }
  }
}

Customer Account

Log In and Out

POST /api/auth   POST /api/auth/logout
{
  "email": "kunde@example.com",
  "password": "..."
}

Use the same login as in the online store. Once logged in, the customer will see their customer group prices, payment methods, and address pre-filled. An incorrect email address or password will result in 401 login_failed.

The logged-in customer

GET /api/customer
{
  "customer": {
    "id": 2224,
    "email": "kunde@example.com",
    "name": "Karen Holm",
    "address": "Vestergade 12",
    "zipcode": "8000",
    "city": "Aarhus C",
    "country_id": 188,
    "shipping_address": null,
    "points": 120,
    "urls": { "profile": "/user-profile", "orders": "/user-orders", "edit": "/user-edit" }
  }
}

Without logging in, customer null. points is the customer's point balance and null, when the store doesn't have a loyalty program.

Customer Orders

GET /api/orders   GET /api/orders/{ordrenummer}

The list includes the order number, date, status, total, currency, and the address of the order page. A single order has the same fields as GET /api/order. Without logging in: 401 login_required.

Account Creation and Forgotten Password

Link to the store's own pages /user-sign-up, /user-reset-password and /user-edit. Then the customer will receive a spam check, a welcome email, points for signing up, and verification of their CVR number, just like in the rest of the store.

Shop and Settings

Terms and conditions

GET /api/settings
{
  "success": true,
  "data": {
    "terms": "<p>...</p>",
    "has_terms": true
  }
}

Typically used during checkout, where the customer must accept the terms and conditions before the order is completed.

The Store

GET /api/shop

The store's name and URL, currency, language, default country, and the list of endpoints.

All endpoints

Products

GET /api/products
GET /api/products/{id}
GET /api/categories
GET /api/categories/{id}
GET /api/frontpage
GET /api/search?q={query}

Shopping Cart

GET /api/basket
POST /api/basket
PUT /api/basket/{line_id}
DELETE /api/basket/{line_id}
POST /api/voucher/apply
POST /api/voucher/remove
GET /api/points
POST /api/points/apply
POST /api/points/remove

Checkout

GET /api/checkout
POST /api/checkout
GET /api/shipping
GET /api/payment-methods
GET /api/countries

Orders and Customers

GET /api/order?basket_id={id}&chk={hash}
POST /api/auth
POST /api/auth/logout
GET /api/customer
GET /api/orders
GET /api/orders/{ordrenummer}

Content

GET /api/resolve/{sti}
GET /api/menus
GET /api/pages
GET /api/pages/{id}
GET /api/blog
GET /api/blog/{id}
GET /api/landing-pages

Shop

GET /api/shop
GET /api/settings

A completed example

The "Washington" theme is a complete storefront built using this API, featuring the Page Designer, customer accounts, countries, and all payment methods. See Washington