Skip to content

Reviews API

Every review of a listing, in one standardized format#

Send a platform and a listing; Zembra collects all of its reviews and returns them in one consistent format, whatever the platform. Pull them once, or keep monitoring the listing and get every change, new reviews, edits, replies, deletions, pushed to you.

  • AccessAPI key, no platform credentials
  • Platforms131 supported
  • EndpointPOST /reviews GET /reviews
  • CommitmentNone: pay per use, no minimum

At a glance

  • One POST to start, results in seconds
  • Listing details returned instantly with every response, at no extra cost
  • Four monitoring levels, from a one-off pull to full tracking
  • Webhooks to your server, Slack or email
  • Reading results with GET is free

Why teams choose Zembra for reviews#

Performance

First results in seconds, listing details returned instantly, and complete results served from ready-made caches: every review of a listing in a single GET.

Completeness

131 platforms, every review including hidden ones, the full history of edits, replies, deletions and restorations.

Quality

One schema for every platform: ratings normalized to 1–5, UTC timestamps, stable review IDs, and one listing behind every variant of its URL.

Rich data

Author, photos, replies, language, translations, rating breakdown, and every extra field the platform shows in rawData.

No noise

Webhooks only fire when something changed, with the level of detail you choose: counts, changes only, or everything.

Fair pricing

Pay per use with credits that get cheaper as you grow. No subscription, no commitment, no minimum.

How it works

From one request to a continuous stream of updates#

You create a job for a listing. Zembra answers right away with the listing's details, collects the reviews, then delivers them, either pushed to your webhook or read whenever you ask.

  1. Create a job with POST /reviews: the platform, the listing's slug or URL, and a monitoring level.
  2. Get an instant answer: the job and the listing's details, so you know right away the listing exists, with no polling.
  3. Zembra pulls every review of the listing, usually within seconds.
  4. Your webhook receives the reviews, then, with monitoring, only what changes after each scheduled check.

The life of a job#

Jobs last 30 days #

A monitoring job runs for 30 days. With autoRenew, a new 30-day job starts where the old one ends; it is charged about a day before expiry, and if your balance is too low the renewal fails and you are notified.

One active monitoring job per listing #

Creating a monitoring job for a listing you already monitor returns the existing job with 409 Conflict, free of charge. Slug variants count as the same listing.

Switching monitoring levels #

The level of a job can't be changed. Delete the job and create a new one at the new level; the new job is charged as a new job.

Stopping: delete or turn off auto-renew #

To stop at the end of the period, set autoRenew to false with PATCH /reviews: everything keeps working until the job expires. DELETE /reviews stops monitoring and webhooks immediately, the job can no longer be read (410 Gone), and nothing is refunded.

Monitoring levels

One-off pull or continuous monitoring#

Every level starts by pulling all the reviews at once. What differs is what happens next.

none basic regular full
What it isA one-off job, no monitoringMonthly updatesDaily updatesComplete, continuous tracking
First pullAll reviews, or up to your sizeLimit / dateLimitAll reviews, at once, when the job is created
Checks for new reviews—Every 30 daysEvery dayNear real time: at most every 8 hours, plus a complete re-check every week
Keeps older reviews up to date (edits, replies, deletions)—Recent reviews onlyRecent reviews onlyAll reviews
Keeps listing details up to date—No (returned on the first request)No (returned on the first request)Yes: review count and rating at most every 8 hours, with the reviews; all other details every week
Limit by size or dateYes, the only level that allows itNo: monitoring always covers every review
Auto-renew every 30 days—Yes, with autoRenew
Data available for5 daysThe 30-day life of the job
Price 50 credits + 1 per review 75 credits + 3 per review
renewal 50 + 3 per new review
125 credits + 3 per review
renewal 75 + 3 per new review
150 credits + 1 per review
renewal 100 + 1 per review

Basic and Regular close the gap between the last review they pulled and the newest ones. They don't promise to keep older reviews or the listing's details up to date. Full is the only level that keeps every review and the listing current.

Which level for which use#

None

One-off audits and research

Competitor benchmarks, market studies, AI training data, importing a new client's history. Set a size or date limit to pay only for what you need.

Basic

Light, periodic tracking

