Documentation

With "Add to delelisten" your customers put your products on their shared delelisten.com shopping list. On the list, each product shows your shop name and a link back to the product page.

You need a free developer account and an app to get your keys. The examples below use placeholders.

1. How it works

  1. The customer presses "Add to delelisten" on your page or in your app.
  2. A delelisten.com window opens. If needed, the customer opens their My Lists there (PIN or social login), picks one of their lists and confirms. Nothing is added without that click.
  3. The products land on the list with your shop name and a link to the product. The window tells your page whether something was added and how many, and nothing more.

You have two keys per app:

Most web shops only need the button. Use the server API when your server should put a whole cart together, or when you want to check afterwards whether the customer added it.

2. The button on your web shop

Load the script once per page, then mark any button or link with data-delelisten. An empty button gets the delelisten look and the text "Add to delelisten" in the visitor's language.

<script src="https://delelisten.com/app/public/button.js" data-key="pk_YOUR_PUBLIC_KEY" defer></script>

<button data-delelisten data-name="Organic milk 1 l" data-quantity="2"></button>

<!-- several products at once, e.g. a recipe or a cart -->
<button data-delelisten
  data-items='[{"name":"Spaghetti 500 g","url":"https://myshop.com/p/11"},{"name":"Tomatoes","quantity":"6"}]'>
  Put the recipe on my list
</button>
AttributeWhat it does
data-nameThe product as it should read on the list (required, max 200 characters).
data-quantityFor example 2 or 500 g (max 20 characters).
data-urlLink to the product. Default: the page the button is on. It must be on your shop address or one of the domains in your app settings, otherwise the link is left out (the product is still added).
data-noteA short note (max 120 characters).
data-itemsSeveral products as a JSON array of {"name", "quantity", "url", "note"}, at most 20.
data-keyThe public key, if it is not on the script tag (for example with several apps on one page).
data-labelYour own text for an empty button.
data-styleThe look of an empty button: button (default: green with the delelisten bag), outline (white with a thin border), icon (only the delelisten bag logo, like a wish-list heart; the text shows as a tooltip and is read out by screen readers) or none (just the text, for your own CSS). A button with your own content is never restyled.
data-sizeWith data-style="icon": the logo size in pixels (16 to 96, default 28).
data-returnWhere the delelisten window goes afterwards (https on your domains). Usually not needed on the web: the window just closes.

The bag shows a + until the customer has added the product; then it turns into the check mark of the delelisten logo, and the icon's tooltip says "Added to delelisten". The text follows the language of your page (<html lang="da">), otherwise the visitor's browser.

<!-- Only the logo, next to your wish-list heart -->
<button data-delelisten data-style="icon" data-name="Smeg 50's Style kettle"></button>

<!-- White button with a border -->
<button data-delelisten data-style="outline" data-name="Smeg 50's Style kettle"></button>

Buttons added to the page later (for example in a product grid loaded by script) are picked up with Delelisten.init(). A link with the class delelisten-add works as well as data-delelisten.

3. The recipe button for food blogs

Put it next to a recipe. Your readers get every ingredient on their shopping list, choose how many people they cook for (all amounts change with it), and untick what they already have. Salt, pepper and water start unticked. Ingredients already on their list add up: 3 eggs from your recipe and 2 eggs on the list become 5 eggs. On the list, each ingredient links back to your recipe.

<script src="https://delelisten.com/app/public/button.js" data-key="pk_YOUR_PUBLIC_KEY" defer></script>

<button data-delelisten data-type="recipe"></button>

That is all for most food blogs: the button reads the recipe from the page itself. WordPress recipe plugins (WP Recipe Maker, Tasty Recipes, Mediavine Create and others), and most recipe sites, already add the recipe for search engines in the standard schema.org Recipe format, with recipeIngredient and recipeYield. The button uses the name, the servings and the ingredient lines from there.

No recipe data on your page? Give the ingredients on the button, one per line, or as a JSON array:

<button data-delelisten data-type="recipe"
  data-title="Pancakes" data-servings="4"
  data-ingredients='["2 dl flour", "3 eggs", "4 dl milk", "1 pinch of salt", "Butter for frying"]'></button>
AttributeWhat it does
data-type="recipe"Makes it a recipe button: a bowl instead of the bag, and the text "Add ingredients to delelisten" in the visitor's language.
data-ingredientsOptional. The ingredient lines as you write them in the recipe ("500 g flour", "1 onion, chopped"), at most 60. Amounts, units and remarks are understood in 13 languages.
data-title, data-servingsOptional. The recipe's name, and how many people it is for. Without servings, the amounts can't be changed.
data-recipeOptional. With several recipes on one page: the name of the recipe this button belongs to.

