Self-hosted App Store keyword tracker for indie iOS developers.
Tracks where your apps rank for a set of keywords across any of Apple's 175 App Store storefronts, snapshots the top results, and remembers everything — so you can see real ASO trends instead of guessing from this-week-only screenshots. A daily chart-position watchdog fires a browser notification the moment one of your apps enters, moves in, or exits a top-free category chart anywhere it's published. Runs entirely on your Mac: a Vapor service stores history in SQLite, a Svelte dashboard renders it, and a menu-bar app supervises the whole thing. You own the data and the schedule — no $50/mo subscription, no abandoned web app, no spreadsheet that goes stale within a week.
macOS 13+.
Download the latest DMG: Keywordista-0.1.0.dmg (signed + notarized). Drag Keywordista.app into /Applications and launch — a magnifying-glass icon appears in your menu bar.
Click Open Dashboard → the browser opens http://127.0.0.1:8080/ (auto-picks :8081…:8090 if :8080 is taken). For other versions, see all releases.
If you want to hack on it, you'll also need Swift 5.10+ and Node 18+.
git clone https://github.com/bootuz/keywordista.git
cd keywordista
make open-mac-appPrefer no menu-bar app? Run ./keywordista to exec the Vapor server in the foreground at :8080, with data in ./db.sqlite instead of ~/Library/Application Support/Keywordista/.
┌──────────────────────────────────┐
│ Keywordista.app (menubar shell) │
│ ─ Picks a free port (8080–8090) │
│ ─ Spawns + supervises the server │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Vapor server (Swift, 127.0.0.1 only) │
│ ├ REST API under /api/v1 │
│ ├ Static Svelte SPA on / │
│ ├ Keyword refresh @ 03:00 UTC (Queues) │
│ └ Chart watchdog @ 04:00 UTC (Queues) │
└───────┬──────────────────────────┬───────────────┘
▼ ▼
┌──────────────────┐ ┌──────────────────────────────┐
│ SQLite (Fluent) │ │ Apple iTunes endpoints │
│ + append-only │ │ ─ /search (keyword rank) │
│ rank history │ │ ─ /lookup (app metadata) │
│ + chart_event │ │ ─ /rss/topfreeapplications │
│ audit log │ │ (chart-watchdog source) │
└──────────────────┘ └──────────────────────────────┘
│
▼
SPA polls /chart-events ──► new Notification(...)
- Append-only history. Each refresh writes a
RankCheckrow. Consecutive identical observations dedupe into a single row withfirstSeenAt/checkedAtso stable ranks don't bloat the DB but the timeline still tells you exactly when something changed. - Storefront-aware everywhere. Apple's 175-territory list is the source of truth. The Add keyword modal picks countries from a searchable multi-select with chips; the chart watchdog uses per-app availability probing so it only polls storefronts where each app actually ships.
- Chart-position watchdog. Daily poll of Apple's free RSS top-free feed for each watched app's primary genre. Emits
entered/moved/exitedevents (Sources/App/Services/ChartTrackerService.swift); a browser-side polling loop firesnew Notification(...)when something happens. Silent until something actually charts — most days are empty, which is correct. - No auth, polite worker. Server binds to
127.0.0.1only — anything that could reach it can already read the SQLite file directly. One job at a time, ~1 req/sec to iTunes; well below Apple's edge-throttling threshold.
make help # full target catalog
make dev-backend # Vapor on :8080
make dev-web # Vite on :5173 (proxies /api → :8080)
swift test # Swift Testing suite
cd web && npm run check # svelte-check + tscThree layers: the Vapor server in Sources/App/, the Svelte 5 + Tailwind SPA in web/, and the SwiftUI MenuBarExtra app in mac/. Build outputs land in Public/ (SPA) and mac/Keywordista.app/ (menu-bar bundle).
Cutting a signed + notarized DMG: see .github/RELEASING.md. Tagged pushes (app-v*, service-v*) run the same release flow in CI.
Everything under /api/v1. No auth, 127.0.0.1-only. Five categories: apps, keywords, dashboard, charts, settings.
Full endpoint table
| Method | Path | What |
|---|---|---|
GET |
/health |
Liveness probe (the menu-bar app pings this) |
POST |
/apps |
Add a watched app — { appStoreId, lookupCountry } |
GET |
/apps |
List watched apps |
DELETE |
/apps/:id |
Remove an app (cascades history) |
POST |
/apps/:id/availability/refresh |
Re-probe a single app's 175 storefronts |
POST |
/keywords |
Add a tracked keyword — { term, countryCode } |
GET |
/keywords |
List keywords |
DELETE |
/keywords/:id |
Remove a keyword (cascade) |
POST |
/keywords/:id/refresh |
Enqueue one immediate refresh |
POST |
/refresh-all |
Enqueue refresh for every keyword |
GET |
/refresh-status |
{ pending } — queued job count |
GET |
/dashboard |
Dashboard table — one row per (keyword, app) |
GET |
/keywords/:id/history?watchedAppId=… |
Full rank history for one (keyword, app) pair |
GET |
/chart-positions |
Currently-charted snapshot rows |
GET |
/chart-events?since=&limit= |
Append-only watchdog activity feed (SPA polls this) |
POST |
/charts/refresh |
Kick the chart watchdog immediately |
GET |
/settings/{asc,asa} |
Read App Store Connect / Apple Search Ads credential status |
PUT/DELETE |
/settings/{asc,asa} |
Update / clear those credentials |
GET |
/version |
{ current, latest, updateAvailable, downloadUrl } |
See requests.http for ready-to-run examples.
- Apple Search Ads popularity scores. v1 derives
difficultyandentryBarrierfrom search results alone. ASA credentials slot in at/api/v1/settings/asais wired, just no fetcher consuming it yet. - Chart-position history / sparklines. The watchdog stores only the latest position per
(app, country, chart, genre); achart_position_historytable is the v2 follow-up. - Auto-updates of the menu-bar app itself. The server can update independently (it pulls service tarballs from GitHub Releases), but bumping the
.appversion still means downloading a new DMG. - Linux / Windows. Vapor runs everywhere;
Keywordista.appis macOS-only. Linux users can clone +./keywordistafrom source.
Bug reports and PRs welcome — see CONTRIBUTING.md. The ASO scoring heuristic in KeywordScorer.swift is a documented best-effort approximation; sharper formulas with citations are explicitly invited.
Found a vulnerability? See SECURITY.md.
Apache License 2.0. © 2026 Astemir Boziev.