Monthly reports and sales prospecting, where a few weeks of delay is fine.

Regular

Daily dashboards on a budget

Reputation dashboards and alerts that need new reviews every day, without tracking every edit.

Full

Live reputation management

Reply workflows, review inboxes and analytics that must reflect every edit, reply and deletion, plus accurate listing details.

Pricing

Pay per use, no commitment#

No subscription, no minimum use, no contract. You pay credits for the reviews you collect, and reading them with GET is free. Credits get cheaper as your volume grows: how credit pricing works.

How a job is priced#

  • none50 + 1 × reviews pulled
  • basic75 + 3 × reviews, then 3 × each new review
  • regular125 + 3 × reviews, then 3 × each new review
  • full150 + 1 × reviews, then 1 × each new review

Renewals. With autoRenew, each new 30-day period costs a renewal fee: Basic 50 and Regular 75 credits plus 3 per new review; Full 100 credits plus 1 per review on the listing, since Full keeps every review up to date.

Charges follow the work: the base fee when the job starts, review fees as reviews are collected. Optional permalink (permanent copies of photos) adds 25 credits. A 409 for a listing you already monitor costs nothing.

Simulate a job #

First 30 days—
Each renewal (auto-renew)—

What your credits get you#

For a typical listing with 300 reviews: a one-off pull costs 350 credits, the first month of Regular monitoring 1,025, the first month of Full monitoring 450.

CreditsPriceOne-off pullsRegular monitoring, first monthFull monitoring, first month
10,000 Free on sign-up ≈ 8,400 reviews (28 listings) ≈ 9 listings ≈ 22 listings
100,000 $92 ≈ 85,500 reviews (285 listings) ≈ 97 listings ≈ 222 listings
1,000,000 $762 ≈ 857,100 reviews (2,857 listings) ≈ 975 listings ≈ 2,222 listings
10,000,000 $4,862 ≈ 8,571,300 reviews (28,571 listings) ≈ 9,756 listings ≈ 22,222 listings

API reference

Requests and parameters#

Base URL https://api.zembra.io, authenticated with Authorization: Bearer <token>. A sandbox at https://sandbox.zembra.io returns realistic dummy data without using credits.

POST /reviews
cURL
  • cURL
  • .Net
  • Go
  • Java
  • Node.js
  • PHP
  • Python
  • Ruby
curl --location 'https://api.zembra.io/reviews' \
--header 'Authorization: Bearer {token}' \
--header 'Content-Type: application/json' \
--data '{
  "network": "google",
  "slug": "my-listing-slug",
  "monitoring": "full",
  "autoRenew": true,
  "internalId": "location-1042",
  "includeRawData": false
}'
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.zembra.io/reviews");
request.Headers.Add("Authorization", "Bearer {token}");
var content = new StringContent("{
  \"network\": \"google\",
  \"slug\": \"my-listing-slug\",
  \"monitoring\": \"full\",
  \"autoRenew\": true,
  \"internalId\": \"location-1042\",
  \"includeRawData\": false
}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
package main

import (
  "fmt"
  "strings"
  "net/http"
  "io/ioutil"
)

func main() {

  url := "https://api.zembra.io/reviews"
  method := "POST"

  payload := strings.NewReader(`{
  "network": "google",
  "slug": "my-listing-slug",
  "monitoring": "full",
  "autoRenew": true,
  "internalId": "location-1042",
  "includeRawData": false
}`)

  client := &http.Client {
  }
  req, err := http.NewRequest(method, url, payload)

  if err != nil {
    fmt.Println(err)
    return
  }
  req.Header.Add("Authorization", "Bearer {token}")
  req.Header.Add("Content-Type", "application/json")

  res, err := client.Do(req)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer res.Body.Close()

  body, err := ioutil.ReadAll(res.Body)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println(string(body))
}
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{
  \"network\": \"google\",
  \"slug\": \"my-listing-slug\",
  \"monitoring\": \"full\",
  \"autoRenew\": true,
  \"internalId\": \"location-1042\",
  \"includeRawData\": false
}");
Request request = new Request.Builder()
  .url("https://api.zembra.io/reviews")
  .method("POST", body)
  .addHeader("Authorization", "Bearer {token}")
  .addHeader("Content-Type", "application/json")
  .build();
