Owlmetry

Store Ratings & Reviews

Per-country App Store rating aggregates from iTunes Lookup + individual customer reviews from the App Store Connect API.

Owlmetry surfaces two independent App Store data feeds — aggregate ratings and individual reviews — pulled by separate jobs from different Apple endpoints. Both are distinct from the in-app Feedback system: store data comes from external users via Apple, not from your SDKs.

What Gets Captured

For every Apple app with a bundle_id, Owlmetry tracks two things:

Per-country aggregate ratings (app_store_ratings, daily snapshot from iTunes Lookup, no auth):

  • Average rating + total rating count, per Apple storefront (~247 ISO2 country codes)
  • Current-version rating + count, per storefront
  • Snapshot date (one row per app × store × country × day — both "current state" and history live in the same table)
  • Per-country rating_count_delta (change in rating_count since the previous daily snapshot — null on first sight of a country, negative on tombstones), and a worldwide worldwide_rating_count_delta summed across countries with prior data
  • A worldwide rollup is denormalized back onto the apps table (worldwide_average_rating, worldwide_rating_count, worldwide_current_version_rating, worldwide_current_version_rating_count, ratings_synced_at) so dashboard cards stay one-row-read fast

Individual reviews (app_store_reviews, populated via your ASC integration):

  • Star rating (1–5)
  • Title (Apple only — Play has no title)
  • Body
  • Reviewer handle (App Store nickname)
  • Country code (the storefront the review was left in, mapped from ASC's ISO 3166 alpha-3 territory codes)
  • Developer response + timestamp + state (PUBLISHED | PENDING_PUBLISH) when present
  • ASC's customerReviewResponses.id (so we can DELETE the reply later)
  • The Owlmetry user who submitted the reply via Owlmetry (null for replies that arrived via sync — i.e. created in ASC's web UI directly — or that came through an agent key)
  • When the review was posted in the store
  • When Owlmetry ingested it

How It Works

JobCadenceSourceWhat it does
app_version_synchourly :15 (system)iTunes Lookup API (no auth)Refreshes latest_app_version + the numeric Apple App Store ID for every Apple app with a bundle_id.
app_store_ratings_syncdaily 04:30 UTC (system)iTunes Lookup API (no auth)Fans out across every Apple storefront for each Apple app with a bundle_id and snapshots per-country average rating + total count to app_store_ratings, then recomputes the worldwide cache on apps.
app_store_connect_reviews_syncdaily 05:30 UTC (system)App Store Connect APIFans out across every project with an active App Store Connect integration and pulls individual reviews via GET /v1/apps/{id}/customerReviews for every Apple app in those projects that has an apple_app_store_id.

The reviews job runs daily and can also be triggered manually for a single project — manual runs hit the same handler, scoped to one project. Manual triggers from Dashboard → Integrations → Sync reviews, via owlmetry integrations sync app-store-connect --project-id <id>, or via the sync-integration MCP tool. Each project still requires its own active App Store Connect integration to be included in the daily fan-out.

Aggregate ratings keep flowing automatically once an Apple app has a bundle ID — no integration setup needed. ASC is only required for individual review ingestion. You can also manually trigger a ratings sync (e.g. right after creating a new app) via owlmetry ratings sync --project-id <id> / Dashboard → project → Sync ratings / sync-app-ratings MCP.

Notifications on new ratings & reviews

Both jobs fan out to every team member via the notification dispatcher:

  • app_store_ratings_sync dispatches an app.rating_changed notification for each app whose worldwide rating count increased between runs — i.e. new ratings have appeared on the App Store. The first sync for an app (no prior baseline) is suppressed so onboarding doesn't flood inboxes.
  • app_store_connect_reviews_sync dispatches an app.review_new notification for each app where at least one new review was inserted on this run and the app already had reviews on file. The body includes the count and a snippet of the newest inserted review.

Per-channel toggles (in-app, email, mobile push) live under each user's notification preferences. app.rating_changed defaults to in-app + mobile push (email off); app.review_new defaults to all three channels on.

Storefront removal (tombstones)

If an app was previously available in a region but iTunes returns nothing for that storefront on a later sync — manual delisting, Apple takedown, or a transient hiccup — the job writes a tombstone row (average_rating IS NULL, rating_count = 0) for that day. The latest-snapshot view (DISTINCT ON (country_code) … ORDER BY snapshot_date DESC) returns the tombstone so the UI correctly shows "no longer available". If iTunes returns data again on the next run, the tombstone is naturally superseded — tombstones self-heal.

Setting Up the App Store Connect Integration

Each Apple Developer account needs its own integration row — ASC keys can't be shared across accounts. If you have apps from multiple Apple Developer teams, you'll need to set up a separate integration in each project that holds them.

  1. In App Store Connect → Users and Access → Integrations → App Store Connect API, click "+" on the Individual Keys tab.
  2. Pick the Customer Support role (least-privilege for read-only review access; the Reply-to-Review write scope can come later).
  3. Download the .p8 file (you only get one chance — Apple won't let you re-download).
  4. Copy the Issuer ID (top of the page) and Key ID (10-character alphanumeric, also embedded in the .p8 filename like AuthKey_ABC1234567.p8).
  5. In Owlmetry, go to Dashboard → Integrations → App Store Connect → Set up, paste all three (Issuer ID, Key ID, full .p8 contents).
  6. Click Test connection to confirm Apple accepts the key — it returns the list of apps visible to the key. If your Owlmetry app's bundle ID isn't in the list, the key was generated under the wrong Apple Developer team.
  7. Click Sync reviews. Every sync paginates through all reviews Apple has on file (200 at a time, newest-first) and reconciles — anything that vanished from ASC since the last run is hard-deleted locally.

Schemas + idempotency: the app_store_reviews table has a unique index on (app_id, store, external_id) so re-runs never insert duplicates. Reviewer-side fields (rating, title, body) are immutable per Apple, so the upsert leaves them alone on conflict. The developer_response_* fields are re-synced on every run though — replies authored or edited or deleted directly in ASC's web UI flow back into Owlmetry on the next sync. After pagination + reconciliation, the job also runs a refresh pass over local rows still marked PENDING_PUBLISH: each is GET'd directly via /v1/customerReviewResponses/{id} because Apple's customerReviews?include=response payload doesn't reliably surface the PENDING_PUBLISH → PUBLISHED state flip. A 404 on that GET means Apple removed the response externally → local developer_response_* fields are cleared. The pass exposes a pending_responses_checked counter on the job result, visible via prod:jobs. App Store Connect is the single source of truth — there is no local hide/un-hide. If a review disappears from ASC (Apple takedown, reviewer deletion, moderation), the next sync drops it from Owlmetry too. Partial sync failures (transport errors, rate-limit aborts) skip the reconciliation step and the refresh pass for the affected app so an incomplete view never wipes real rows.

Where to See Them

Ratings (per-country, incl. star-only ratings — not just text reviews):

  • Web dashboard: /dashboard/projects/<id>★ 4.6 (1,234) badge on each Apple app card showing the worldwide rollup, plus a "Ratings by country" disclosure that lazy-loads the per-country grid. The reviews page has a "By country" panel covering every app in the project. Daily deltas (+8 since yesterday) appear next to counts on the dashboard, the Reviews "By country" panel, and the per-country grid wherever a prior snapshot exists.
  • CLI: owlmetry ratings list <appId> --project-id <id>, owlmetry ratings by-country --project-id <id>, owlmetry ratings sync --project-id <id> (admin-only manual trigger).
  • MCP: list-app-ratings, list-ratings-by-country, sync-app-ratings.

Reviews (individual customer reviews with text):

  • Web dashboard: /dashboard/reviews — list view with filters (app, store, rating, country, has-developer-response, full-text search). The detail dialog has the full reply round-trip.
  • CLI: owlmetry reviews list, owlmetry reviews view <id>, owlmetry reviews respond <id>, owlmetry reviews delete-response <id> --yes.
  • MCP: list-reviews, get-review, respond-to-review, delete-review-response.

Replying to App Store Reviews

You can reply to an Apple review directly from the dashboard, CLI, MCP, or the iOS companion app — Owlmetry calls App Store Connect's customerReviewResponses endpoint, persists the resulting reply ID + state, and shows it on every surface so you can see at a glance which reviews you've already responded to.

Where the reply is sent: directly to App Store Connect. It becomes publicly visible on the App Store listing once Apple publishes it (usually within minutes). Owlmetry shows PENDING_PUBLISH until the next sync confirms Apple has published it — that happens on the next daily run or any manual trigger. If Apple deletes the reply on their side (e.g., via the ASC web UI), the next sync clears the local fields too.

Editing: Apple has no PATCH endpoint for review responses. Owlmetry implements edit as DELETE-then-POST against ASC. From your perspective it's a single PUT — the existing reply is replaced atomically.

Deleting: removes the reply from the public App Store listing. Irrecoverable — the only way back is to post a new reply. The dashboard requires you to type delete to confirm, the CLI requires --yes, the iOS app uses a destructive alert, and the MCP tool description leads with a destructive-action warning.

Limits: 5,970 characters maximum (Apple's responseBody limit, surfaced as MAX_REVIEW_RESPONSE_LENGTH in @owlmetry/shared/constants).

Role required: the App Store Connect API key needs Customer Support role or higher. If Apple returns 403, the server surfaces a role-hint message — rotate the key in Dashboard → Integrations → App Store Connect.

Permissions:

  • Replies are gated on reviews:write. Both users and agent keys with that permission can reply, edit, and delete a reply (matches the user's flow when an agent triages public reviews via MCP).
  • The responded_by_user_id column attributes user-submitted replies. Agent-submitted replies leave it null.

Replies that arrived from outside Owlmetry (created in ASC's web UI before this feature, or by another team member directly in ASC): the daily sync ingests them with the response body but without a developer_response_id, so Owlmetry can't DELETE them directly. Re-posting through Owlmetry's PUT route works — it'll fall through to the POST path. To delete one via Owlmetry, sync the integration first to pick up the response ID Apple returns.

Copying the Integration to Other Projects

If multiple projects under the same team contain apps from the same Apple Developer team, you can clone the credentials in one click. Dashboard → Integrations on the target project → "Copy from another project" → pick the source. Owlmetry duplicates the issuer/key/p8 verbatim and runs a live listAppStoreConnectApps call to confirm Apple still accepts the credentials. The source project's integration is unchanged.

CLI: owlmetry integrations copy app-store-connect --from <source-project-id> --to <target-project-id>. MCP: copy-integration.

Permissions

  • reviews:read — list / view ratings or reviews (default for new agent keys; ratings reuse the same permission)
  • reviews:write — trigger a manual ratings sync (admin role) and post / edit / delete a reply via Apple (users and agent keys both allowed for the reply round-trip)
  • integrations:write — required to set up / update / sync the App Store Connect integration

When Data Doesn't Show Up

Ratings missing:

  • App platform isn't apple — Play Store rating ingest is deferred (would need a Google Play Console scrape or paid API).
  • App has no bundle_id — required for iTunes Lookup.
  • Daily sync hasn't run yet for a brand-new app — trigger owlmetry ratings sync --project-id <id> to populate immediately.
  • App not for sale in any storefront iTunes returns data for — every (app, country) lookup returned resultCount: 0.

Reviews missing:

  • No App Store Connect integration on the project — the sync errors out with "App Store Connect integration not found or disabled".
  • App's apple_app_store_id hasn't been resolved yet — wait for the next hourly app_version_sync tick (or trigger it manually via the Jobs API for the specific app).
  • auth_error on sync — the .p8 was revoked in App Store Connect, the issuer/key IDs are mismatched, or clock skew. Test Connection from the dashboard surfaces the precise Apple error message.
  • App not visible to the key — the Apple Developer team that owns the key doesn't have access to that app. ASC keys are scoped to a single team.

Ready to get started?

Connect your agent via MCP or CLI and start tracking.