Work / PriceTrail

Side project · public · started October 2026

A price monitor that collects, stores and alerts on its own, every day.

PriceTrail tracks game and hardware prices across stores in Brazil and the US. It is the public version of the managed data extraction I do for clients: sources that change, a collector that survives them, and an API on top.

Role
Design, backend, frontend, ops
Stack
Python · PostgreSQL · Next.js · GitHub Actions
Status
In progress · open source soon
Links
Live site and source code coming with the first deploy
pricetrail · collectorlast run · 2026-10-06
19
products tracked
29
offers · 5 sources
97
price observations
−75%Red Dead Redemption 2$59.99 → $14.99

The interface

A catalog, a page per product, and a status page that admits when something broke.

Warm paper, a serif and a mono font, on purpose nothing like this site. Every card shows the latest price per store, the list price when the store published one, and when it was last checked.

PriceTrail catalog: game cards with prices from Steam Brazil and Steam US, on-sale badges and the day of the last check
Catalog · one card per product, one line per store, gold when the store published a list price
PriceTrail product page for Red Dead Redemption 2: price per store with list price and 30-day range, the price history block and the email alert form
Product · price per store, 30-day low and high, history chart once there are two days of data, and the alert form
PriceTrail status page: a table with each source's kind, last run, offers read, 7-day success rate and average time
Status · every source with its last run, success rate over 7 days and average time; a broken collector shows up here

What it does

Small on purpose. Every piece earns its place.

Daily price history
One observation per offer per day, kept forever, with the 30-day low and high on every product.
Two kinds of badge
“On sale” when the store itself published a list price; “↓ −x%” when the price fell below the last different observation. Both can show at once.
Price alerts by email
Pick a store and a target price. You confirm by link, every email has an unsubscribe link, and there are no accounts.
Status page
Each run is recorded per source: status, offers read, duration, error. A store that changed its page shows up before anyone sees a stale price.
Read-only JSON API
/api/products, /api/products/{slug} and /api/status. No key; cached and refreshed after each run.
Two currencies, two countries
Steam in Brazil and the US side by side, so the same game shows in BRL and USD.

How the data is collected

Read each store the way it publishes, never guess.

There is no general-purpose scraper. Each source is a small adapter with one method, fetch(offers), and a parser kept as a pure function so it can be tested offline against saved responses. Four kinds so far:

The rules it follows

  • Only public product data: name, price, list price, availability. No personal data from any store.
  • robots.txt and terms are checked before a source is added; the two Shopify stores state their product pages are crawlable.
  • Identified User-Agent, one request per second, at most one run a day.
  • A product missing from a page or feed is skipped and the run becomes “partial”; a price is never guessed or recorded as “unavailable”.
  • Failures back off exponentially. A source that keeps failing is skipped for the day, never retried in a loop.
  1. Public storefront JSON

    Steam (Brazil, US)

    The store's own price endpoint, batched where it allows, one request per region.

  2. Product pages · schema.org

    Fangamer

    Reads the Product offers from JSON-LD or microdata, so the same adapter works on any store that publishes structured data.

  3. Atom product feed

    8BitDo

    The Shopify-style feed many stores expose at /collections/all.atom: one request for the whole catalog.

  4. Official API

    Mercado Livre

    Client-credentials app registered with the marketplace; the source is skipped while the keys are absent, never scraped.

How it runs

No server to keep alive.

A GitHub Actions cron runs migrate → seed → collect → notify → revalidate. The catalog lives in a TOML file and is seeded into Postgres; the collector writes one price point per offer and one row per run; alerts go out after the run; the site drops its cache last, so it never shows a half-finished day.

pricetrail · how it runsdaily · 09:17 UTC
SteamFangamer8BitDoMercado LivreCollectorPython · ActionsPostgresprice_pointsAPI + siteNext.js · /api/*Price alertsemail
last run29 offers · 4 sources oknext scheduled in

The API

Everything the site shows, you can also fetch.

Products, offers, 30-day lows and the status of every source are read-only JSON, refreshed right after each daily run. It is the same shape I hand to clients: stable and documented.

api · read-only JSON200 OK
$ 

Under the hood

Boring choices, so it keeps running.

Schema
Six tables in plain SQL (products, offers, price_points, runs, sources, alerts), migrations applied in order by the CLI, and a source_status view that feeds both the status page and pricetrail status.
Tests
pytest: parsers run against fixtures saved from each store; collector and alert tests run on the real database in throwaway schemas.
CI
Every push runs pytest and ruff for the collector, tsc and eslint for the site.
Site
Next.js 16 with cached components tagged “prices”; the collector calls /api/revalidate with a secret after each run. The price chart is a server-rendered SVG, one panel per currency.
CLI
pricetrail migrate · seed · collect · notify · status, the same commands the cron runs.

Figures are a snapshot of the project's own database on 2026-10-06.

Your data

Need something like this for your own prices, listings or catalog data?

If it is public on other sites, I can collect it every day, keep it clean and hand it to you as an API or a page your team can use.

Tell me about your project
Back to work