Turn every Amazon Store Card charge into a receipt.
Your card statement says AMAZON.COM $47.03. Amazon splits one order into several charges (one per shipment) and never shows which items made up which charge. itemized scrapes your own Amazon account nightly, matches each card charge to the exact items behind it, and shows the result as a receipt — plus your current card balance, a running ledger of new charges this cycle, and a Telegram digest when the balance moves.
Self-hosted, single-user, LAN-only. Built for a homelab; no cloud services involved beyond your own Amazon session.
![]() |
![]() |
| Search — ⌘K from anywhere: find an item, jump to the charge that paid for it. | Mobile — the same receipt on a phone. |
Screenshots use generated sample data, not a real account.
The app walks you through it on first load — no config files to edit:
Then 3. Connect signs in for you — showing Amazon's page inline only if it asks to verify — and 4. First sync pulls your history. Install steps are below.
Amazon has no consumer API for orders. What it does have is a logged-in Transactions page (amazon.com/cpe/yourpayments/transactions) that lists every charge made to each of your cards with the order number it belongs to. itemized:
- Runs a headless Chromium (Playwright) with a persistent profile, bootstrapped once by pasting cookies exported from your browser (no password is ever stored).
- Reads the Transactions page newest-first until it reaches charges it already knows, then the details page of every new order (items, prices, tax, shipments, images), and the store card's My Card page (balance, due date, minimum due).
- Matches charges to items with a confidence ladder: single charge → per-shipment sums → cent-exact subset-sum → proportional. Gift-card and cashback-points payments are recognised and kept out of your card totals.
- Serves a React UI: charges timeline with unrolling receipts, statement view mirroring your card statement by billing cycle, item search (⌘K), the Card page (balance + running ledger), sync status, settings.
Syncing is two-tier: a light check (every 30 minutes by default) reads just the Transactions page — the session cookie alone is enough there, so it costs no sign-in and takes about a second — and the moment it spots a new charge it carries on into a full run: item details, card balance, matching, and a Telegram digest. A full run also happens nightly. Order and card pages are what make Amazon demand a fresh password, so they are never touched speculatively.
When Amazon eventually wants a verification step it cannot answer, you get a Telegram alert and finish it in the browser view; the next run picks up where it left off.
server/— Node 24, TypeScript, Express, MySQL 8, Playwright, node-html-parser, node-cron. Pure HTML parsers with sanitized real-page fixtures, so an Amazon redesign fails tests loudly instead of corrupting data.web/— React 19, Vite, TypeScript, Tailwind v4, Framer Motion, TanStack Query, Radix.
On a fresh Ubuntu 24.04 (or Debian 12) box with a normal user that can sudo:
git clone https://github.com/mbrande/amazon_itemized.git itemized
cd itemized
sudo scripts/install.shThat installs Apache, MySQL 8, Node 24 and Chromium's libraries; creates the database and a user with a
generated password; asks a few questions (which network may open the app, its address, optionally your Amazon
sign-in — or leave that for the setup wizard); builds everything; installs a systemd service and an Apache
site that serves /itemized/ and proxies the API, restricted to your LAN; then checks the whole path with a
real request. It ends by printing the URL to open. Finish there: app password → Amazon sign-in → first sync.
Afterwards:
| update | git pull && npm run deploy |
| logs | journalctl -u itemized -f |
| settings | /etc/itemized.env (then sudo systemctl restart itemized) |
| change answers | sudo scripts/install.sh --reconfigure |
| non-interactive | sudo scripts/install.sh --yes (defaults; no Amazon credentials) |
| bring your own MySQL 8 | sudo scripts/install.sh --existing-db |
| remove | sudo scripts/install.sh --uninstall (asks before dropping the database) |
Re-running the installer is safe; it reuses /etc/itemized.env and never rotates the database password on its
own. The generated password and anything typed at a hidden prompt are written only to /etc/itemized.env
(root:<you>, mode 0640) — never printed. MariaDB will not do: the schema uses MySQL 8 collations and JSON
columns. Anything the installer doesn't cover (other distros, a reverse proxy other than Apache) — the manual
steps below are what it automates.
npm install
npx playwright install chromium --workspace server # or: cd server && npx playwright install chromiumCreate a MySQL database + user, then an env file (systemd uses /etc/itemized.env; for dev, server/.env works too):
PORT=8772
DB_HOST=127.0.0.1 DB_USER=itemized DB_PASS=… DB_NAME=itemized
ITEMIZED_STATE_DIR=/home/you/.itemized # browser profile + cookies, chmod 700
ITEMIZED_PUBLIC_URL=http://your-host/itemized # used in alert links
ITEMIZED_ALERT_WEBHOOK= # optional; also editable in Settings
TZ=America/Los_Angeles
AMAZON_EMAIL=you@example.com # optional: the setup wizard can store these instead
AMAZON_PASSWORD=… # read only at sign-in time; never logged or served
AMAZON_TOTP_SECRET= # optional base32 secret if your Amazon 2FA uses an authenticator app
Amazon asks for a fresh password on order and card pages roughly an hour after the last one. With
AMAZON_EMAIL / AMAZON_PASSWORD set, the scraper answers that itself. Anything a script should not answer
(a code sent to your phone, "verify it's you", a captcha) is relayed to Settings → Sign in to Amazon: a live
view of the scraper's own browser you can click and type into, plus a Telegram alert when it is needed.
npm run migrate -w server # apply migrations
npm run dev:server # API on 127.0.0.1:8772
npm run dev:web # Vite on 127.0.0.1:5173 (proxies /itemized/api)Open the app and a setup wizard takes over on first run: it first asks you to set an app password
(the API refuses every request without it — the app holds a signed-in Amazon browser and your entire purchase
history, so it must not sit open on a network), then: enter your Amazon sign-in, press Connect (itemized signs
in and shows you Amazon's page only if a verification step appears), then start the first backfill. Credentials entered
there are written to <ITEMIZED_STATE_DIR>/credentials.env with owner-only permissions; the AMAZON_* environment
variables above are an alternative for deployments that manage secrets outside the app and always take precedence.
Pasting cookies exported with the Cookie-Editor extension still works as a fallback (Settings → Alternative).
Production on this repo's home box: Apache serves web/dist at /itemized/, proxies /itemized/api to the systemd service, and restricts both to the LAN (Require ip). scripts/deploy.sh builds, syncs and restarts. See CLAUDE.md for the full runtime layout and conventions.
n8n/itemized-alerts.json is an importable n8n workflow (Webhook → Telegram). Import it, set your chat ID and credential, activate, and paste the webhook URL into Settings. Payload: { app, level, title, message, url, at }.
- This is unofficial scraping of your own account. Amazon can change the pages at any time; the parser tests and the
parse_failedguard exist for exactly that. - The store card's My Card page may demand a fresh password (Amazon's
max_auth_age=3600policy) after certain account actions; the balance step is best-effort and never fails a run. Never open the card's payment-history link from the scraper — it triggers a bank OTP challenge. - No app-level auth: it relies on network-level restriction. Do not expose it to the internet.
- Never run
npm run seedagainst a database holding real data — it wipes it (it exists for UI development).
MIT






