Export 90 days of price history to CSV

Last updated · Changelog · OpenAPI spec · Markdown

Write every recorded price of a list of products over the last 90 days to one CSV: one row per price point, with the product, retailer, date, price, currency and availability. The input file has an identifier header (barcodes, ASINs, model numbers or product URLs).

StepREST endpointMCP toolCredits
History for up to 100 products, as CSVGET /v1/products/offers/history?format=csvproduct_price_history3 per product with offers, plus 90 per request (1 per day of the window)

Cost for N products: 3 × N + 90 for every 100 products. 1,000 products cost 3,000 + 900 = 3,900 credits. A request in which no product has offers is not charged. Shorter windows cost less per request; the longest window is 366 days. See Retrieving Offers With Historical Offer Data.

bash

#!/usr/bin/env bash
# Export the last 90 days of recorded prices for a list of products to one CSV.
# Usage: SHOPSAVVY_API_KEY=... ./history-export.sh skus.csv history.csv
# skus.csv has an "identifier" column: barcodes, ASINs, model numbers or product URLs.
set -euo pipefail
. ./shopsavvy-call.sh

end=$(date -u +%Y-%m-%d)
start=$(date -u -v-90d +%Y-%m-%d 2>/dev/null || date -u -d '90 days ago' +%Y-%m-%d)
: > "$2"
tail -n +2 "$1" | tr -d '\r' | grep -v '^$' | split -l 100 - "$SS_TMP/chunk_"
for chunk in "$SS_TMP"/chunk_*; do
  ids=$(paste -sd, "$chunk" | jq -sRr @uri)
  api GET "/products/offers/history?ids=$ids&start=$start&end=$end&format=csv" > "$SS_TMP/part.csv"
  # A 404 (no product with offers in this chunk) comes back as JSON, not CSV: skip it.
  head -c 1 "$SS_TMP/part.csv" | grep -q '{' && continue
  if [ -s "$2" ]; then tail -n +2 "$SS_TMP/part.csv" >> "$2"; else cat "$SS_TMP/part.csv" >> "$2"; fi
done
echo "Wrote $2 ($start to $end)"

Python

"""Export the last 90 days of recorded prices for a list of products to one CSV.

Usage: SHOPSAVVY_API_KEY=... python history_export.py skus.csv history.csv
skus.csv has an "identifier" column: barcodes, ASINs, model numbers or product URLs.
"""
import csv
import sys
from datetime import date, timedelta

from shopsavvy_call import call


def main(source, destination):
    with open(source, newline="") as f:
        identifiers = [row["identifier"].strip() for row in csv.DictReader(f) if row["identifier"].strip()]
    end = date.today()
    start = end - timedelta(days=90)
    header_written = False
    with open(destination, "w", newline="") as out:
        for first in range(0, len(identifiers), 100):
            params = {"ids": ",".join(identifiers[first:first + 100]), "start": start.isoformat(),
                      "end": end.isoformat(), "format": "csv"}
            response = call("GET", "/products/offers/history", not_found_ok=True, params=params)
            if response.status_code == 404:
                continue  # none of these matched a product with offers: nothing charged
            lines = response.text.splitlines(keepends=True)
            out.writelines(lines if not header_written else lines[1:])
            header_written = True
    print(f"Wrote {destination} ({start} to {end})")


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2])

TypeScript

// Export the last 90 days of recorded prices for a list of products to one CSV.
// Usage: SHOPSAVVY_API_KEY=... npx tsx history-export.ts skus.csv history.csv
// skus.csv has an "identifier" column: barcodes, ASINs, model numbers or product URLs.
import { readFileSync, writeFileSync } from 'node:fs'
import { call } from './shopsavvy-call'

async function main(source: string, destination: string) {
  const identifiers = readFileSync(source, 'utf8').split(/\r?\n/).slice(1).map((line) => line.trim()).filter(Boolean)
  const end = new Date()
  const start = new Date(end.getTime() - 90 * 24 * 60 * 60 * 1000)
  const day = (d: Date) => d.toISOString().slice(0, 10)
  let csv = ''
  for (let first = 0; first < identifiers.length; first += 100) {
    const ids = encodeURIComponent(identifiers.slice(first, first + 100).join(','))
    const response = await call('GET', `/products/offers/history?ids=${ids}&start=${day(start)}&end=${day(end)}&format=csv`, { notFoundOk: true })
    if (response.status === 404) continue // none of these matched a product with offers: nothing charged
    const text = await response.text()
    csv += csv === '' ? text : text.slice(text.indexOf('\n') + 1)
  }
  writeFileSync(destination, csv)
  console.log(`Wrote ${destination} (${day(start)} to ${day(end)})`)
}

main(process.argv[2], process.argv[3])
Was this page helpful?