data-style, data-label, data-size and data-key work as on the shop button. From your own script: Delelisten.addRecipe() (the recipe on the page) or Delelisten.addRecipe({ title, servings, ingredients: [...] }). The server API takes a recipe too (see below).

<!-- Only the bowl icon, e.g. next to "Print" and "Pin" -->
<button data-delelisten data-type="recipe" data-style="icon"></button>

4. The cart button

For the cart page of a web shop: the whole cart goes on the customer's list in one go, up to 60 products, each with a tick box so they can leave out what they don't need. Products already on their list add up.

<button data-delelisten data-type="cart"
  data-items='[{"name":"Organic milk 1 l","quantity":"2","url":"https://myshop.com/p/123"},
               {"name":"Rye bread","url":"https://myshop.com/p/456"}]'></button>

It shows a cart instead of the bag and the text "Add my cart to delelisten". If your cart changes on the page, call it from your own script instead: Delelisten.addCart(products). For very large carts (long product names and links), the server API is safer: a link in the browser can only be so long, so the button leaves out product links first and then the last products.

5. From your own JavaScript

// After button.js has loaded:
Delelisten.add({ name: "Organic milk 1 l", quantity: "2", url: "https://myshop.com/p/123" });
Delelisten.add([{ name: "Spaghetti 500 g" }, { name: "Tomatoes", quantity: "6" }]);

// When the customer is done (the event bubbles up from the button that was pressed):
document.addEventListener("delelisten:done", (event) => {
  if (event.detail.status === "added") {
    showMessage(event.detail.count + " product(s) are now on your delelisten list");
  }
  // event.detail.status is "added" or "cancelled"
});

Call Delelisten.add directly from a click handler, so the browser allows the window. If pop-ups are blocked, delelisten opens in a new tab instead.

The button only builds a link. You can build it yourself, for example in an app, an e-mail or a QR code on a shelf label:

https://delelisten.com/app/public/add.php?k=pk_YOUR_PUBLIC_KEY&n=Organic%20milk%201%20l&q=2&u=https%3A%2F%2Fmyshop.com%2Fp%2F123
ParameterMeaning
kYour public key (required).
n, q, u, noteName, quantity, product link and note of one product.
itemsInstead of n/q/u: several products as a JSON array, encoded as base64url (URL-safe base64 without =). Add type=cart for a whole cart.
recipeInstead of products: a recipe as base64url JSON {"t": title, "s": servings, "u": recipe link, "l": ["500 g flour", …]}.
returnWhere to go afterwards: https on your domains, or an address that starts with one of your app's return addresses (for example myshop://delelisten). delelisten adds delelisten=added&count=2 or delelisten=cancelled&count=0.

Encode every value (spaces as %20, not +).

7. The server API

Address: https://delelisten.com/app/public/shop-api.php. Send the secret key in the header X-Api-Key (or Authorization: Bearer sk_live_…). Answers are JSON in UTF-8.

Check your key

GET https://delelisten.com/app/public/shop-api.php?r=ping
-> 200 {"ok": true, "app": {"name": "My Shop", "domains": ["myshop.com"]}}

Create an add request

Your server sends the products; you get a link that is valid for 24 hours. Send the customer to that link (redirect, new window or in-app browser). The customer picks a list and confirms; the link then cannot be used again.

POST https://delelisten.com/app/public/shop-api.php?r=add-requests
Content-Type: application/json
X-Api-Key: sk_live_…

{
  "items": [
    {"name": "Organic milk 1 l", "quantity": "2", "url": "https://myshop.com/p/123", "note": "the blue one"},
    {"name": "Rye bread"}
  ],
  "return_url": "https://myshop.com/cart",
  "reference": "cart-981"
}

-> 201
{
  "id": "ar_…",
  "status": "pending",
  "url": "https://delelisten.com/app/public/add.php?r=ar_…",
  "item_count": 2,
  "added_count": 0,
  "reference": "cart-981",
  "created_at": "2026-10-11T09:30:00+00:00",
  "expires_at": "2026-10-12T09:30:00+00:00",
  "completed_at": null
}

items is required (1 to 20 products, each with a name; a whole cart: up to 60). return_url and reference (your own id, max 100 characters) are optional. If a product link is not on your domains, it is left out and the answer has a warnings list.

For a recipe, send recipe instead of items. The customer then ticks the ingredients and chooses the servings on delelisten; item_count is the number of ingredient lines.

