Google Sheets · Apps Script · Zalando · Otto
Get Zalando and Otto product data into Google Sheets
Many price checks start in a spreadsheet: a list of products, the price today, the price last week. Copying those numbers by hand from Zalando or Otto gets old quickly. Google Sheets has a built-in scripting tool, Apps Script, that can fetch data from the web – so the sheet can fill itself.
In this guide you add a small script to a Google Sheet. One button refreshes title, brand, price and availability for a list of Zalando product links; another fills a second tab with Otto search results for a search term. A daily timer keeps the prices current. Everything runs inside Google – you do not need a server or a computer that stays on.
Updated · by everydata.io
What you will have at the end
- A “Zalando” tab that refreshes prices and availability for your product links.
- An “Otto” tab that lists the first page of search results for any search term.
- An everydata menu in the sheet and an optional daily refresh.
What you need
- A Google account and a new Google Sheet.
- A free everydata.io API key – create one here.
- A few Zalando product links (for example from zalando.de) and a search term for Otto.
01Prepare the two tabs
- Rename the first tab to
Zalando. Put these headings in row 1: URL, Title, Brand, Price, Regular price, Availability, Checked at. Paste your Zalando product links into column A from row 2. - Add a second tab called
Otto. Write “Search term” in A1 and your term (for example “highboard”) in B1. Put the headings Product, Brand, Price, Regular price, Rating, Link in row 3.
02Store your API key in the script, not in a cell
Open Extensions → Apps Script. In the editor, go to Project Settings → Script properties and add a property named EVERYDATA_API_KEY with your key as the value. Anyone you share the sheet with can see its cells, but not the script properties.
03Paste the script
Replace the contents of Code.gs with the script below and save. refreshZalando sends each product link to the Zalando endpoint and writes the result next to it. searchOtto sends the search term to the Otto search endpoint and writes one row per product.
const API = "https://api.everydata.io";
/**
* One GET request to everydata.io. The key lives in the script properties, not in the sheet.
* Waits when rate limited (429) and retries a failed page load (5xx) once; other errors come back as { error }.
*/
function apiGet(path, params) {
const key = PropertiesService.getScriptProperties().getProperty("EVERYDATA_API_KEY");
const query = Object.keys(params)
.map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(params[k]))
.join("&");
let retried = false;
for (let attempt = 0; attempt < 4; attempt++) {
const res = UrlFetchApp.fetch(API + path + "?" + query, {
headers: { "x-api-key": key },
muteHttpExceptions: true,
});
const code = res.getResponseCode();
if (code === 429) {
// Too many requests at once. The answer says how long to wait: "... Try again in 12 seconds."
const wait = res.getContentText().match(/in (\d+) seconds/);
Utilities.sleep(Math.min(wait ? Number(wait[1]) + 1 : 60, 60) * 1000);
continue;
}
if (code >= 500 && !retried) {
retried = true; // 5xx answers are not counted against your quota
Utilities.sleep(5000);
continue;
}
if (code >= 400) return { error: code };
return JSON.parse(res.getContentText());
}
return { error: 429 };
}
/** Sheet "Zalando": product URLs in column A from row 2; writes columns B–G. */
function refreshZalando() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Zalando");
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const urls = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
const rows = urls.map(([url]) => {
if (!url) return ["", "", "", "", "", ""];
const p = apiGet("/zal/zalando-lookup-product", { url: url });
if (p.error) return ["Error " + p.error, "", "", "", "", new Date()];
return [p.productTitle, p.manufacturer, p.price, p.retailPrice, p.warehouseAvailability, new Date()];
});
sheet.getRange(2, 2, rows.length, 6).setValues(rows);
}
/** Sheet "Otto": search term in B1; writes the first page of results from row 4. */
function searchOtto() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Otto");
const keyword = sheet.getRange("B1").getValue();
const data = apiGet("/ott/otto-search-by-keyword", { keyword: keyword, page: 1 });
if (sheet.getLastRow() >= 4) sheet.getRange(4, 1, sheet.getLastRow() - 3, 6).clearContent();
if (data.error) {
sheet.getRange("A4").setValue("Error " + data.error);
return;
}
const rows = (data.searchProductDetails || []).map((p) => [
p.productDescription, p.manufacturer, p.price, p.retailPrice, p.productRating, "https://www.otto.de" + p.dpUrl,
]);
if (rows.length) sheet.getRange(4, 1, rows.length, 6).setValues(rows);
}
/** Adds an "everydata" menu to the spreadsheet. */
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("everydata")
.addItem("Refresh Zalando prices", "refreshZalando")
.addItem("Search Otto", "searchOtto")
.addToUi();
}Reload the spreadsheet: an “everydata” menu appears. The first time you use it, Google asks you to allow the script to connect to an external service – that is the request to everydata.io.
04What comes back
For a Zalando product link the API returns the current product page as data. Shortened documented example:
{
"responseStatus": "PRODUCT_FOUND_RESPONSE",
"responseMessage": "Product successfully found!",
"productTitle": "EVERQUEST TEXAPORE MID - Hikingschuh - dusty olive",
"manufacturer": "Jack Wolfskin",
"url": "https://www.zalando.de/jack-wolfskin-everquest-texapore-mid-hikingschuh-dusty-olive-ja442c00s-n11.html",
"warehouseAvailability": "Auf Lager.",
"retailPrice": 119.95,
"price": 104.97,
"priceSaving": 14.98
}price is what the product costs now, retailPrice the regular price before a discount. For an Otto search, each product in searchProductDetails becomes one row. dpUrl is a path on otto.de, which is why the script puts https://www.otto.de in front of it:
{
"responseStatus": "PRODUCT_FOUND_RESPONSE",
"responseMessage": "Product successfully found!",
"keyword": "highboard",
"resultCount": 5166,
"searchProductDetails": [
{
"productDescription": "LeGer Home by Lena Gercke Highboard Essentials, Sideboard, Kommode, Anrichte, Schrank, Stauraumschrank, Breite: 111 cm, UV lackiert, Push-to-open-Funktion",
"manufacturer": "LeGer Home by Lena Gercke",
"price": 869.99,
"retailPrice": 1109.99,
"productRating": "1.0",
"dpUrl": "/p/leger-home-by-lena-gercke-highboard-essentials-sideboard-kommode-anrichte-schrank-stauraumschrank-breite-111-cm-uv-lackiert-push-to-open-funktion-1658198337/?variationId=2081559634"
}
]
}05Refresh the prices every day
Add this function to the same script, select it in the editor toolbar and press Run once. From then on Google refreshes the Zalando tab every morning, even when the sheet is closed.
/** Run once from the editor: refreshes the Zalando sheet every morning between 7 and 8. */
function installDailyTrigger() {
ScriptApp.newTrigger("refreshZalando").timeBased().everyDays(1).atHour(7).create();
}To keep a history instead of overwriting the prices, copy the Price column into a new column (or a second tab) before each refresh.
06How many requests this uses
Each Zalando product lookup counts as 2 requests against your monthly quota; an Otto search counts as 1. As an example, a daily refresh of one Zalando product is about 60 requests a month, so the free plan of 100 requests is enough to try it out. For longer lists, see pricing.
Avoid calling the API from a custom cell formula such as =ZALANDO(A2). Sheets recalculates formulas on its own schedule, which can repeat requests you did not intend to make. A menu item or a timer runs exactly when you decide.
Endpoints used in this guide
Product details by URL · Zalando API
Search products by keyword · Otto API
What else you can do with this data
Any zalando.de or zalando.co.uk product page as JSON: title, brand, seller, price, markdown, images, materials and every size and colour with stock.
Any otto.de product page as JSON: title, manufacturer, price, retailPrice, priceSaving, stock line, images, specs and full variation matrix. Realtime.
Try it with your own data
The free plan includes 100 requests a month, usable on every platform – enough to follow this guide end to end.