Public
Configurable Apple refurb stock tracker with multi-store alerts.
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

apple-refurb-tracker

Configurable Apple Certified Refurbished stock tracker. Polls multiple Apple storefronts, detects stock changes, and alerts via email + webhook.

How It Works

cron.ts (every 15 min)
  └─▸ For each store (IE, US, UK):
       ├─ Fetch refurb page
       ├─ Parse REFURB_GRID_BOOTSTRAP tiles
       ├─ Diff against last known state
       ├─ Store events in SQLite
       └─ Dispatch alerts (email + webhook)

main.ts (HTTP API)
  └─▸ /snapshot /history /cadence /dwell /batches /health

File Structure

main.ts          — HTTP API (Hono). Serves stock data and analytics.
cron.ts          — Interval entrypoint. Polls stores, detects changes, alerts.
tracker-lib.mjs  — Core logic: store configs, parsers, schema, alerts.
README.md        — This file.

Setup After Deploy

  1. Set the interval schedule — Open cron.ts in the Val Town editor and set the Cron trigger. Recommended: every 15 minutes (free tier minimum).
  2. Set env vars (optional):
    • API_KEY — Gates the HTTP API. If unset, the API is open.
    • ALERT_WEBHOOK_URL — Webhook URL for Slack/Discord/custom. Only needed if you use channels: ["webhook"] in alert rules.
  3. Customize alert rules — Edit DEFAULT_ALERT_RULES in tracker-lib.mjs to match the specs you care about.

Adding a New Storefront

Add a config object to STORE_CONFIGS in tracker-lib.mjs:

{ id: "de", label: "Apple Germany", region: "DE", baseUrl: "https://www.apple.com", refurbPath: "/de/shop/refurbished/mac", currency: "EUR", vatRate: 0.19, }

No other code changes needed.

Alert Rules

Rules are defined in DEFAULT_ALERT_RULES (in tracker-lib.mjs). Each rule has:

FieldDescription
nameHuman-readable name
matchFilter object — see below
alertOn"appeared", "disappeared", "price_change", or "all"
channels["email"], ["webhook"], or both

Match fields

FieldTypeExample
storeIdsstring[]["ie"] — only IE store
modelstring"MacBook Pro" — substring match
minMemoryGbnumber24
minStorageGbnumber512
screenInchesnumber[][14, 15, 16]
chipstring"M4 Pro" — substring match
maxPriceCentsnumber200000 — price ceiling
partNumberPatternsstring[]["FW*"] — substring match

An empty match: {} catches everything — useful as a catch-all.

Example rules

// Any MacBook appearing → email. { name: "Any MacBook", match: {}, alertOn: "appeared", channels: ["email"] } // 24GB+ MacBook Pro 14/15-inch → email + Slack. { name: "Pro 24GB+", match: { model: "MacBook Pro", minMemoryGb: 24, screenInches: [14, 15] }, alertOn: "appeared", channels: ["email", "webhook"] } // Anything under €1,500 → webhook only. { name: "Budget picks", match: { maxPriceCents: 150000 }, alertOn: "appeared", channels: ["webhook"] }

HTTP API Endpoints

All endpoints require ?key=<API_KEY> if the API_KEY env var is set.

EndpointDescriptionKey params
GET /This README
GET /snapshotCurrent stockstore, model
GET /historyChange eventsstore, part, kind, since, until, limit
GET /cadenceAppearances by hour/daystore
GET /dwellTime-in-stock statsstore
GET /batchesBatch arrivals (3+ items)store, min
GET /healthRun health per store

Environment Variables

VariableRequiredPurpose
API_KEYNoGates HTTP API
ALERT_WEBHOOK_URLNoWebhook URL for alert dispatch