{
  "recipe": {
    "title": "Pancakes",
    "servings": 4,
    "url": "https://myfoodblog.com/pancakes",
    "ingredients": ["2 dl flour", "3 eggs", "4 dl milk", "1 pinch of salt"]
  },
  "return_url": "https://myfoodblog.com/pancakes"
}

Check an add request

GET https://delelisten.com/app/public/shop-api.php?r=add-requests&id=ar_…
-> 200 {"id": "ar_…", "status": "added", "added_count": 2, ...}

status is pending, added, cancelled or expired. Requests are kept for 30 days.

Examples

<?php
// Cart page: "Put my cart on my delelisten list"
$secret = getenv('DELELISTEN_SECRET');            // sk_live_…  (never in a web page or app)

$body = [
    'items' => [
        ['name' => 'Organic milk 1 l', 'quantity' => '2', 'url' => 'https://myshop.com/p/123'],
        ['name' => 'Rye bread', 'url' => 'https://myshop.com/p/456'],
    ],
    'return_url' => 'https://myshop.com/cart',    // where the customer comes back to
    'reference'  => 'cart-981',                     // your own id, optional
];

$ch = curl_init('https://delelisten.com/app/public/shop-api.php?r=add-requests');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($body),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'X-Api-Key: ' . $secret],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
]);
$answer = json_decode((string) curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status === 201) {
    header('Location: ' . $answer['url']);        // the customer picks a list, then returns
    exit;
}
error_log('delelisten: ' . ($answer['error']['message'] ?? 'no answer'));

// Back on https://myshop.com/cart?delelisten=added&count=2
if (($_GET['delelisten'] ?? '') === 'added') {
    echo (int) $_GET['count'] . ' products are now on your delelisten list.';
}

8. Mobile apps

Apps use the add link with the public key (or a link your server got from the server API). Open it in the system's secure browser sheet, not in a WebView: there the customer is already signed in to delelisten. Register your return address (for example myshop://delelisten) under "Return addresses" in the app settings, so delelisten can send the customer back to your app.

import AuthenticationServices
import UIKit

/// Opens delelisten in a secure browser sheet. The customer stays signed in to delelisten (shared with Safari).
final class DelelistenButton: NSObject, ASWebAuthenticationPresentationContextProviding {
    static let publicKey = "pk_YOUR_PUBLIC_KEY"
    static let scheme = "myshop"                 // register "myshop://delelisten" under Return addresses
    private var session: ASWebAuthenticationSession?
    private weak var window: UIWindow?

    func add(name: String, quantity: String = "", productURL: String = "", from window: UIWindow?,
             done: @escaping (_ added: Int) -> Void) {
        var c = URLComponents(string: "https://delelisten.com/app/public/add.php")!
        c.queryItems = [
            URLQueryItem(name: "k", value: Self.publicKey),
            URLQueryItem(name: "n", value: name),
            URLQueryItem(name: "q", value: quantity),
            URLQueryItem(name: "u", value: productURL),
            URLQueryItem(name: "return", value: "\(Self.scheme)://delelisten"),
        ]
        c.percentEncodedQuery = c.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
        self.window = window
        session = ASWebAuthenticationSession(url: c.url!, callbackURLScheme: Self.scheme) { url, _ in
            // url = myshop://delelisten?delelisten=added&count=1  (nil when the customer closed the sheet)
            let items = url.flatMap { URLComponents(url: $0, resolvingAgainstBaseURL: false)?.queryItems } ?? []
            let added = items.first { $0.name == "delelisten" }?.value == "added"
            done(added ? Int(items.first { $0.name == "count" }?.value ?? "0") ?? 0 : 0)
        }
        session?.presentationContextProvider = self
        session?.prefersEphemeralWebBrowserSession = false
        session?.start()
    }

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        window ?? ASPresentationAnchor()
    }
}

9. Errors and limits

Errors come as {"error": {"code": "…", "message": "…"}} with a matching HTTP status.

StatusCodeMeaning
401missing_keyNo key in the request.
401wrong_keyYou sent the public key; the server API needs the secret key.
401invalid_keyUnknown secret key (maybe replaced by a new one).
403not_activeThe app is paused, or the account is waiting for approval or paused.
400bad_jsonThe body is not JSON.
422no_items, too_many_itemsNo product with a name, or more than 20 (60 for a whole cart).
422no_ingredientsA recipe without any ingredient line that could be read.
422bad_return_urlThe return address is not https on your domains and not one of your return addresses.
400 / 404bad_id, not_foundUnknown add request.
429rate_limitedToo many requests. Wait a minute.

Limits: 20 products per add, 600 API requests per minute per app, 10 apps per account. A list holds at most 500 products; when it is full, fewer are added and the count says how many.

10. Rules and privacy