App Ratings API
Retrieve mobile app ratings from Apple App Store and Google Play Store. Track app performance as alternative data for consumer-facing companies.
The response carries two views of the same records:
| View | Shape | Use it for |
|---|---|---|
data |
One entry per date, carrying the company’s biggest app on each platform | Quick company-level reads, and clients written before apps existed |
apps |
One series per app per platform, most-rated first | Anything quantitative — a company can publish many apps, and which ones matter is your judgement |
Both are derived from the same underlying records, so they never disagree about which app is the biggest on a platform.
Endpoint
Section titled “Endpoint”GET /v2/app-ratings/{symbol}Authentication
Section titled “Authentication”Authenticate using one of the following methods (in order of recommendation):
| Method | Example |
|---|---|
| Bearer token (recommended) | Authorization: Bearer YOUR_API_KEY |
| X-API-Key header | X-API-Key: YOUR_API_KEY |
| Query parameter | ?apiKey=YOUR_API_KEY |
| Legacy query parameter | ?token=YOUR_API_KEY |
Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
symbol |
string | Yes | Stock ticker symbol (e.g., UBER, DASH) |
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
startDate |
string | No | Start date (YYYY-MM-DD) |
endDate |
string | No | End date (YYYY-MM-DD) |
limit |
integer | No | Maximum number of records to return (1-500). A record is one app on one date, so 500 covers a 25-app company for 20 days. Omit it for the most recent window; use startDate/endDate for history |
History begins on 3 September 2026, the day daily per-app collection
started. A date range entirely before that returns an empty data and apps.
An app added to the registry later starts its series on the day it was added
and is never backfilled, so an app’s first observations date is the day
FinBrain began tracking it.
Request
Section titled “Request”from finbrain import FinBrainClient
fb = FinBrainClient(api_key="YOUR_API_KEY")
df = fb.app_ratings.ticker("UBER", date_from="2026-09-01", date_to="2026-09-30", as_dataframe=True)print(df)curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.finbrain.tech/v2/app-ratings/UBER"import requests
url = "https://api.finbrain.tech/v2/app-ratings/UBER"headers = {"Authorization": "Bearer YOUR_API_KEY"}params = {"startDate": "2026-09-01", "endDate": "2026-09-30", "limit": 100}
response = requests.get(url, headers=headers, params=params)data = response.json()
for entry in data["data"]["data"]: # Either side is None when there is no rated app on that store ios = entry["ios"] or {} android = entry["android"] or {} print(f"{entry['date']}: iOS {ios.get('score')}, Android {android.get('score')}")#include <iostream>#include <string>#include <curl/curl.h>#include <nlohmann/json.hpp>
using json = nlohmann::json;
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) { userp->append((char*)contents, size * nmemb); return size * nmemb;}
json get_app_ratings(const std::string& symbol, const std::string& api_key) { CURL* curl = curl_easy_init(); std::string response;
if (curl) { std::string url = "https://api.finbrain.tech/v2/app-ratings/" + symbol;
struct curl_slist* headers = nullptr; std::string auth_header = "Authorization: Bearer " + api_key; headers = curl_slist_append(headers, auth_header.c_str());
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_perform(curl); curl_slist_free_all(headers); curl_easy_cleanup(curl); }
return json::parse(response);}
int main() { auto result = get_app_ratings("UBER", "YOUR_API_KEY");
// Either side is null when there is no rated app on that store for (auto& entry : result["data"]["data"]) { std::cout << entry["date"].get<std::string>() << ": "; if (!entry["ios"].is_null()) std::cout << "iOS " << entry["ios"]["score"].get<double>() << " "; if (!entry["android"].is_null()) std::cout << "Android " << entry["android"]["score"].get<double>(); std::cout << std::endl; }
return 0;}use reqwest::blocking::Client;use serde::Deserialize;use std::error::Error;
#[derive(Debug, Deserialize)]struct IosRating { score: f64, #[serde(rename = "ratingsCount")] ratings_count: i64,}
#[derive(Debug, Deserialize)]struct AndroidRating { score: f64, #[serde(rename = "ratingsCount")] ratings_count: i64, // Play Store does not always publish an install count. #[serde(rename = "installCount")] install_count: Option<i64>,}
// Either side is null when the company publishes no rated app on that store.#[derive(Debug, Deserialize)]struct AppRatingEntry { date: String, ios: Option<IosRating>, android: Option<AndroidRating>,}
#[derive(Debug, Deserialize)]struct AppRatingsInner { symbol: String, name: String, data: Vec<AppRatingEntry>,}
#[derive(Debug, Deserialize)]struct AppRatingsResponse { success: bool, data: AppRatingsInner,}
fn get_app_ratings(symbol: &str, api_key: &str) -> Result<AppRatingsResponse, Box<dyn Error>> { let url = format!( "https://api.finbrain.tech/v2/app-ratings/{}", symbol );
let client = Client::new(); let response: AppRatingsResponse = client .get(&url) .header("Authorization", format!("Bearer {}", api_key)) .send()? .json()?;
Ok(response)}
fn main() -> Result<(), Box<dyn Error>> { let result = get_app_ratings("UBER", "YOUR_API_KEY")?;
for entry in &result.data.data { let ios = entry.ios.as_ref().map(|r| r.score); let android = entry.android.as_ref().map(|r| r.score); println!("{}: iOS {:?}, Android {:?}", entry.date, ios, android); }
Ok(())}const response = await fetch( "https://api.finbrain.tech/v2/app-ratings/UBER", { headers: { "Authorization": "Bearer YOUR_API_KEY" } });const result = await response.json();
for (const entry of result.data.data) { // Either side is null when there is no rated app on that store console.log( `${entry.date}: iOS ${entry.ios?.score ?? "n/a"}, ` + `Android ${entry.android?.score ?? "n/a"}` );}Response
Section titled “Response”Success Response (200 OK)
Section titled “Success Response (200 OK)”{ "success": true, "data": { "symbol": "AAPL", "name": "Apple Inc.", "cik": "0000320193", "data": [ { "date": "2026-09-04", "ios": { "score": 4.89492, "ratingsCount": 8807114 }, "android": { "score": 4.8378, "ratingsCount": 12161409, "installCount": 844211870 } }, { "date": "2026-09-03", "ios": { "score": 4.89492, "ratingsCount": 8804809 }, "android": { "score": 4.8378, "ratingsCount": 12158128, "installCount": 844014248 } } ], "apps": [ { "platform": "android", "appId": "com.shazam.android", "appName": "Shazam: Find Music & Concerts", "observations": [ { "date": "2026-09-04", "score": 4.8378, "ratingsCount": 12161409, "installCount": 844211870 }, { "date": "2026-09-03", "score": 4.8378, "ratingsCount": 12158128, "installCount": 844014248 } ] }, { "platform": "ios", "appId": "284993459", "appName": "Shazam: Find Music & Concerts", "observations": [ { "date": "2026-09-04", "score": 4.89492, "ratingsCount": 8807114, "installCount": null }, { "date": "2026-09-03", "score": 4.89492, "ratingsCount": 8804809, "installCount": null } ] }, { "platform": "ios", "appId": "1160481993", "appName": "Apple Wallet", "observations": [ { "date": "2026-09-04", "score": 4.76624, "ratingsCount": 7382510, "installCount": null }, { "date": "2026-09-03", "score": 4.76624, "ratingsCount": 7380997, "installCount": null } ] } ] }, "meta": { "timestamp": "2026-09-04T15:06:32.888Z" }}Note how data reports Shazam on both stores — Apple’s most-rated app on each —
while apps goes on to Apple Wallet and the rest of the portfolio (148 apps for
Apple at the time of writing). Both arrays are truncated here; observations
runs each app’s full history over the requested date range, one entry per day.
Response Fields
Section titled “Response Fields”| Field | Type | Description |
|---|---|---|
success |
boolean | Whether the request was successful |
data |
object | Response data wrapper |
data.symbol |
string | Stock ticker symbol |
data.name |
string | Company name |
data.cik |
string | null | The company’s SEC Central Index Key, zero-padded to 10 digits. A string, because the leading zeros are part of the identifier. null for an issuer with no SEC registration, such as a non-US listing. Use it to join this dataset to Insider Trading, Corporate Lobbying, Government Contracts and Patent Filings by company: a ticker gets renamed and recycled, a CIK does not |
data.data |
array | Blended view: one entry per date, daily (see below) |
data.apps |
array | Per-app view: one series per app per platform, most-rated first |
meta.timestamp |
string | Response timestamp (ISO 8601) |
App Rating Object Fields (data.data[])
Section titled “App Rating Object Fields (data.data[])”| Field | Type | Description |
|---|---|---|
date |
string | Date of the snapshot (YYYY-MM-DD) |
ios |
object | null | iOS App Store metrics, null when there is no rated iOS app |
ios.score |
number | iOS App Store rating (1-5) |
ios.ratingsCount |
integer | Number of App Store ratings |
android |
object | null | Google Play Store metrics, null when there is no rated Android app |
android.score |
number | Google Play Store rating (1-5) |
android.ratingsCount |
integer | Number of Play Store ratings |
android.installCount |
integer | null | Play Store install count, null when the store does not publish one |
Each entry describes the company’s biggest app on each platform by ratings
count — not a blend of everything it publishes. ios or android is null
when the company publishes nothing on that store, or when its app there is
unrated.
App Series Object Fields (data.apps[])
Section titled “App Series Object Fields (data.apps[])”| Field | Type | Description |
|---|---|---|
platform |
string | ios or android |
appId |
string | null | App Store numeric id or Play Store package name. Every record since the 3 September 2026 restart carries one; null is reserved for a record without an app key and does not occur in served data |
appName |
string | null | App title as published on the store |
observations |
array | That app’s own history, newest first |
observations[].date |
string | Date of the snapshot (YYYY-MM-DD) |
observations[].score |
number | null | Store rating (1-5), null when the app is unrated |
observations[].ratingsCount |
integer | null | Number of ratings |
observations[].installCount |
integer | null | Play Store install count. Always null on ios — Apple publishes no install count |
Working With Multiple Apps
Section titled “Working With Multiple Apps”A company can publish many apps: Apple has over a hundred on iOS alone, and a
retailer typically ships a shopping app, a payments app and a loyalty app under
the same ticker. data answers “how is this company’s flagship app doing”;
apps answers “what does this company publish, and how is each one doing”.
We deliberately publish no blended company score. Weighting a portfolio of apps into one number means making a judgement — by ratings volume, by revenue relevance, by product line — that belongs to you, not to us. Every app arrives with its own series so you can filter and weight it yourself.
from finbrain import FinBrainClient
fb = FinBrainClient(api_key="YOUR_API_KEY")
# Long frame: one row per app per observationapps = fb.app_ratings.ticker("AAPL", as_dataframe=True, per_app=True)
# What does this company publish, and how big is each app?print(apps.groupby(["platform", "app_id", "app_name"])["ratings_count"].max())
# One app's own seriesshazam = apps[apps["app_id"] == "284993459"]import requests
url = "https://api.finbrain.tech/v2/app-ratings/AAPL"headers = {"Authorization": "Bearer YOUR_API_KEY"}
payload = requests.get(url, headers=headers).json()["data"]
for app in payload["apps"]: latest = app["observations"][0] # newest first label = app["appName"] or "unidentified app" print(f"{app['platform']:<8} {label:<30} " f"{latest['score']} ({latest['ratingsCount']} ratings)")const response = await fetch( "https://api.finbrain.tech/v2/app-ratings/AAPL", { headers: { "Authorization": "Bearer YOUR_API_KEY" } });const { data } = await response.json();
for (const app of data.apps) { const latest = app.observations[0]; // newest first console.log( `${app.platform} ${app.appName ?? "unidentified app"}: ` + `${latest.score} (${latest.ratingsCount} ratings)` );}Interpretation
Section titled “Interpretation”| Rating | Quality |
|---|---|
| 4.5 - 5.0 | Excellent |
| 4.0 - 4.5 | Good |
| 3.5 - 4.0 | Average |
| 3.0 - 3.5 | Below average |
| Below 3.0 | Poor |
Errors
Section titled “Errors”| Code | Error | Description |
|---|---|---|
| 400 | Bad Request | Invalid symbol |
| 401 | Unauthorized | Invalid or missing API key |
| 403 | Forbidden | Authenticated, but not authorized to access this resource |
| 404 | Not Found | Symbol not found |
| 429 | Too Many Requests | Rate limit exceeded — wait and retry |
| 500 | Internal Server Error | Server-side error |
Related
Section titled “Related”- App Ratings Dataset - Use cases and analysis examples
- LinkedIn Data - Employee metrics
- News Sentiment - News sentiment
