A one-off CSV for Excel, or a Google Sheet that refreshes itself.
Repuso collects customer reviews from Google, TripAdvisor, Trustpilot, the App Store and 50+ other platforms, and serves them through one REST API in a single JSON schema - with rating history, sentiment, AI summaries and webhooks on new reviews. You point it at a public profile; it keeps that profile current and tells you when something changes.
This guide assumes you already have an API key and at least one profile connected. Both take about two minutes: start with the quickstart, or get a key in the console - the trial is 10 days and needs no card.
The whole export is a loop and a writer. Reviews come back in one schema regardless of platform, so the columns are the same whether the row came from Google or TripAdvisor.
import csv, requests
API, AUTH = "https://api.repuso.com/public/v1", ("", "YOUR_API_KEY")
COLS = ["id","type","profile_id","from_name","rating_value","rating_scale",
"sentiment","posted_on","text"]
def rows():
params = {"limit": 100}
while True:
page = requests.get(f"{API}/reviews/get", params=params, auth=AUTH, timeout=30).json()
for r in page["items"]:
yield {c: r.get(c, "") for c in COLS}
if not page.get("has_more"):
break
params["before_ts"] = page["next_before_ts"]
params["before_id"] = page["next_before_id"]
with open("reviews.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=COLS)
w.writeheader()
w.writerows(rows())Open it in Excel, Numbers or Sheets. Two details worth knowing before you hand it to someone: rating_value is not always out of five - divide by rating_scale if you are averaging across platforms - and review text can contain newlines, which is why this uses a real CSV writer rather than joining strings with commas.
Filtering server-side beats exporting everything and deleting rows afterwards:
# only the negative ones, this year, from two profiles
?max_rating=3&since=2026-01-01&profiles=49331,49334
# only reviews mentioning delivery
?q=delivery
# only reviews with photos or video
?media_only=true
# translated into English on the way out
?translate=enIf the point is a shared sheet rather than a file, skip the CSV. This Apps Script rewrites a tab from the API, and a time-based trigger keeps it current with nothing running on your side.
function refreshReviews() {
const KEY = "YOUR_API_KEY";
const headers = { Authorization: "Basic " + Utilities.base64Encode(":" + KEY) };
const sheet = SpreadsheetApp.getActive().getSheetByName("Reviews");
let url = "https://api.repuso.com/public/v1/reviews/get?limit=100";
const out = [["Date","Platform","Rating","Author","Sentiment","Review"]];
while (url) {
const page = JSON.parse(UrlFetchApp.fetch(url, { headers }).getContentText());
page.items.forEach(r => out.push([
r.posted_on, r.type, r.rating_value, r.from_name, r.sentiment, r.text
]));
url = page.has_more
? `https://api.repuso.com/public/v1/reviews/get?limit=100&before_ts=${page.next_before_ts}&before_id=${page.next_before_id}`
: null;
}
sheet.clear();
sheet.getRange(1, 1, out.length, out[0].length).setValues(out);
}In the Apps Script editor: Triggers → add a time-driven trigger on refreshReviews, hourly or daily. Write the values in one setValues call as above rather than appending row by row - Apps Script quotas punish per-row writes, and a few thousand reviews will time out.
Exporting everything every hour is wasteful once you are past a few thousand reviews. Store the newest posted_on you exported and pass it back as since, then append only what is new. Or take a webhook and write rows as reviews arrive.