Response response = client.newCall(request).execute();
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer {token}");
myHeaders.append("Content-Type", "application/json");

var raw = JSON.stringify({
  "network": "google",
  "slug": "my-listing-slug",
  "monitoring": "full",
  "autoRenew": true,
  "internalId": "location-1042",
  "includeRawData": false
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.zembra.io/reviews", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
<?php
$client = new Client();
$headers = [
  'Authorization' => 'Bearer {token}',
  'Content-Type' => 'application/json'
];
$body = '{
  "network": "google",
  "slug": "my-listing-slug",
  "monitoring": "full",
  "autoRenew": true,
  "internalId": "location-1042",
  "includeRawData": false
}';
$request = new Request('POST', 'https://api.zembra.io/reviews', $headers, $body);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
import requests
import json

url = "https://api.zembra.io/reviews"

payload = json.dumps({
  "network": "google",
  "slug": "my-listing-slug",
  "monitoring": "full",
  "autoRenew": True,
  "internalId": "location-1042",
  "includeRawData": False
})
headers = {
  'Authorization': 'Bearer {token}',
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
require "uri"
require "json"
require "net/http"

url = URI("https://api.zembra.io/reviews")

https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = "Bearer {token}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  "network": "google",
  "slug": "my-listing-slug",
  "monitoring": "full",
  "autoRenew": true,
  "internalId": "location-1042",
  "includeRawData": false
})

response = https.request(request)
puts response.read_body

Response

{
    "message": "Reviews fetching order received for Google: my-listing. Please allow a few minutes before requesting reviews.",
    "data": {
        "job": {
            "network": "google",
            "slug": "my-listing-slug",
            "jobId": "123e4567-e89b-12d3-a456-426614174000",
            "uid": "OTBmNTBlYzViMjM2NTU4MGVmNTUxMmQwMmY0YzZlNmU",
            "monitoring": "full",
            "internalId": "",
            "requestedAt": "2023-01-01T00:00:00Z",
            "expiresAt": "2023-01-01T00:00:00Z",
            "includeRawData": false,
            "cost": 165,
            "active": true,
            "sizeLimit": 100,
            "dateLimit": 1734140315,
            "autoRenew": true,
            "renewed": false,
            "url": ""
        },
        "target": {
            "address": {
                "street": "5621 Main St",
                "city": "Mallorca",
                "region": "QC",
                "postalCode": "78128",
                "country": "US"
            },
            "aliases": [
                "my-listing-1",
                "my-first-listing"
            ],
            "categories": [
                "Restaurant",
                "Fast Food"
            ],
            "formattedAddress": "5621 Main St, Mallorca, QC 78128, US",
            "globalRating": 4.5,
            "id": "ChIJV9OJxsOq3oARnyGzSXY0ym8",
            "lastCrawled": "2023-01-01T00:00:00Z",
            "lastUpdated": "2023-01-01T00:00:00Z",
            "link": "https://my.zembratech.com/dashboard/review-page/insights/MWJmZTc5MDZjMzg5OGI3OGMyNjJiODNjOGJjMWI3Y2Q",
            "name": "My Google Business",
            "network": "google",
            "phone": "(210) 456-0987",
            "photos": [
                "https://media2.trover.com/T/5a5eae0184b8636d6b0385f0/fixedw-1.jpg",
                "https://media2.trover.com/T/5a5eae0184b8636d6b0385f0/fixedw-2.jpg"
            ],
            "priceRange": "$$$",
            "profileImage": "https://media2.trover.com/T/5a5eae0184b8636d6b0385f0/fixedw.jpg",
            "ratingBreakdown": {
                "overall": 8.4,
                "perCriteria": [
                    {
                        "criteria": "Overall",
                        "rating": 8.4
                    },
                    {
                        "criteria": "Cleanliness",
                        "rating": 8.9
                    },
                    {
                        "criteria": "Facilities",
                        "rating": 8.3
                    },
                    {
                        "criteria": "Location",
                        "rating": 8.7
                    },
                    {
                        "criteria": "Service",
                        "rating": 8.7
                    },
                    {
                        "criteria": "Value for money",
                        "rating": 8.2
                    }
                ],
                "ratingDistribution": [
                    {
                        "rating": "9+ Exceptional",
                        "count": 135
                    },
                    {
                        "rating": "8-9 Excellent",
                        "count": 66
                    },
                    {
                        "rating": "7-8 Very Good",
                        "count": 32
                    },
                    {
                        "rating": "6-7 Good",
                        "count": 24
                    },
                    {
                        "rating": "<6 Below Expectation",
                        "count": 11
                    }
                ]
            },
            "rawData": [],
            "reviewCount": {
                "native": {
                    "total": 6320,
                    "active": 5986,
                    "hidden": 334
                },
                "available": {
                    "total": 6320,
                    "active": 5986,
                    "hidden": 334
                }
            },
            "slug": "my-listing-slug",
            "url": "https://maps.google.com/?cid=16256511492469774330",
            "website": "https://my-business.com"
        }
    }
}
{
    "message": "Invalid slug [not-a-real-listing-slug]"
}
{
    "message": "Invalid bearer token"
}
{
    "message": "Cannot process request: balance too low!"
}
{
    "message": "Requested network [Facebook] is not enabled for your account"
}
{
    "message": "Listing not found. Maybe not a valid listing."
}

