# Get Zalando and Otto product data into Google Sheets

> Pull live Zalando prices and Otto search results into a Google Sheet with a short Apps Script: a custom menu, a daily refresh and no server of your own.

- URL: https://everydata.io/guides/zalando-otto-google-sheets
- Updated: 2026-10-08
- Publisher: everydata.io (https://everydata.io)

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.

## 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](https://everydata.io/login).
- A few Zalando product links (for example from zalando.de) and a search term for Otto.

## 1. Prepare 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.

## 2. Store 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.

## 3. Paste 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.

`Code.gs`:

```javascript
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.

## 4. What comes back

For a Zalando product link the API returns the current product page as data. Shortened documented example:

Example response of `GET /zal/zalando-lookup-product`:

```json
{
  "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:

Example response of `GET /ott/otto-search-by-keyword`:

```json
{
  "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"
    }
  ]
}
```

## 5. Refresh 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.

`Code.gs (add at the end)`:

```javascript
/** 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.

## 6. How 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](https://everydata.io/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

- `GET /zal/zalando-lookup-product`
- `GET /ott/otto-search-by-keyword`

## More

- [All platforms](https://everydata.io/apis) · [Pricing](https://everydata.io/pricing) · [API reference](https://everydata.io/docs) · [Getting started](https://everydata.io/docs/getting-started) · [MCP server](https://everydata.io/docs/mcp) · [Status](https://everydata.io/status)
- Machine-readable: [llms.txt](https://everydata.io/llms.txt), [llms-full.txt](https://everydata.io/llms-full.txt), [OpenAPI](https://api.everydata.io/openapi.json)
