Revunda developer docs: free review endpoints
Base URL https://revunda.xyz. No API key, GET only, JSON. Spec: /openapi.json (OpenAPI 3.1). Site summary for AI assistants: /llms.txt.
| Endpoint | Returns | Limit per IP |
|---|---|---|
/api/check | Top 3 complaint themes in the latest 50 reviews from the app's storefront (App Store or Google Play) | 20 per day |
/api/rexport | App Store reviews, 50 per page, 10 pages per storefront | 40 per minute, 400 per day |
/api/pexport | Google Play reviews, up to 100 per page, token pagination | 12 per minute, 100 per day |
/api/badge | SVG badge with App Store rating and number of ratings | cached 12 h |
| Review link | Google “write a review” link format and prefilled QR builder | no server call |
Rules for every endpoint
- Limits are counted per IP and per server instance; treat the numbers as maximums. On the export endpoints a 429 includes
retry_after(seconds, or"tomorrow"). - The JSON endpoints send no CORS headers. Call them from a server, script or serverless function, not from browser code on another domain.
/api/badgeallows any origin. - Export responses are cached at the CDN (first pages for up to 6 hours), so a new review can take a few hours to appear.
- Data comes from Apple's public review feed and Google Play's public listing. Reviewer names are not returned.
- For production volume use the RapidAPI listing (API key, plan per volume).
GET /api/check
Reads the latest 50 public reviews from the app's storefront (the storefront in the link (apps.apple.com/<cc>/…, Google Play gl/hl), else the optional country or lang parameter, else US; fewer than 20 local reviews are topped up with US reviews), classifies every 1–3★ review with an AI model (TypeSafe Jev) into 7 themes (Bugs, Crashes / freezes, Feature requests, Pricing / subscription, UX / design, Ads, Performance) and returns the 3 most frequent, each with a count and a representative quote of up to 280 characters. It powers the free app check.
| Parameter | Required | Value |
|---|---|---|
url | yes | App Store link (…/id324684580), numeric App Store ID, Google Play link (play.google.com/store/apps/details?id=…) or package name (com.spotify.music) |
country | no | Storefront, 2-letter code (de). Used when the link has none; default us. Fewer than 20 local reviews are topped up with US reviews. |
lang | no | Page-language hint (de|fr|es|pl|it|pt|en); maps to a storefront (de→de, en→us) and sets the Google Play language |
curl
curl "https://revunda.xyz/api/check?url=https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580"
curl "https://revunda.xyz/api/check?url=com.spotify.music"JavaScript (Node 18+)
const app = "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580";
const r = await fetch("https://revunda.xyz/api/check?url=" + encodeURIComponent(app));
const j = await r.json();
if (!r.ok) throw new Error(j.error);
for (const t of j.top) console.log(t.theme, t.n, t.quote);Python
import requests
r = requests.get("https://revunda.xyz/api/check", params={"url": "com.spotify.music"}, timeout=60)
j = r.json()
if r.status_code != 200:
raise SystemExit(j["error"])
for t in j["top"]:
print(t["theme"], t["n"], t["quote"])Response (App Store, trimmed)
{
"app": "Spotify: Music and Podcasts",
"id": "324684580",
"reviews": 50,
"negative": 10,
"classified": 10,
"top": [
{
"theme": "Pricing / subscription",
"n": 6,
"quote": "They are now playing adds after EVERY song instead of just after a few songs, and of course you need to pay a subscription just to do simple…",
"stars": 2
},
{
"theme": "Bugs",
"n": 2,
"quote": "It has a lot of the songs and that’s why I’m happy with it but if you don’t have premium I would suggest of making a playlist of all the son…",
"stars": 3
}
],
"day": "2026-10-24",
"rating": 4.8,
"count": 42722184,
"genre": "Music",
"med": 4.8,
"peers": [
{
"n": "Suno",
"id": "6480136315",
"r": 4.9
},
{
"n": "Shazam: Find Music & Concerts",
"id": "284993459",
"r": 4.9
}
],
"tok": "<opaque>"
}Google Play results add "store": "play", developer and ios (the same app on the App Store, or null). peers and med (top free apps in the same App Store genre and their median rating) are filled for App Store apps only. "cached": true marks a repeat result from the same day. Ignore tok.
| Status | Meaning |
|---|---|
| 400 | Not an App Store or Google Play link |
| 404 | No such app, or no recent reviews in that storefront or the US |
| 429 | 20 checks per day per IP used (repeat requests count) |
| 502 | AI classifier not responding; retry in a few minutes |
| 503 | Shared daily cap reached; retry tomorrow |
| 504 | Google Play slow to respond; retry in a minute |
Error bodies are {"error": "<message>"}.
GET /api/rexport
App Store reviews, newest first, 50 per page. Apple's public feed stops at 10 pages (500 reviews) per storefront; query other storefronts for more. Page 1 also returns the app's name, seller, rating, number of ratings and current version. It powers the review export.
| Parameter | Required | Value |
|---|---|---|
id | yes | numeric App Store ID, 5–12 digits |
cc | no | storefront, default us: us gb ca au nz ie in sg de at ch fr be nl lu es pt it pl cz sk hu ro bg gr se no dk fi ee lv lt tr ua il ae sa eg za ng ke mx br ar cl co pe jp kr tw hk th vn ph id my |
page | no | 1–10, default 1 |
curl
curl "https://revunda.xyz/api/rexport?id=324684580&cc=us&page=1"JavaScript (Node 18+): all pages of one storefront
const all = [];
for (let page = 1; page <= 10; page++) {
const r = await fetch(`https://revunda.xyz/api/rexport?id=324684580&cc=us&page=${page}`);
if (!r.ok) break;
const j = await r.json();
all.push(...j.reviews);
if (!j.more) break;
}
console.log(all.length);Python: save as CSV
import csv, requests
rows, page = [], 1
while page <= 10:
j = requests.get("https://revunda.xyz/api/rexport", params={"id": "324684580", "cc": "gb", "page": page}, timeout=30).json()
rows += j.get("reviews", [])
if not j.get("more"):
break
page += 1
with open("reviews.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["id", "date", "rating", "title", "text", "version", "votes", "voters"])
w.writeheader()
w.writerows(rows)Response (trimmed)
{
"id": "324684580",
"cc": "us",
"page": 1,
"reviews": [
{
"id": "14698713946",
"date": "2026-10-22T14:06:07-07:00",
"rating": 5,
"title": "Love spotify just 1 complaint",
"text": "On one of the recent updates something changed and my gen 2 onn party speaker started sounding distorted/clipping and its only when i have v…",
"version": "9.1.92",
"votes": 0,
"voters": 0
}
],
"app": {
"name": "Spotify: Music and Podcasts",
"seller": "Spotify",
"rating": 4.77081,
"ratings": 42722184,
"version": "9.1.94"
},
"more": true
}| Status | Body |
|---|---|
| 400 | {"error":"bad_input"}: bad id, storefront or page |
| 429 | {"error":"rate_limited","retry_after":60} or "retry_after":"tomorrow"; "busy" when the shared per-minute cap is hit |
| 502 | {"error":"upstream"}: Apple's feed did not answer |
An unknown app returns 200 with "app": null and no reviews.
GET /api/pexport
Google Play reviews, newest first, up to 100 per page. Pass the next value of one response as t (URL-encoded) to get the next page; it is null on the last page. The first page also returns info (name, developer, rating, number of ratings, version). replied shows whether the developer has answered.
| Parameter | Required | Value |
|---|---|---|
app | yes | package name, e.g. com.spotify.music |
lang | no | review language, default en: en es de fr it pt pl nl sv da no fi cs sk hu ro bg el tr uk ru ja ko zh id th vi ar he hi |
cc | no | country, default us: us gb ca au nz ie in sg de at ch fr be nl es pt it pl cz sk hu ro bg gr se no dk fi tr ua il ae sa za ng mx br ar cl co jp kr tw hk th vn ph id my |
t | no | next token from the previous page |
curl
curl "https://revunda.xyz/api/pexport?app=com.spotify.music&lang=en&cc=us"JavaScript (Node 18+): 300 reviews
let t = "", all = [];
while (all.length < 300) {
const r = await fetch(`https://revunda.xyz/api/pexport?app=com.spotify.music&lang=en&cc=us${t ? "&t=" + encodeURIComponent(t) : ""}`);
if (!r.ok) break;
const j = await r.json();
all.push(...j.reviews);
if (!j.next) break;
t = j.next;
}
console.log(all.length);Python: two pages of German reviews
import requests
API = "https://revunda.xyz/api/pexport"
params = {"app": "com.spotify.music", "lang": "de", "cc": "de"}
j = requests.get(API, params=params, timeout=30).json()
print(j["info"]["name"], len(j["reviews"]))
if j["next"]:
j2 = requests.get(API, params={**params, "t": j["next"]}, timeout=30).json()
print(len(j2["reviews"]))Response (trimmed)
{
"app": "com.spotify.music",
"lang": "en",
"cc": "us",
"reviews": [
{
"id": "9f4851bc-18de-4567-9241-2bfd5436c7d5",
"date": "2026-10-23T02:02:36.442Z",
"rating": 5,
"title": "",
"text": "so nice 👍🏼👍🏼 spotify",
"version": "9.1.90.2270",
"votes": 0,
"replied": false
}
],
"info": {
"name": "Spotify: Music and Podcasts",
"developer": "Spotify AB",
"rating": 4.348969,
"ratings": 36529556,
"version": "VARY"
},
"next": "Cs4BIsUBAV8d1P2eGXKBg74A…"
}| Status | Body |
|---|---|
| 400 | {"error":"bad_input"} |
| 404 | {"error":"not_found"}: no public app with that package name |
| 429 | {"error":"rate_limited"|"busy","retry_after":60|"tomorrow"} |
| 502 | {"error":"upstream"} |
GET /api/badge
An SVG badge such as “App Store: ★ 4.8 · 42.7M ratings”. Parameters: id (App Store ID), country (default us), label (up to 24 characters, default “App Store”). Bad input returns status 400 with an “invalid id” badge instead of a broken image. Builder: /apps/badge/.
<img src="https://revunda.xyz/api/badge?id=324684580&country=us" alt="App Store rating">
Google review link
The review link builder runs in the browser and makes no server call. The link it builds has this format:
https://search.google.com/local/writereview?placeid=<PLACE_ID>- Find the Place ID with Google's Place ID finder. Place IDs that start with
EiorEhbelong to an address, not a business, and open the wrong page. - A Business Profile short link
https://g.page/r/<code>becomeshttps://g.page/r/<code>/review. - Links with
cid=or a0x…:0x…ID cannot be converted; use the Place ID. - To hand someone a ready QR code and printable table card, link to
https://revunda.xyz/local/review-link/?placeid=<PLACE_ID>; the page fills in the ID and builds everything on load.
JavaScript
const reviewLink = placeId =>
"https://search.google.com/local/writereview?placeid=" + encodeURIComponent(placeId);
console.log(reviewLink("ChIJN1t_tDeuEmsRUsoyG83frY4"));Python
from urllib.parse import quote
def review_link(place_id):
return "https://search.google.com/local/writereview?placeid=" + quote(place_id)
print(review_link("ChIJN1t_tDeuEmsRUsoyG83frY4"))Higher volume: RapidAPI and Apify
- RapidAPI: App Store reviews and Google Business Profile suspension risk: API key; free plan with 20 requests a month; endpoints for App Store review insights and a Google Business Profile suspension risk score.
- Apify: App Store review insights: runs on Apify, pay per result.
- Apify: Google Maps review insights: runs on Apify, pay per result.
- Apify: Restaurant review insights: runs on Apify, pay per result.
More on the Apify tools: /data/.
For AI assistants
- /llms.txt: products, prices, free tools, endpoints, sample reports and key guides.
- /llms-full.txt: the same plus the full endpoint reference and every guide URL.
- /openapi.json: OpenAPI 3.1, usable as a tool or action definition.
Guides for app developers: /apps/guides/developer/.
FAQ
Do I need an API key?
No. The endpoints on this page are free and keyless. The RapidAPI listing uses a key and adds a Google Business Profile suspension risk endpoint.
Can I call the endpoints from a browser on my own site?
Only /api/badge. The JSON endpoints send no CORS headers, so a browser on another domain blocks the response. Call them from a server or serverless function.
Why did /api/check return different themes than yesterday?
It reads the latest 50 reviews from the storefront and caches the result per app, storefront and day, so the result changes as new reviews arrive.
How do I get more than 500 App Store reviews?
Apple's public feed stops at 10 pages per storefront. Query more storefronts with cc (for example gb, ca, au) and remove duplicates by review id.
Is the data affiliated with Apple or Google?
No. It comes from Apple's public review feed and Google Play's public listing.