POST /reviews: create a job#

networkstring, required
The platform, e.g. google, yelp. See platforms.
slugstring, required
The listing: its identifier, its URL, or part of it.
monitoringnone · basic · regular · full
Default none. See monitoring levels.
sizeLimitinteger ≥ 25, none only
The most recent reviews to collect, and to pay for.
dateLimitUnix seconds, none only
Only collect reviews posted after this date.
autoRenewboolean, monitoring only
Renew the job every 30 days. Default: your account setting.
internalIdstring, 3–255
Your own reference. See internalId.
includeRawDataboolean
Include rawData in webhook deliveries.
skipListenersboolean
Don't send this monitoring job's changes to your webhooks.

GET /reviews: read the reviews (free)#

jobId / uid / network + slugidentify the job
A job ID, the listing's uid, or the platform and slug.
limit, offsetinteger, limit 0–1000
Paginate the response. Without filters, every review comes back in one response.
sortBy, sortDirectiontimestamp, rating, … · ASC/DESC
Default: newest first.
postedAfter, postedBeforeUnix seconds
Reviews posted in a date range.
minRating, maxRatingnumber
Filter on the normalized 1–5 rating.
hasReplies, hasEdits, hasPhotos, hasTranslation, isHidden, isRecommendedboolean
Filter on review properties.
includeDeleted, isDeletedboolean
Include or select deleted reviews.
fields[], languagearray · 2–3 letters
Pick fields (e.g. rawData) or a language.

One-off (none) jobs only support limit and offset.

Don't confuse the limits. sizeLimit and dateLimit on POST decide how many reviews are collected and charged, and only for one-off jobs. limit on GET only paginates what you read back: it never changes what is collected or what you pay. #

Other calls#

GET /reviews/jobsList your jobs; filter by internalId, slug, platform or level. Free.
PATCH /reviewsChange internalId, autoRenew, includeRawData or skipListeners. Free.
PUT /reviewsForce an extra check now: partial (15 credits) or full (50 credits).
DELETE /reviewsDelete a job. See stopping a job.

Status codes#

200Job created, or reviews returned
206First pull still running: partial results
400Invalid parameter
402Balance too low
403Platform not enabled on your account
404The listing doesn't exist on the platform
406Not a reviewable business
409Listing already monitored: existing job returned, free
410Job expired or deleted
429Rate limit reached
503Platform temporarily unavailable

Data

What you get back#

Every response has the same layout: the job, the listing (target), the reviews, and how many were returned.

A review#

