Source of truth
Every scrape requests https://ring.com/appstore/api/apps with User-Agent: ring-intel/0.1. The response body is stored as raw JSON with a SHA-256 hash. Each app is snapshotted on every successful scrape. There is no Ring SDK, Partner API, or GitHub listing mirror.
What the numbers are
clickUsers30d, accountLinkingUsers30d, and clickToLinkRate30d come from Ring's biMetrics object. They are Ring-reported business-intelligence figures for a trailing 30-day window (see windowEndDate on each app). ring-intel does not invent downloads, installs, active users, or revenue.
What we do not do
- No download or install estimates
- No revenue or ARR models
- No Sensor Tower-style traffic panels built from third-party panels
- No inferred ranking outside the catalog payload
- No Partner API, SDK, or unofficial GitHub catalog
Listing fields
Name, studio, descriptions, features, images, billing, plans, trial eligibility, auth type, and partner identifiers are persisted as Ring returned them. Price labels are formatted from billing.plans[].amountInMinorUnits when the pricing model is paid; otherwise the listing is shown as Free.
Additional observed fields, backfilled from stored raw_json when a column is added:
- deployedAt — Ring's millisecond timestamp on the listing, shown as UTC. This is not ring-intel's first-seen date.
- latestUpdates — Contentful rich text on the listing, flattened to plain text for display.
- authType —
ring_drivenoroauth2as published. - Ring-billed vs 3P — from
billing.thirdPartySubscription.isRing3PBillingOwner.trueis Ring-billed;falseis 3P-billed; missing on listings with no third-party subscription object (typically free). - requiresRingBasePlan — billing flag on the listing.
- Featured — a non-null Contentful
sortOrderis treated as a featured slot (the integer is the slot). - Hidden —
contentfulData.partnerStatus === "hidden". Hidden listings can still appear in the public catalog payload. - partnerVideoUrl — CMS promo video URL, when present.
Privacy declarations
The privacy matrix reads partner-submitted Contentful fields. They are labeled as CMS declarations, not as verified data-handling:
partnerHumanAccessVideos— human video accesspartnerAiTrainingUsesCustomerData/partnerAiTrainingAnonymizesData— AI trainingpartnerGeneratesBiometricData— biometricspartnerGeneratesHealthInsights— healthpartnerGeneratesPreciseGeolocation— geolocationpartnerVideoDataRetention— video retention copypartnerAccessPurpose— access purpose copy
Permissions
Each listing includes a scopes array (name, isMandatory) and optional permissionsRoleId. The permission graph is the bipartite adjacency of live apps to those scope names. Catalog filters accept a scope name. A scope changelog event is written when the normalized scope set changes between scrapes.
Catalog facets
cameraType, deviceType, customerType, and partnerTags are stored as published string arrays and used as catalog filters. Values are not inferred beyond the payload.
Changelog
- first_seen — the app id was not in the local store and appeared in a scrape. This is first observation by ring-intel, not Ring's launch date.
- disappeared — a previously present app id was missing from a later catalog response.
- price — pricing model or plan amounts changed.
- plan — plan identities, names, intervals, or units changed.
- biMetrics — Ring-reported 30d clicks, links, or click-to-link changed.
- copy — name, studio, descriptions, features, tags, or homepage changed.
- scope — the listing's catalog scope set changed.
- companion_joined — a store listing was first joined to this Ring app.
- companion_disappeared — a joined listing 404'd or no longer matches.
- companion — observed version, rating count, or Play install range changed.
Publishers
Publishers are grouped by Ring partnerId. Several Appstore listings can share one partner organization and still use different public studio names.
Companion store listings
ring-intel joins public iOS, Android, and TV store listings to Ring catalog apps. A match is valid only if at least one of:
- the catalog homepage HTML contains an App Store, Play, or tvOS badge URL for that listing
- the iTunes
sellerUrlhost matches the RinghomepageUrlhost, or both sit under a known studio host (erbacciltd.com,allscout.co) - the Play developer page lists the package and the developer is that known studio
- an explicit seed that was re-verified live (404s are dropped)
Titles are not fuzzy-matched. Shared studio homepages do not attach every badge to every SKU: AllScout's five Ring listings share one AllScout Ai binary; an extra PoolAgent listing is joined only to PoolAgent. Ring Cheer Chime is first-party and is never given a third-party companion. First-party Ring store badges that appear on partner homepages (com.ring / Ring Always Home) are ignored.
Collision rejects we keep explicit:
- Porch.com is not Porchify
- Fria
dozzi.fria.aiis not catalog Dozzi (dozzisleep.com) - Arabic/Turkish “Sojur” apps are not
sojur.io
Seeva Online, Porchify, Beside Care, and Sojur stay unmatched unless a domain-joined listing appears. Minut may join when sellerUrl is minut.com, and is labeled a hardware companion, not Ring-specific. Video Wall is labeled TV (tvOS + Google TV), not a phone app.
iTunes Search/Lookup is free and requires no key. We prefer lookup-by-id once an id is known, and back off on HTTP 403. Observed fields: trackName, bundleId, trackId, sellerName, sellerUrl, version, currentVersionReleaseDate, averageUserRating, userRatingCount, trackViewUrl.
Google Play public HTML is parsed for an install range such as 500+, last update, developer name, and the details URL. The range is stored as a range. It is never treated as 500 installs, never turned into a rank, and never compared to Ring accountLinkingUsers30d.
Store-only SKUs seen on a known studio Play developer page (Garage Check, MomentReel, Flashbell, Dangerous Animal Alert, School Bus Home) are persisted as store-only, not in the Ring catalog. They have no invented Ring BI.
Cadence
Production scrape cadence is daily at 8am CT: cron 0 13 * * * hitting /api/cron/scrape. That job also does a cheap iTunes lookup-by-id for known companions when time remains. Play HTML and homepage badge discovery run on POST /api/cron/companions with the same CRON_SECRET. Hobby allows one cron per day; there is no 6-hour companion cron.