Ce guide est disponible en anglais.
How to Fetch and Respond to Reviews via API
Fetch reviews from Google, Facebook and custom review sites, filter them, post owner replies, add internal notes and read review analytics with the Synup API.
En bref
- Ce que ce guide permet
- Pull every review for a location across platforms, find the ones that need attention, post a reply that is pushed to the source platform, and measure the results.
- API utilisées
- get
/api/v1/reviews/sourcesLister les sources d'avis - post
/api/v1/reviews/sourcesAjouter une source d'avis personnalisée - get
/api/v1/reviewsLister les avis - post
/api/v1/reviews/replyRépondre à un avis - post
/api/v1/reviews/notesAjouter une note à un avis - get
/api/v1/reviews/notesLister les notes d'un avis - get
/api/v1/reviews/summaryObtenir le résumé des avis - get
/api/v1/reviews/analyticsObtenir les statistiques d'avis - get
/api/v1/reviews/topic-trendsObtenir les tendances des sujets
- get
- Référence
- Reviews
- Prérequis
- A location with at least one connected review source (Google or Facebook) or a custom review URL
- An API key with read and write access to Reviews
- The location id and its client id, which every reviews call requires together
- Cas d'usage typiques
- A review inbox inside your own product
- An automated workflow that drafts replies for negative reviews and routes them for approval
- A reporting feature that shows rating, volume and response rate per location
Two calls do the core of it. A paginated GET pulls a location's reviews, from every connected platform plus any custom pages you added, each with its rating, text, sentiment and reply state. A POST sends an owner reply, which Synup pushes back to the platform the review came from. The rest is the filters that make the inbox usable and the analytics that turn a pile of reviews into the one number an owner actually watches.
How reviews flow through the API
- PlatformsGoogle, Facebook, plus custom pages such as Yelp or TripAdvisor
- Synup review syncReviews fetched, scored for sentiment, deduplicated
- Your inbox
GET /api/v1/reviewsFiltered by status, rating, sentiment, date - Reply
POST /api/v1/reviews/replyPushed to the source platform - Measure
GET /api/v1/reviews/analyticsRating, volume, response rate, themes
Every reviews call takes locationId and clientId together. Where the location came from doesn't matter. The
Google Business Profile guide covers connecting the account that
brings Google reviews in.
Review sources
GET /api/v1/reviews/sources lists where a location's reviews come from: the connected platforms plus any
custom review-page URLs, each with its status and whether it's hidden. To add a page that isn't a connected
platform, a Yelp or TripAdvisor listing say, post its URL to POST /api/v1/reviews/sources and Synup detects
the platform from the URL. It doesn't validate the URL on the way in, so a wrong one surfaces later as a failed
fetch on that source, not as an error on the add call.
curl "https://ai.synup.com/api/v1/reviews/sources?locationId=LOCATION_ID&clientId=CLIENT_ID" \
-H "Authorization: Bearer $SYNUP_API_KEY"
curl -X POST https://ai.synup.com/api/v1/reviews/sources \
-H "Authorization: Bearer $SYNUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "locationId": "LOCATION_ID", "clientId": "CLIENT_ID", "url": "https://www.yelp.com/biz/grove-street-dental-san-francisco" }'Fetch the reviews that need attention
GET /api/v1/reviews returns a page of reviews for one location, up to 100 per page, with a nextCursor.
The filters combine, so an inbox view is one request: status=unreplied for what is waiting, ratings=1,2 for
the ones that hurt, sentiment=negative when the text is worse than the stars, from and to for a window,
sort=newest for a feed.
curl "https://ai.synup.com/api/v1/reviews?locationId=LOCATION_ID&clientId=CLIENT_ID&status=unreplied&ratings=1,2,3&sort=newest&limit=100" \
-H "Authorization: Bearer $SYNUP_API_KEY"const API = "https://ai.synup.com/api/v1";
const headers = {
Authorization: `Bearer ${process.env.SYNUP_API_KEY}`,
"Content-Type": "application/json",
};
async function* reviews(locationId, clientId, filters = {}) {
let cursor = "";
do {
const params = new URLSearchParams({ locationId, clientId, limit: "100", cursor, ...filters });
const res = await fetch(`${API}/reviews?${params}`, { headers });
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const { data } = await res.json();
yield* data.reviews; // { id, author, rating, body, reviewDate, platform, sentiment, reply, reviewUrl, respondable }
cursor = data.nextCursor ?? "";
} while (cursor);
}
for await (const review of reviews("LOCATION_ID", "CLIENT_ID", { status: "unreplied", ratings: "1,2,3", sort: "newest" })) {
console.log(review.rating, review.platform, review.body.slice(0, 80));
}import os
import requests
API = "https://ai.synup.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SYNUP_API_KEY']}"}
def reviews(location_id, client_id, **filters):
cursor = ""
while True:
params = {"locationId": location_id, "clientId": client_id, "limit": 100, "cursor": cursor, **filters}
r = requests.get(f"{API}/reviews", headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
data = r.json()["data"]
yield from data["reviews"] # id, author, rating, body, reviewDate, platform, sentiment, reply, reviewUrl, respondable
cursor = data.get("nextCursor") or ""
if not cursor:
return
for review in reviews("LOCATION_ID", "CLIENT_ID", status="unreplied", ratings="1,2,3", sort="newest"):
print(review["rating"], review["platform"], review["body"][:80])Each review also carries a topics array, the themes Synup pulled out of the text (wait times, staff, pricing),
which is what a per-topic inbox filter keys on. And it carries respondable, which is false once a review has
been removed from its source or comes from a
source that doesn't accept replies. Check it before offering a reply button. Pass deleted=1 to list the
reviews that have disappeared from their platform instead of the live ones.
Post a reply
POST /api/v1/reviews/reply takes the review id and the reply text, posts the owner reply and pushes it to the
source platform. The response tells you whether the push happened now (pushed: true, status: posted) or is
queued (pending), and when it failed whether a retry might succeed (retryable).
curl -X POST https://ai.synup.com/api/v1/reviews/reply \
-H "Authorization: Bearer $SYNUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reviewId": "REVIEW_ID",
"text": "Thank you for letting us know. I am sorry the wait was longer than it should have been. Please call us on 415 555 0142 and ask for Maria so we can make it right."
}'async function reply(reviewId, text) {
const res = await fetch(`${API}/reviews/reply`, {
method: "POST",
headers,
body: JSON.stringify({ reviewId, text }),
});
if (res.status === 422) return { status: "not_respondable", error: (await res.json()).error };
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const { data } = await res.json();
return data; // { ok, pushed, status: posted|pending|failed|draft, externalReplyId, retryable }
}def reply(review_id, text):
r = requests.post(f"{API}/reviews/reply", headers=HEADERS, timeout=30,
json={"reviewId": review_id, "text": text})
if r.status_code == 422:
return {"status": "not_respondable", "error": r.json()["error"]}
r.raise_for_status()
return r.json()["data"] # ok, pushed, status (posted|pending|failed|draft), externalReplyId, retryableA 422 means the review can't be replied to, usually because it was removed from its source. A failed push
with retryable: true is worth one retry after a pause. With retryable: false, stop retrying and have someone
look at the platform.
Keep the internal conversation out of the reply
POST /api/v1/reviews/notes attaches a note to a review that only your team sees, and
GET /api/v1/reviews/notes reads them back. Use notes for the handoff: "escalated to the store manager",
"customer called back", "draft reply awaiting approval". They never reach the platform.
curl -X POST https://ai.synup.com/api/v1/reviews/notes \
-H "Authorization: Bearer $SYNUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reviewId": "REVIEW_ID", "body": "Escalated to the downtown manager; reply drafted, awaiting approval." }'Measure
Three endpoints turn the review list into reporting, from portfolio-wide down to a single topic.
GET /api/v1/reviews/summaryrolls up rating distribution, volume and trend across a client or the whole agency for a date range, filterable by tags. One call for a portfolio view.GET /api/v1/reviews/analyticsis the per-location deep dive: KPIs with period-over-period deltas over 7, 30 or 90 days, a monthly trend of volume, rating, sentiment mix and response rate over 3, 6 or 12 months, a per-platform breakdown, day-of-week timing, an agency benchmark, themes and an AI summary.GET /api/v1/reviews/topic-trendstracks what customers talk about: a time series per topic, rising and declining movers, and emerging or faded topics over 30, 90 or 180 days.
curl "https://ai.synup.com/api/v1/reviews/analytics?locationId=LOCATION_ID&clientId=CLIENT_ID&kpiDays=30&trendMonths=6" \
-H "Authorization: Bearer $SYNUP_API_KEY"Response rate is the number most owners watch. It comes straight from the analytics trend, and it is the metric an automated reply workflow moves first.
Building an automated review-response workflow
Today you poll for this. review.received sits in the webhook event catalog as a planned
event, and the loop below switches to it unchanged once it goes live.
- Poll
GET /reviews?status=unreplied&sort=newestEvery 15 minutes per active location - Route4 and 5 stars: auto-reply template. 3 and below: draft plus human approval
- Draft
POST /reviews/notesStore the draft as a note so the team sees it in Synup too - ApproveYour UI, or a Slack message with an approve button
- Reply
POST /reviews/replyCheck pushed and status; retry once if retryable
Track the review ids you've already seen so an edit on the platform doesn't read as a new review, and never
reply twice. reply on the review row is non-null once a reply exists.
Gotchas
| Situation | What happens | What to do |
|---|---|---|
clientId left out | 400 | Both ids are required on every reviews call. |
| Reply to a removed review | 422 | Mark it as not respondable in your inbox. |
| Reply to a custom-source review (Yelp, TripAdvisor) | respondable: false, reply fails | Read-only. Reply on the platform itself. |
status: pending on a reply | Push queued, not yet confirmed | Re-read the review later. reply is set when it lands. |
status: failed, retryable: true | Temporary platform error | Retry once after a minute. |
| Same review under two sources | Two rows with different platforms | Expected. They are separate listings' reviews. |