id, urlThe review's ID on the platform and its link.
text, language, translationThe text, its language, and the platform's translation when it offers one.
rating, recommendationRating on a 1–5 scale; recommendation (1, 0, -1) on platforms that use it.
timestamp, editedAtWhen it was posted and last edited, in UTC.
author, photosName, photo, profile link and location of the author; review photos.
repliesThe business's and others' replies.
editsPrevious versions of the review.
isHidden, isDeletedSee hidden, edited and deleted reviews.
rawDataSee rawData.
Example GET /reviews response
{
    "status": "SUCCESS",
    "message": "Reviews for Google: my-listing-slug",
    "data": {
        "job": {
            "network": "google",
            "slug": "my-listing-slug",
            "jobId": "0d6c7d22-1c7e-4bde-9a3c-5b1c3b2f1e10",
            "uid": "MWJmZTc5MDZjMzg5OGI3OGMyNjJiODNjOGJjMWI3Y2Q",
            "monitoring": "full",
            "skipListeners": false,
            "internalId": "location-1042",
            "requestedAt": "2026-01-01 08:00:00",
            "expiresAt": "2026-01-31 08:00:00",
            "includeRawData": false,
            "permalink": false,
            "cost": 470,
            "active": true,
            "autoRenew": true,
            "renewed": false
        },
        "target": {
            "address": {
                "street": "5621 Main St",
                "city": "San Diego",
                "region": "CA",
                "postalCode": "92101",
                "country": "US"
            },
            "aliases": [
                "old-listing-slug"
            ],
            "categories": [
                "Restaurant"
            ],
            "formattedAddress": "5621 Main St, San Diego, CA 92101, US",
            "globalRating": 4.4,
            "id": "ChIJV9OJxsOq3oARnyGzSXY0ym8",
            "lastCrawled": "2026-01-01 08:00:00",
            "lastUpdated": "2026-01-01 08:00:00",
            "name": "My Restaurant",
            "network": "google",
            "phone": "(619) 555-0134",
            "priceRange": "$$",
            "reviewCount": {
                "native": {
                    "total": 320,
                    "active": 312,
                    "hidden": 8
                },
                "available": {
                    "total": 320,
                    "active": 312,
                    "hidden": 8
                }
            },
            "slug": "my-listing-slug",
            "url": "https://maps.google.com/?cid=16256511492469774330",
            "website": "https://my-restaurant.com",
            "rawData": {
                "…": "everything else the platform shows"
            }
        },
        "reviews": [
            {
                "id": "ChdDSUhNMG9nS0VJQ0FnSUR4",
                "url": "https://www.google.com/maps/reviews/...",
                "text": "Great food and friendly staff. Will come back!",
                "language": "en",
                "timestamp": "2026-01-02 19:41:00",
                "editedAt": "2026-01-05 10:12:00",
                "rating": 5,
                "recommendation": null,
                "isHidden": false,
                "isDeleted": false,
                "author": {
                    "id": "1084...",
                    "name": "Jane D.",
                    "photo": "https://...",
                    "url": "https://..."
                },
                "photos": [
                    "https://..."
                ],
                "edits": [
                    {
                        "id": "ChdDSUhNMG9nS0VJQ0FnSUR4",
                        "text": "Good food.",
                        "timestamp": "2026-01-02 19:41:00",
                        "rating": 4
                    }
                ],
                "replies": [
                    {
                        "id": "r-1",
                        "text": "Thank you Jane, see you soon!",
                        "timestamp": "2026-01-03 09:00:00"
                    }
                ]
            }
        ],
        "returned": 1
    }
}

One rating scale: 1 to 5#

Platforms rate on different scales. Zembra maps every rating linearly onto 1–5, rounded to one decimal: on a 1–10 scale, 10 becomes 5, 8 becomes 4.1, 5 becomes 2.8 and 1 stays 1. The original value is still in rawData.

Hidden, edited, deleted#

  • Hidden reviews are on the platform but not shown by default, like Yelp's "not recommended" reviews. They're returned with isHidden: true.
  • Edited reviews keep their ID; earlier versions are listed in edits and editedAt tells you when.
  • Deleted reviews are never erased: they're flagged isDeleted: true and left out unless you ask with includeDeleted. If one reappears, it's restored.

rawData#

The standard fields cover what every platform has in common. rawData carries everything else a platform shows, exactly as it shows it: extra ratings, badges, platform-specific fields. Ask for it with fields[]=rawData on GET, or includeRawData for webhooks. It's free, but it can make payloads large.

Listing details, included #

