# Export 90 days of price history to CSV

> Export every recorded price for a product list over the last 90 days to one CSV, 100 products per request, with the credit cost worked out up front.

*Source: https://shopsavvy.com/data/documentation/recipe-price-history-export* · Part of the [ShopSavvy Data API documentation](https://shopsavvy.com/data/documentation.md)

---

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).

| Step | REST endpoint | MCP tool | Credits |
|------|---------------|----------|---------|
| History for up to 100 products, as CSV | `GET /v1/products/offers/history?format=csv` | `product_price_history` | 3 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](https://shopsavvy.com/data/documentation/offers-history).

## bash

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

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

```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])
```
