Change Detection
Track store listing changes over time for both tracked and competitor apps.

Overview
Section titled “Overview”AppStoreCat detects changes in app store listings on every sync cycle. When a field changes (title, description, screenshots, etc.), the old and new values are recorded, producing a timeline of how apps evolve their store presence.
Tracked Fields
Section titled “Tracked Fields”| Field | Description |
|---|---|
title | App title changed |
subtitle | App subtitle changed (iOS) |
description | App description changed |
whats_new | Release notes changed |
screenshots | Screenshot set changed |
promotional_text | Promotional text changed (iOS only; always null on Android) |
locale_added | A new supported locale was added |
locale_removed | A supported locale was removed |
How It Works
Section titled “How It Works”- On every sync, the listing content is hashed into a
checksum - If the checksum matches the previous sync, nothing has changed — it is skipped
- If the checksum differs, each field is compared individually
- For each changed field, a
StoreListingChangerecord is created with:- Field name (
field_changed) - Old value
- New value
- Detection timestamp
- Locale (
localecolumn)
- Field name (
Locale Change Detection
Section titled “Locale Change Detection”Beyond individual field changes, AppStoreCat also tracks locale-level changes:
locale_added: when an app starts supporting a new languagelocale_removed: when an app drops support for a language
These are detected by comparing the supported_locales array between syncs.
Tracked App Changes
Section titled “Tracked App Changes”GET /api/v1/changes/apps?field=title&app_id=123&page=2Returns changes for all tracked apps. Optional query params: field (filter by field type), app_id (restrict to a single tracked app the caller owns), page (page cursor).
Competitor Changes
Section titled “Competitor Changes”GET /api/v1/changes/competitors?field=description&app_id=123&page=2Returns changes for all competitor apps. Same optional query params as /changes/apps.
Response Shape
Section titled “Response Shape”Both endpoints return a paginated envelope (PaginatedChangeResponse) rather than a bare array:
{ "data": [ /* ChangeResource[] */ ], "links": { "first": "…", "last": "…", "prev": null, "next": "…" }, "meta": { "current_page": 1, "last_page": 4, "per_page": 50, "total": 183, "…": "…" }, "meta_ext": { "has_scope_apps": true }}meta_ext.has_scope_apps lets the UI distinguish “no changes yet — keep watching” from “you have not tracked any apps yet”.
Go to Changes > Apps or Changes > Competitors to see the change timeline. Each change shows:
- App name and icon
- Changed field
- Old and new values with diff highlighting
- Locale and timestamp
Technical Details
Section titled “Technical Details”- Model:
StoreListingChange - Table:
app_store_listing_changes(thelocalecolumn holds a BCP-47 code) - Indexes:
(app_id, detected_at),(version_id),(app_id, locale),(field_changed) - Sync step:
AppSyncer::syncListing()(field changes),AppSyncer::detectLocaleChanges()(locale changes) - Controller:
ChangeMonitorController - Detection method: Checksum-based (SHA-256 of the combined listing content)