Every response includes the listing (target): name, address, phone, website, categories, rating, review counts and more, at no extra cost. On POST it comes back immediately, so you know at once whether the listing exists (404) or can be reviewed (406), without waiting for a job.

Slugs can change #

Platforms sometimes change a listing's identifier or URL. Zembra keeps the old ones as aliases and resolves every variant to the same listing and the same job. The listing's uid never changes.

Use internalId #

Set internalId to your own reference, like your location ID. It's returned in every response and every webhook, carried over on renewals, and searchable with GET /reviews/jobs, so you can match data to your records without extra lookups.

Webhooks

Get changes pushed to you#

Set up your destinations once in your dashboard's API settings: your server, Slack, or email. Every monitoring job then delivers there, unless you set skipListeners.

Settings#

EventsNew reviews, edited reviews, new replies, deleted reviews, restored reviews
Payloadsummary (counts), changes (only what changed) or all (every review)
FrequencyRight away, or as a daily digest at your chosen time (UTC)
DeliveryJSON POST with the X-Zembra-Token header, 100 reviews per call
Retries5 attempts over about 1.5 hours; after that the webhook is paused and you're notified

One-off jobs deliver their reviews once, when the pull is done. Use reference and page to detect a delivery you already processed.

Payloads#

summary · Counts only: how many new, edited, replied, deleted and restored reviews. Light, ideal to trigger your own GET.
{
    "reference": "761154dc-...",
    "type": "reviews",
    "internalId": "location-1042",
    "format": "summary",
    "data": {
        "summary": {
            "countNewReviews": 3,
            "countEditedReviews": 1,
            "countNewReplies": 2,
            "countDeletedReviews": 0,
            "countRestoredReviews": 0
        },
        "job": {
            "network": "google",
            "slug": "my-listing-slug",
            "jobId": "0d6c7d22-1c7e-4bde-9a3c-5b1c3b2f1e10",
            "uid": "MWJmZTc5MDZjMzg5OGI3OGMyNjJiODNjOGJjMWI3Y2Q",
            "monitoring": "full",
            "skipListeners": false,
            "internalId": "location-1042",
            "requestedAt": "2026-01-01 08:00:00",
            "expiresAt": "2026-01-31 08:00:00",
            "includeRawData": false,
            "permalink": false,
            "cost": 470,
            "active": true,
            "autoRenew": true,
            "renewed": false
        }
    }
}
changes · Only the reviews that changed, grouped by event, with the listing details.
{
    "reference": "761154dc-...",
    "type": "reviews",
    "internalId": "location-1042",
    "format": "changes",
    "data": {
        "job": {
            "network": "google",
            "slug": "my-listing-slug",
            "jobId": "0d6c7d22-1c7e-4bde-9a3c-5b1c3b2f1e10",
            "uid": "MWJmZTc5MDZjMzg5OGI3OGMyNjJiODNjOGJjMWI3Y2Q",
            "monitoring": "full",
            "skipListeners": false,
            "internalId": "location-1042",
            "requestedAt": "2026-01-01 08:00:00",
            "expiresAt": "2026-01-31 08:00:00",
            "includeRawData": false,
            "permalink": false,
            "cost": 470,
            "active": true,
            "autoRenew": true,
            "renewed": false
        },
        "target": {
            "name": "My Restaurant",
            "…": "listing info"
        },
        "updates": {
            "newReviews": [
                {
                    "id": "ChdDSUhNMG9nS0VJQ0FnSUR4",
                    "url": "https://www.google.com/maps/reviews/...",
                    "text": "Great food and friendly staff. Will come back!",
                    "language": "en",
                    "timestamp": "2026-01-02 19:41:00",
                    "editedAt": "2026-01-05 10:12:00",
                    "rating": 5,
                    "recommendation": null,
                    "isHidden": false,
                    "isDeleted": false,
                    "author": {
                        "id": "1084...",
                        "name": "Jane D.",
                        "photo": "https://...",
                        "url": "https://..."
                    },
                    "photos": [
                        "https://..."
                    ],
                    "edits": [
                        {
                            "id": "ChdDSUhNMG9nS0VJQ0FnSUR4",
                            "text": "Good food.",
                            "timestamp": "2026-01-02 19:41:00",
                            "rating": 4
                        }
                    ],
                    "replies": [
                        {
                            "id": "r-1",
                            "text": "Thank you Jane, see you soon!",
                            "timestamp": "2026-01-03 09:00:00"
                        }
                    ]
                }
            ],
            "editedReviews": [],
            "newReplies": [],
            "deletedReviews": [],
            "restoredReviews": []
        }
    }
}
all · Every review of the listing after each check, split into pages of 100.
{
    "reference": "761154dc-...",
    "type": "reviews",
    "internalId": "location-1042",
    "format": "all",
    "pages": 4,
    "page": 1,
    "data": {
        "job": {
            "network": "google",
            "slug": "my-listing-slug",
            "jobId": "0d6c7d22-1c7e-4bde-9a3c-5b1c3b2f1e10",
            "uid": "MWJmZTc5MDZjMzg5OGI3OGMyNjJiODNjOGJjMWI3Y2Q",
            "monitoring": "full",
            "skipListeners": false,
            "internalId": "location-1042",
            "requestedAt": "2026-01-01 08:00:00",
            "expiresAt": "2026-01-31 08:00:00",
            "includeRawData": false,
            "permalink": false,
            "cost": 470,
            "active": true,
            "autoRenew": true,
            "renewed": false
        },
        "target": {
            "name": "My Restaurant",
            "…": "listing info"
        },
        "reviews": [
            {
                "id": "ChdDSUhNMG9nS0VJQ0FnSUR4",
                "url": "https://www.google.com/maps/reviews/...",
                "text": "Great food and friendly staff. Will come back!",
                "language": "en",
                "timestamp": "2026-01-02 19:41:00",
                "editedAt": "2026-01-05 10:12:00",
                "rating": 5,
                "recommendation": null,
                "isHidden": false,
                "isDeleted": false,
                "author": {
                    "id": "1084...",
                    "name": "Jane D.",
                    "photo": "https://...",
                    "url": "https://..."
                },
                "photos": [
                    "https://..."
                ],
                "edits": [
                    {
                        "id": "ChdDSUhNMG9nS0VJQ0FnSUR4",
                        "text": "Good food.",
                        "timestamp": "2026-01-02 19:41:00",
                        "rating": 4
                    }
                ],
                "replies": [
                    {
                        "id": "r-1",
                        "text": "Thank you Jane, see you soon!",
                        "timestamp": "2026-01-03 09:00:00"
                    }
                ]
            },
            "… up to 100 reviews per call"
        ],
        "returned": 100
    }
}

