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.

EndpointReturnsLimit per IP
/api/checkTop 3 complaint themes in the latest 50 reviews from the app's storefront (App Store or Google Play)20 per day
/api/rexportApp Store reviews, 50 per page, 10 pages per storefront40 per minute, 400 per day
/api/pexportGoogle Play reviews, up to 100 per page, token pagination12 per minute, 100 per day
/api/badgeSVG badge with App Store rating and number of ratingscached 12 h
Review linkGoogle “write a review” link format and prefilled QR builderno server call

Rules for every endpoint

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.

ParameterRequiredValue
urlyesApp Store link (…/id324684580), numeric App Store ID, Google Play link (play.google.com/store/apps/details?id=…) or package name (com.spotify.music)
countrynoStorefront, 2-letter code (de). Used when the link has none; default us. Fewer than 20 local reviews are topped up with US reviews.
langnoPage-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.

StatusMeaning
400Not an App Store or Google Play link
404No such app, or no recent reviews in that storefront or the US
42920 checks per day per IP used (repeat requests count)
502AI classifier not responding; retry in a few minutes
503Shared daily cap reached; retry tomorrow
504Google 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.

ParameterRequiredValue
idyesnumeric App Store ID, 5–12 digits
ccnostorefront, 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
pageno1–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
}
StatusBody
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.

ParameterRequiredValue
appyespackage name, e.g. com.spotify.music
langnoreview 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
ccnocountry, 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
tnonext 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…"
}
StatusBody
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">

![App Store rating](https://revunda.xyz/api/badge?id=324684580&country=us)

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>

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"))

More on the Apify tools: /data/.

For AI assistants

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.