{"openapi":"3.1.0","info":{"title":"App Pain API","version":"0.1.0","description":"Unified iOS + Android app store data with LLM pain-point mining. Reviews, metadata, version history and cross-store search for ANY app — built for competitive research and agent workflows.","contact":{"name":"App Pain API","url":"https://app-pain-api.quiet-delta.workers.dev"}},"servers":[{"url":"https://app-pain-api.quiet-delta.workers.dev"}],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"components":{"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key"},"ApiKeyQuery":{"type":"apiKey","in":"query","name":"api_key"}},"parameters":{"store":{"name":"store","in":"path","required":true,"schema":{"type":"string","enum":["ios","android"]}},"appId":{"name":"appId","in":"path","required":true,"description":"iOS numeric App Store id or Android package name","schema":{"type":"string"},"examples":{"ios":{"value":"835599320"},"android":{"value":"com.zhiliaoapp.musically"}}},"country":{"name":"country","in":"query","schema":{"type":"string","default":"us"},"description":"ISO 3166-1 alpha-2, lowercase"},"refresh":{"name":"refresh","in":"query","schema":{"type":"string","enum":["1"]},"description":"Pro keys only: bypass the cache read and force a fresh fetch (still writes cache). Free keys get 403 refresh_requires_pro — caches refresh automatically."}}},"paths":{"/v1/watchlist":{"get":{"summary":"Your competitor watchlist and alert settings","responses":{"200":{"description":"OK"}}},"post":{"summary":"Watch an app (Starter: 2 apps weekly, Pro: 15 apps daily)","description":"Body: {\"appId\": \"<store URL | iOS id | Android package>\", \"store\"?: \"ios|android\", \"webhookUrl\"?: \"https://...\"}. Digests of new 1-2★ reviews and new/rising pain points are pushed to the webhook (Slack and Discord webhooks are formatted natively; any other URL gets JSON).","responses":{"201":{"description":"Added"},"403":{"description":"upgrade_required / watchlist_full"}}}},"/v1/watchlist/{store}/{appId}":{"delete":{"summary":"Stop watching an app","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"}],"responses":{"200":{"description":"Removed"}}}},"/v1/watchlist/webhook":{"put":{"summary":"Set (or clear with null) the alert webhook","description":"Body: {\"url\": \"https://hooks.slack.com/...\"}","responses":{"200":{"description":"OK"}}}},"/v1/watchlist/test":{"post":{"summary":"Send a sample digest (last 7 days) to your webhook now","responses":{"200":{"description":"Delivered"}}}},"/v1/usage":{"get":{"summary":"Your plan and this month's usage","description":"Tier, calls / new painpoint analyses / premium deep-dives used this month vs your plan limits (null = unlimited), reset time and — for paid keys — when the key expires after a cancellation. Not counted as a call.","responses":{"200":{"description":"OK"}}}},"/v1/apps/{store}/{appId}":{"get":{"summary":"App metadata","description":"Name, developer, category, price, rating histogram. Cached 24h.","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"},{"$ref":"#/components/parameters/country"}],"responses":{"200":{"description":"OK"}}}},"/v1/apps/{store}/{appId}/reviews":{"get":{"summary":"Reviews (unified schema, both stores)","description":"50 reviews/page, up to 10 pages. Cached 6h.","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"},{"$ref":"#/components/parameters/country"},{"name":"page","in":"query","schema":{"type":"integer","default":1,"maximum":10}},{"name":"sort","in":"query","schema":{"type":"string","enum":["recent","helpful"],"default":"recent"},"description":"Android: both. iOS: best-effort (store default order)"},{"$ref":"#/components/parameters/refresh"}],"responses":{"200":{"description":"OK"}}}},"/v1/apps/{store}/{appId}/painpoints":{"get":{"summary":"Pain-point mining (LLM map-reduce clustering)","description":"Fetches reviews, filters negative ones (rating <= maxRating), clusters complaints into ranked pain points with evidence quotes. Cached 7 days.","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"},{"$ref":"#/components/parameters/country"},{"name":"maxRating","in":"query","schema":{"type":"integer","default":3,"maximum":5},"description":"Reviews with rating <= this are treated as pain signal"},{"name":"reviews","in":"query","schema":{"type":"integer","default":300,"minimum":1,"maximum":500},"description":"How many reviews to analyze — rounded up to a tier of 100, 300 or 500 (tiers share caches)"},{"name":"model","in":"query","schema":{"type":"string","enum":["glm-5.3-flash"]},"description":"Premium refinement model (pro keys only, ~¥0.4 per analysis, daily budget fuse applies) — finer clusters and cleaner synthesis than the free default"},{"name":"stream","in":"query","schema":{"type":"string","enum":["1"]},"description":"SSE progress stream: 10s heartbeat pings + stage events (fetching-reviews, llm-map, llm-reduce) + a final result event. Recommended for cold analyses — intermediate proxies may cut silent connections at 60s."},{"$ref":"#/components/parameters/refresh"}],"responses":{"200":{"description":"OK"},"503":{"description":"LLM fuse blown (daily budget) or LLM not configured"}}}},"/v1/apps/{store}/{appId}/versions":{"get":{"summary":"Version history with per-version ratings","description":"Android: per-version review stats (version, reviews, avgRating, firstSeen/lastSeen) from up to 900 recent reviews — spot rating crashes after a bad release. iOS: current version + release notes (via lookup); per-version distribution best-effort. Cached 6h.","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"},{"$ref":"#/components/parameters/country"}],"responses":{"200":{"description":"OK"}}}},"/v1/search":{"get":{"summary":"Search apps (both stores)","description":"Competitor discovery. Omit store for merged results. Cached 6h.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"store","in":"query","schema":{"type":"string","enum":["ios","android"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":10,"maximum":30}},{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/refresh"}],"responses":{"200":{"description":"OK"}}}},"/v1/apps/{store}/{appId}/painpoints/history":{"get":{"summary":"Pain-point timeline (archived snapshots)","description":"Historical pain-point snapshots from the archive — theme rise/decay over time. Snapshots land when an app is re-analyzed (~weekly per app). Storage read: does not consume LLM quota or the daily budget fuse. This time series cannot be rebuilt later — store windows only expose recent reviews, so the archive is the only source of history.","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"},{"name":"since","in":"query","schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD inclusive"},{"name":"limit","in":"query","schema":{"type":"integer","default":13,"maximum":52}}],"responses":{"200":{"description":"OK"}}}},"/v1/apps/{store}/{appId}/painpoints/trend":{"get":{"summary":"Pain-point trend (what got worse, what got fixed)","description":"Compares two archived pain-point snapshots and labels every theme new / resolved / rising / falling / stable with share deltas in percentage points. Default window: latest snapshot vs the earliest one within the prior ~30 days. Themes are aligned by title (analyses reuse prior titles for the same issue). Storage read: no LLM quota used.","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"},{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"Baseline: latest snapshot on or before this date (YYYY-MM-DD)"},{"name":"to","in":"query","schema":{"type":"string","format":"date"},"description":"Comparison: latest snapshot on or before this date (default: newest)"}],"responses":{"200":{"description":"OK"}}}},"/v1/apps/{store}/{appId}/reviews/archive":{"get":{"summary":"Archived new reviews by day","description":"Daily archived review deltas (deduplicated against the previous 7 days) plus the merged new-review list, newest first. Coverage starts at archive inception. Storage read only.","parameters":[{"$ref":"#/components/parameters/store"},{"$ref":"#/components/parameters/appId"},{"name":"days","in":"query","schema":{"type":"integer","default":14,"maximum":90}},{"name":"since","in":"query","schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD inclusive"},{"name":"until","in":"query","schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD inclusive"}],"responses":{"200":{"description":"OK"}}}}}}