Performance

Fast, with no priority tiers#

What to expect #

The job and the listing come back instantly. Reviews usually follow within seconds; very large listings, with thousands of reviews, can take longer. Meanwhile, GET answers 206 with what's collected so far.

One pipeline for everyone #

There are no paid priority tiers and no queue per platform: every job, on every platform and level, runs through the same pipeline. The first pull of each new job goes ahead of routine checks, so new jobs start fast.

What can cause delays #

A platform slowing down or blocking automated access, a platform outage (see status.zembra.io; POST answers 503 meanwhile), very large listings, or platforms that limit how many reviews they show. A failed check is retried within an hour.

Rate limits #

100 job requests and 1,000 reads per minute, per account. Batches of up to 1,000 requests are available for large imports.

Supported platforms

131 platforms, one format#

FAQ

Common questions#

Do I need the business's login?

No. The Reviews API reads public reviews; no credentials or platform approval are needed. #

Do GET requests cost credits?

No. Reading jobs and reviews is free; you only pay to collect reviews. #

Is there a minimum or a commitment?

No. Pay per use, with no subscription, no minimum and no contract. New accounts get 10,000 free credits. #

Can I change a job's monitoring level?

Not on an existing job: delete it and create a new one at the level you want. #

How do I stop monitoring a listing?

Turn off autoRenew to stop at the end of the 30 days, or delete the job to stop immediately (no refund). #

Can I test without spending credits?

Yes. The sandbox at sandbox.zembra.io returns realistic dummy data and never touches your balance. #

Start today for free

Create an account and get 10,000 free credits: enough to pull about 8,400 reviews. No commitment, no minimum.