This API allows your Shopify app or website to fetch Google Reviews for any business on-demand. The API automatically caches data for 7 days per business to optimize API usage and provide fast responses.
Base URL: https://scraper.capula.co
GET /api/reviewsFetch Google Reviews for any business using its organizationId.
URL:
https://scraper.capula.co/api/reviews
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
organizationId |
YES | string | - | Google Maps organization ID |
count |
No | integer | 10 | Number of reviews to return (min: 1, max: 50) |
Example URL:
https://www.google.com/maps/place/Empire+State+Plumbing+Heating+%26+Air+Conditioning/@42.9354131,-73.8094899,17z/data=!3m1!4b1!4m6!3m5!1s0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d!8m2!3d42.9354131!4d-73.806715!16s%2Fg%2F11b6g2tcd1
1s:1s0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d
1s prefix to get your organizationId:0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d
GET https://scraper.capula.co/api/search-business?query=Empire State Plumbing NY
Note: Currently returns instructions. Full search functionality coming soon.
GET https://scraper.capula.co/api/reviews?organizationId=0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d&count=10
GET https://scraper.capula.co/api/reviews?organizationId=0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d&count=20
GET https://scraper.capula.co/api/reviews?organizationId=0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d&count=50
Status Code: 200 OK
Response Body:
{
"data": [
{
"rating": 5,
"comment": "I called for help with huge sump pump issues and \"Empire Plumbing\" sent me an awesome, knowledgeable, dependable young man (Logan Darling), who worked very hard and very clean, while eliminating all my sump pump issues. Big job so Logan had help, Michael (?)\\n\\nI feel both men did a beautiful job! I really could not be happier with the degree of professionalism I encountered from everyone at \"Empire Plumbing\".\\nThanks Logan!\\nThanks Michael!\\nAlisha and Everyone I spoke with (sorry can't remember).\\nDennis A. Moffre (homeowner)",
"date": "2025-06-16T18:27:39.118Z",
"author": null,
"photos": [
"https://lh3.googleusercontent.com/geougc-cs/AB3l90B37O5-RizlL4gp_RoiouWEkvtlPUYOy8NjhDMZeZdblwdoIYgp3pAjhCYd92m-Y7xw0rHmOu1JTQ9-nDcGtIC_Hq7UoO4afp2CiuhxekJwjr-DkTUBrTJ13F52Ootc5wY0rkCcndkYNjE"
],
"owner_response": "Thank you so much, Dennis, for your detailed and kind feedback! We're thrilled to hear that Logan, Mike, and the rest of our team provided you with knowledgeable, dependable, and professional service."
},
{
"rating": 5,
"comment": "I was having issues with my central air unit at my new build house. The unit was installed by a contractor other than Empire State plumbing. Jimmy came out the same day i called and was extremely kind and professional. He had the issue fixed quickly and explained the whole process as he went. I'm very glad I called this company and am a lifelong customer now.",
"date": "2025-07-07T19:50:50.771Z",
"author": null,
"photos": [
"https://lh3.googleusercontent.com/geougc-cs/AB3l90DlJgCfxnx-5NqSF31tgSti1e8x4HWHpOGh43Mq7yYE6CULxoGXDzWehjwLI7kKNLcXRMemru8lY1AJWu7bjEh8_No8FhulySDqlO-Ejufa7KvT_x8E1DMhrwHqWqCXbpP8QmT23sAmJhg"
],
"owner_response": "Eric, thank you for the amazing review! We're so glad Jimmy was able to help out quickly and make a great impression."
}
],
"count": 10,
"organizationId": "0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d",
"cached": true,
"scraped_at": "2025-10-09T01:13:45.123Z",
"cache_expires_in_days": 6.9
}
Response Fields:
| Field | Type | Description |
|---|---|---|
data |
array | Array of Google reviews |
data[].rating |
integer | Star rating (1-5) |
data[].comment |
string | Review text/comment |
data[].date |
string | When review was published (ISO 8601) |
data[].author |
string/null | Reviewer's name (may be null for privacy) |
data[].photos |
array | URLs to review photos (if any) |
data[].owner_response |
string/null | Business owner's response (if any) |
count |
integer | Number of reviews returned |
organizationId |
string | The business organization ID |
cached |
boolean | true if served from cache, false if freshly scraped |
scraped_at |
string | When the data was originally scraped |
cache_expires_in_days |
float | Days remaining until cache expires |
{
"error": "Missing parameter",
"message": "The \"organizationId\" parameter is required",
"example": "/api/reviews?organizationId=0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d&count=10",
"help": "Find organizationId from Google Maps URL or use Google Maps Place ID"
}
{
"error": "Invalid count",
"message": "Count must be between 1 and 50",
"provided": 100
}
{
"error": "No reviews found",
"message": "Could not fetch reviews for organization 0xinvalidid",
"organizationId": "0xinvalidid"
}
{
"error": "API request failed",
"message": "Google Reviews API returned status 403",
"organizationId": "0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d"
}
"cached": falseThis counts as 1 API request
Subsequent Requests (within 7 days): All requests for the same organizationId will:
"cached": trueThis does NOT count as an API request
After 7 Days: The cache expires and the next request will fetch fresh data again.
Example Scenario: - Website A requests reviews for "Starbucks NYC" → 1 API request (fresh scrape) - Website A requests again 2 days later → 0 API requests (cached) - Website B requests "Starbucks NYC" 5 days later → 0 API requests (cached) - Website A requests after 8 days → 1 API request (cache expired, fresh scrape) - Total API requests used: 2
async function getGoogleReviews(organizationId, count = 10) {
const url = `https://scraper.capula.co/api/reviews?organizationId=${organizationId}&count=${count}`;
try {
const response = await fetch(url);
const data = await response.json();
if (response.ok) {
console.log(`Got ${data.count} reviews for business ${data.organizationId}`);
console.log(`Cached: ${data.cached}`);
console.log(`Cache expires in: ${data.cache_expires_in_days} days`);
// Use the reviews
data.data.forEach(review => {
console.log(`⭐ ${review.rating}/5 - ${review.comment.substring(0, 100)}...`);
console.log(`📅 ${review.date}`);
if (review.owner_response) {
console.log(`💬 Owner: ${review.owner_response.substring(0, 100)}...`);
}
if (review.photos.length > 0) {
console.log(`📷 Photos: ${review.photos.length}`);
}
});
return data.data;
} else {
console.error('Error:', data.error, data.message);
return null;
}
} catch (error) {
console.error('Request failed:', error);
return null;
}
}
// Usage:
getGoogleReviews('0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d', 10);
const axios = require('axios');
async function getGoogleReviews(organizationId, count = 10) {
try {
const response = await axios.get('https://scraper.capula.co/api/reviews', {
params: {
organizationId: organizationId,
count: count
}
});
console.log(`Got ${response.data.count} reviews`);
console.log(`Cached: ${response.data.cached}`);
return response.data.data;
} catch (error) {
if (error.response) {
console.error('Error:', error.response.data.error);
console.error('Message:', error.response.data.message);
} else {
console.error('Request failed:', error.message);
}
return null;
}
}
// Usage:
getGoogleReviews('0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d', 10);
import requests
def get_google_reviews(organization_id, count=10):
url = 'https://scraper.capula.co/api/reviews'
params = {
'organizationId': organization_id,
'count': count
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
print(f"Got {data['count']} reviews")
print(f"Cached: {data['cached']}")
print(f"Cache expires in: {data['cache_expires_in_days']} days")
for review in data['data']:
print(f"⭐ {review['rating']}/5")
print(f"💬 {review['comment'][:100]}...")
print(f"📅 {review['date']}")
if review['owner_response']:
print(f"💼 Owner: {review['owner_response'][:100]}...")
print("---")
return data['data']
except requests.exceptions.HTTPError as e:
error_data = e.response.json()
print(f"Error: {error_data.get('error')}")
print(f"Message: {error_data.get('message')}")
return None
except Exception as e:
print(f"Request failed: {e}")
return None
# Usage:
get_google_reviews('0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d', 10)
<?php
function getGoogleReviews($organizationId, $count = 10) {
$url = 'https://scraper.capula.co/api/reviews?' . http_build_query([
'organizationId' => $organizationId,
'count' => $count
]);
$response = file_get_contents($url);
$data = json_decode($response, true);
if ($data) {
echo "Got {$data['count']} reviews\\n";
echo "Cached: " . ($data['cached'] ? 'yes' : 'no') . "\\n";
foreach ($data['data'] as $review) {
echo "⭐ {$review['rating']}/5\\n";
echo "💬 {$review['comment']}\\n";
echo "📅 {$review['date']}\\n";
if (!empty($review['owner_response'])) {
echo "💼 Owner: {$review['owner_response']}\\n";
}
echo "---\\n";
}
return $data['data'];
}
return null;
}
// Usage:
getGoogleReviews('0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d', 10);
?>
# Get 10 reviews
curl "https://scraper.capula.co/api/reviews?organizationId=0x89de0b0b3cdbe1d3%3A0x27519164cd8d3b5d&count=10"
# Get 20 reviews with pretty JSON
curl "https://scraper.capula.co/api/reviews?organizationId=0x89de0b0b3cdbe1d3%3A0x27519164cd8d3b5d&count=20" | jq
<div id="reviews-container"></div>
<script>
async function displayReviews(organizationId) {
const response = await fetch(`https://scraper.capula.co/api/reviews?organizationId=${organizationId}&count=10`);
const data = await response.json();
const container = document.getElementById('reviews-container');
data.data.forEach(review => {
const reviewEl = document.createElement('div');
reviewEl.className = 'review';
reviewEl.innerHTML = `
<div class="rating">${'⭐'.repeat(review.rating)}</div>
<p class="comment">${review.comment}</p>
<p class="date">${new Date(review.date).toLocaleDateString()}</p>
${review.owner_response ? `<div class="owner-response">
<strong>Business Response:</strong> ${review.owner_response}
</div>` : ''}
${review.photos.length > 0 ? `<div class="photos">
${review.photos.map(p => `<img src="${p}" alt="Review photo" />`).join('')}
</div>` : ''}
`;
container.appendChild(reviewEl);
});
}
// Usage:
displayReviews('0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d');
</script>
function StarRating({ rating }) {
return (
<div className="star-rating">
{[1, 2, 3, 4, 5].map(star => (
<span key={star} className={star <= rating ? 'filled' : 'empty'}>
⭐
</span>
))}
</div>
);
}
function ReviewCard({ review }) {
return (
<div className="review-card">
<StarRating rating={review.rating} />
<p className="comment">{review.comment}</p>
<p className="date">{new Date(review.date).toLocaleDateString()}</p>
{review.owner_response && (
<div className="owner-response">
<strong>Business Response:</strong>
<p>{review.owner_response}</p>
</div>
)}
{review.photos.length > 0 && (
<div className="review-photos">
{review.photos.map((photo, i) => (
<img key={i} src={photo} alt="Review" />
))}
</div>
)}
</div>
);
}
https://scraper.capula.co/health
Response:
{
"status": "healthy",
"timestamp": "2025-10-09T01:13:45Z"
}
https://scraper.capula.co/api/reviews?organizationId=0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d&count=5
The API response includes helpful fields to monitor cache usage:
{
"cached": true,
"scraped_at": "2025-10-09T01:13:45Z",
"cache_expires_in_days": 6.5
}
cached: true → Data served from cache (no API request used)cached: false → Fresh scrape (1 API request used)cache_expires_in_days → Time remaining before next scrape is neededcached field to know if you're using API quotacache_expires_in_days to schedule refreshes efficientlyWhen merchant sets up your app:
// In your Shopify app setup
const merchantSettings = {
businessName: "Empire State Plumbing",
googleMapsUrl: "https://www.google.com/maps/place/...",
organizationId: "0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d", // Extracted from URL
reviewsEnabled: true
};
// Save to your database
await saveMerchantSettings(merchantSettings);
// When customer views product/page with reviews
async function loadReviews(merchantId) {
const settings = await getMerchantSettings(merchantId);
if (settings.reviewsEnabled) {
const response = await fetch(
`https://scraper.capula.co/api/reviews?organizationId=${settings.organizationId}&count=10`
);
const data = await response.json();
// Cache locally for even faster access
await cacheReviews(merchantId, data.data);
return data.data;
}
}
<!-- In your Shopify theme liquid template -->
<div class="google-reviews">
<h3>Customer Reviews</h3>
<div id="reviews-list"></div>
</div>
<script>
loadReviews('{{ shop.id }}').then(reviews => {
displayReviews(reviews);
});
</script>
0xHEX1:0xHEX2 (e.g., 0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d)1s prefix)author field may be null for anonymous reviews[] if no photos attachedowner_response is null if business hasn't repliedcached field in responsenull or missingAPI Status: https://scraper.capula.co/health Main Documentation: https://scraper.capula.co/docs Shopify Guide: https://scraper.capula.co/docs/shopify Base URL: https://scraper.capula.co
For technical support or API issues, check the RapidAPI dashboard or contact your system administrator.
https://scraper.capula.co/api/reviews?organizationId=YOUR_ID&count=5cached field to track API usage| Scenario | Response Time |
|---|---|
| Cached data (within 7 days) | <100ms |
| Fresh scrape (first time) | 5-10 seconds |
| Business not found | ~3-5 seconds |
One endpoint does everything:
GET https://scraper.capula.co/api/reviews?organizationId={id}&count={num}
That's it! The API handles: - ✅ Fetching reviews from Google Maps - ✅ Caching for 7 days - ✅ Parsing ratings, comments, photos - ✅ Including owner responses - ✅ Rate limit management - ✅ Error handling
Your Shopify app just makes a simple HTTP GET request and receives ready-to-display Google Reviews.