Track award flight mileage costs across AMEX Membership Rewards transfer partners. Get notified when prices drop below your target thresholds.
MileageHawk monitors award flight pricing from Austin/Dallas to 30+ international destinations across 17 AMEX transfer partner airlines. It scrapes daily availability data from Seats.aero, normalizes prices to AMEX Membership Rewards points, and alerts you when deals appear.
- Daily price tracking across Economy Plus, Business, and First Class cabins
- AMEX points normalization — see the true cost in Membership Rewards points (accounting for transfer ratios like 1:1, 5:4, 1:1.6)
- Deal scoring — prices rated from Fair to Unicorn based on 30-day averages and regional thresholds
- Configurable alerts — get notified via Email (Resend) or SMS (Twilio) when prices drop below your target
- Route browser with SVG world map visualization and typeahead search
- Price history charts (30/60/90 day) with per-airline breakdown
- Route detail pages with full price comparison tables across all airlines
| Airline | Program | Ratio | Seats.aero |
|---|---|---|---|
| Aer Lingus | AerClub | 1:1 | - |
| Aeromexico | Aeromexico Rewards | 1:1.6 | Yes |
| Air Canada | Aeroplan | 1:1 | Yes |
| Air France/KLM | Flying Blue | 1:1 | Yes |
| ANA | Mileage Club | 1:1 | - |
| Avianca | LifeMiles | 1:1 | Yes |
| British Airways | Executive Club | 1:1 | - |
| Cathay Pacific | Asia Miles | 5:4 | - |
| Delta | SkyMiles | 1:1 | Yes |
| Emirates | Skywards | 5:4 | Yes |
| Etihad | Etihad Guest | 1:1 | Yes |
| Iberia | Iberia Plus | 1:1 | - |
| JetBlue | TrueBlue | 5:4 | Yes |
| Qantas | Frequent Flyer | 1:1 | Yes |
| Qatar Airways | Privilege Club | 1:1 | Yes |
| Singapore Airlines | KrisFlyer | 1:1 | Yes |
| Virgin Atlantic | Flying Club | 1:1 | Yes |
- Framework: Next.js 16 (App Router, Server Components)
- Language: TypeScript
- Database: PostgreSQL via Prisma ORM
- UI: Tailwind CSS + shadcn/ui
- Charts: Recharts
- State: TanStack React Query
- Validation: Zod
- Notifications: Resend (email), Twilio (SMS)
- Data Source: Seats.aero Pro API
- Testing: Vitest (57 tests)
- Node.js 18+
- PostgreSQL database (local or cloud via Neon/Supabase)
- Seats.aero Pro API key ($9.99/month)
-
Clone and install
git clone <repo-url> cd mileage-hawk npm install --legacy-peer-deps
-
Configure environment
cp .env.example .env # Edit .env with your database URL, API keys, etc. -
Set up database
npm run db:generate # Generate Prisma client npm run db:push # Push schema to database npm run db:seed # Seed airlines, airports, and routes
-
Run development server
npm run dev
See .env.example for all variables. Required ones:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
SEATS_AERO_API_KEY |
Yes | Seats.aero Pro API key |
CRON_SECRET |
Yes | Secret for authenticating cron endpoints |
NEXTAUTH_SECRET |
For auth | NextAuth.js session secret |
RESEND_API_KEY |
For email | Resend API key for email alerts |
TWILIO_ACCOUNT_SID |
For SMS | Twilio SID for SMS alerts |
TWILIO_AUTH_TOKEN |
For SMS | Twilio auth token |
TWILIO_PHONE_NUMBER |
For SMS | Twilio sender number |
6:00 AM CT - Scrape prices from Seats.aero API
|
6:30 AM CT - Aggregate prices (min/avg/max by route+airline+cabin)
|
7:00 AM CT - Evaluate alerts (compare thresholds, send notifications)
- Airline — 17 AMEX transfer partners with ratios
- Airport — 36 airports (3 origins + 33 destinations)
- Route — Origin-destination pairs (~90 routes)
- DailyMileagePrice — Raw scraped prices with deduplication
- PriceHistory — Aggregated daily min/avg/max
- User — User accounts (NextAuth)
- UserAlert — Alert configurations (route, cabin, threshold, channels)
- AlertHistory — Alert trigger log with notification status
- ScrapeLog — Scraping job history and metrics
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/routes |
List routes with filters |
| GET | /api/routes/[id]/prices |
Current prices for a route |
| GET | /api/routes/[id]/history |
Price history for charts |
| GET | /api/prices/best-deals |
Top deals with scoring |
| GET | /api/prices/search |
Full price search with pagination |
| GET | /api/airlines |
AMEX partner list |
| GET/POST | /api/alerts |
List/create alerts (auth required) |
| PUT/DELETE | /api/alerts/[id] |
Manage alerts (auth required) |
| POST | /api/cron/scrape-prices |
Daily scrape (CRON_SECRET) |
| POST | /api/cron/aggregate-prices |
Daily aggregation (CRON_SECRET) |
| POST | /api/cron/check-alerts |
Alert evaluation (CRON_SECRET) |
| GET | /api/health |
Health check with data freshness |
| Path | Description |
|---|---|
/ |
Dashboard — stats, quick search, best deals |
/routes |
Route browser — map, filters, grouped cards |
/routes/[slug] |
Route detail — price table, history chart |
/deals |
Best deals — filterable grid |
/airlines |
AMEX partner directory |
/alerts |
Alert management — toggle, delete |
/alerts/new |
Alert wizard — 4-step creation flow |
/settings |
User settings |
npm run dev # Development server
npm run build # Production build
npm run start # Production server
npm run lint # ESLint
npm run test # Run tests (Vitest)
npm run test:watch # Tests in watch mode
# Database
npm run db:generate # Generate Prisma client
npm run db:push # Push schema to DB
npm run db:migrate # Run migrations
npm run db:seed # Seed reference data
npm run db:studio # Open Prisma Studio
npm run db:reset # Reset database- Connect your GitHub repo to Vercel
- Set all environment variables in the Vercel dashboard
- Vercel auto-detects Next.js and builds
- Cron jobs are configured in
vercel.json:- Scrape:
0 12 * * *(12:00 UTC = 6:00 AM CT) - Aggregate:
30 12 * * *(12:30 UTC) - Alerts:
0 13 * * *(13:00 UTC)
- Scrape:
npm run test57 unit tests covering:
- AMEX transfer ratio calculations
- Deal scoring algorithm
- Rate limiting logic
- Zod validators (alerts, search)
- Reference data integrity (airlines, airports, thresholds)
- CRON_SECRET protects all cron endpoints
- Rate limiting on alert creation (10/min) and search (30/min)
- Security headers via middleware (X-Content-Type-Options, X-Frame-Options, etc.)
- Zod validation on all API inputs
- Auth guards on alert CRUD endpoints
- Cache-Control headers on price/route GET endpoints
Private.