This API allows your Shopify app to fetch: - Instagram posts (photos or reels) from any public Instagram user on-demand - Google Reviews for any business with a Google Maps profile
The API automatically caches data to stay within free API limits: - Instagram: 24-hour cache (35 requests/day limit) - Google Reviews: 7-day cache (500 requests/month limit)
Base URL: https://scraper.capula.co
GET /api/scrapeFetch Instagram photos or reels for any username.
GET /api/reviewsFetch Google Reviews for any business.
URL:
https://scraper.capula.co/api/scrape
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
username |
YES | string | - | Instagram username (without @) |
count |
No | integer | 7 | Number of posts to return (min: 1, max: 50) |
type |
No | string | photos |
Type of media: photos or reels |
GET https://scraper.capula.co/api/scrape?username=pascuccicoffee&count=7&type=photos
GET https://scraper.capula.co/api/scrape?username=pascuccicoffee&count=10&type=reels
GET https://scraper.capula.co/api/scrape?username=nike&count=5
Status Code: 200 OK
Response Body:
{
"data": [
{
"media_url": "https://scraper.capula.co/media/pascuccicoffee_123456.webp",
"permalink": "https://www.instagram.com/p/ABC123/",
"timestamp": "2025-10-08T09:30:00Z",
"caption": "Fresh coffee this morning! #coffee",
"media_type": "photo"
},
{
"media_url": "https://scraper.capula.co/media/pascuccicoffee_789012.webp",
"permalink": "https://www.instagram.com/p/DEF456/",
"timestamp": "2025-10-07T14:20:00Z",
"caption": "New blend available now",
"media_type": "photo"
}
],
"count": 2,
"username": "pascuccicoffee",
"type": "photos",
"cached": true,
"scraped_at": "2025-10-09T00:36:13Z",
"cache_expires_in_hours": 18.5
}
Response Fields:
| Field | Type | Description |
|---|---|---|
data |
array | Array of Instagram posts |
data[].media_url |
string | Direct URL to the optimized image (WebP format) |
data[].permalink |
string | Link to the original Instagram post |
data[].timestamp |
string | When the post was published (ISO 8601 format) |
data[].caption |
string | Post caption text (truncated to 200 chars) |
data[].media_type |
string | Type of media: "photo" or "reel" |
count |
integer | Number of posts returned |
username |
string | Instagram username that was scraped |
type |
string | Type of media requested: "photos" or "reels" |
cached |
boolean | true if served from cache, false if freshly scraped |
scraped_at |
string | When the data was originally scraped |
cache_expires_in_hours |
float | Hours remaining until cache expires |
{
"error": "Missing parameter",
"message": "The \"username\" parameter is required",
"example": "/api/scrape?username=pascuccicoffee&count=7&type=photos"
}
{
"error": "Invalid type",
"message": "Type must be \"photos\" or \"reels\"",
"example": "/api/scrape?username=pascuccicoffee&count=7&type=photos"
}
{
"error": "Invalid count",
"message": "Count must be between 1 and 50",
"provided": 100
}
{
"error": "No posts found",
"message": "Could not fetch posts for @invaliduser123. Username may not exist or may be private.",
"username": "invaliduser123"
}
{
"error": "Internal server error",
"message": "Error details here..."
}
"cached": falseThis counts as 1 API request
Subsequent Requests (within 24 hours): All requests for the same username will:
"cached": trueThis does NOT count as an API request
After 24 Hours: The cache expires and the next request will fetch fresh data again.
Example Scenario:
- Website A requests @pascuccicoffee at 9 AM → 1 API request (fresh scrape)
- Website A requests @pascuccicoffee at 3 PM → 0 API requests (cached)
- Website B requests @pascuccicoffee at 6 PM → 0 API requests (cached)
- Website A requests @nike at 10 AM → 1 API request (fresh scrape)
- Total API requests used: 2 out of 35
async function getInstagramPosts(username, count = 7, type = 'photos') {
const url = `https://scraper.capula.co/api/scrape?username=${username}&count=${count}&type=${type}`;
try {
const response = await fetch(url);
const data = await response.json();
if (response.ok) {
console.log(`Got ${data.count} ${type} from @${username}`);
console.log(`Cached: ${data.cached}`);
console.log(`Cache expires in: ${data.cache_expires_in_hours} hours`);
// Use the posts
data.data.forEach(post => {
console.log(`Image: ${post.media_url}`);
console.log(`Caption: ${post.caption}`);
console.log(`Link: ${post.permalink}`);
});
return data.data;
} else {
console.error('Error:', data.error, data.message);
return null;
}
} catch (error) {
console.error('Request failed:', error);
return null;
}
}
// Usage examples:
getInstagramPosts('pascuccicoffee', 7, 'photos');
getInstagramPosts('nike', 10, 'reels');
const axios = require('axios');
async function getInstagramPosts(username, count = 7, type = 'photos') {
try {
const response = await axios.get('https://scraper.capula.co/api/scrape', {
params: {
username: username,
count: count,
type: type
}
});
console.log(`Got ${response.data.count} ${type} from @${username}`);
return response.data.data;
} catch (error) {
if (error.response) {
// Server responded with error
console.error('Error:', error.response.data.error);
console.error('Message:', error.response.data.message);
} else {
// Network error
console.error('Request failed:', error.message);
}
return null;
}
}
// Usage:
getInstagramPosts('pascuccicoffee', 7, 'photos');
import requests
def get_instagram_posts(username, count=7, media_type='photos'):
url = 'https://scraper.capula.co/api/scrape'
params = {
'username': username,
'count': count,
'type': media_type
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
print(f"Got {data['count']} {media_type} from @{username}")
print(f"Cached: {data['cached']}")
print(f"Cache expires in: {data['cache_expires_in_hours']} hours")
for post in data['data']:
print(f"Image: {post['media_url']}")
print(f"Caption: {post['caption']}")
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_instagram_posts('pascuccicoffee', 7, 'photos')
<?php
function getInstagramPosts($username, $count = 7, $type = 'photos') {
$url = 'https://scraper.capula.co/api/scrape?' . http_build_query([
'username' => $username,
'count' => $count,
'type' => $type
]);
$response = file_get_contents($url);
$data = json_decode($response, true);
if ($data) {
echo "Got {$data['count']} {$type} from @{$username}\n";
echo "Cached: " . ($data['cached'] ? 'yes' : 'no') . "\n";
foreach ($data['data'] as $post) {
echo "Image: {$post['media_url']}\n";
echo "Caption: {$post['caption']}\n";
}
return $data['data'];
}
return null;
}
// Usage:
getInstagramPosts('pascuccicoffee', 7, 'photos');
?>
# Get 7 photos from @pascuccicoffee
curl "https://scraper.capula.co/api/scrape?username=pascuccicoffee&count=7&type=photos"
# Get 10 reels from @nike
curl "https://scraper.capula.co/api/scrape?username=nike&count=10&type=reels"
The media_url field contains a direct link to an optimized WebP image. You can use it directly in HTML:
<img src="https://scraper.capula.co/media/pascuccicoffee_123456.webp" alt="Instagram post">
Image Details: - Format: WebP (modern, compressed format) - Max Size: 1200px (maintains aspect ratio) - Quality: 85% (high quality, optimized file size) - Cache: Images are cached with 12-hour headers
https://scraper.capula.co/health
Response:
{
"status": "healthy",
"timestamp": "2025-10-09T00:36:34Z"
}
https://scraper.capula.co/api/scrape?username=pascuccicoffee&count=3&type=photos
The API response includes helpful fields to monitor cache usage:
{
"cached": true,
"scraped_at": "2025-10-09T00:36:13Z",
"cache_expires_in_hours": 18.5
}
cached: true → Data served from cache (no API request used)cached: false → Fresh scrape (1 API request used)cache_expires_in_hours → Time remaining before next scrape is neededcached field to know if you're using API quotacache_expires_in_hours to schedule refreshes efficientlytype=photos → Returns only photo posts (no videos/reels)type=reels → Returns only Instagram Reelshttps://scraper.capula.co/media/API Status: https://scraper.capula.co/health Documentation: https://scraper.capula.co/docs Base URL: https://scraper.capula.co
For technical support or API upgrades, please contact your system administrator.
https://scraper.capula.co/healthhttps://scraper.capula.co/api/scrape?username=pascuccicoffee&count=3&type=photoscached field to track API usage| Scenario | Response Time |
|---|---|
| Cached data (within 24h) | <100ms |
| Fresh scrape (first time) | 5-10 seconds |
| Username not found | ~3-5 seconds |
GET /api/reviewsFetch Google Reviews for any business with a Google Maps profile.
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 (min: 1, max: 50) |
The organizationId is extracted from the Google Maps URL for your business:
Step-by-Step Guide:
1s in the URLExample URL:
https://www.google.com/maps/place/...data=!3m1!4b1!4m6!3m5!1s0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d!...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the organizationId
The organizationId in this example: 0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d
GET https://scraper.capula.co/api/reviews?organizationId=0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d&count=10
{
"data": [
{
"rating": 5,
"comment": "Excellent service! Very professional and quick response.",
"date": "2025-10-01T14:30:00Z",
"author": "John Smith",
"photos": [],
"owner_response": "Thank you for your kind words!"
},
{
"rating": 4,
"comment": "Good quality work, would recommend.",
"date": "2025-09-28T10:15:00Z",
"author": "Jane Doe",
"photos": ["https://...photo1.jpg"],
"owner_response": null
}
],
"count": 2,
"organizationId": "0x89de0b0b3cdbe1d3:0x27519164cd8d3b5d",
"cached": true,
"scraped_at": "2025-10-09T01:24:27Z",
"cache_expires_in_days": 5.2
}
| Field | Type | Description |
|---|---|---|
data |
array | Array of Google reviews |
data[].rating |
integer | Star rating (1-5) |
data[].comment |
string | Review text |
data[].date |
string | When the review was posted (ISO 8601) |
data[].author |
string | Reviewer's name |
data[].photos |
array | URLs of review photos (if any) |
data[].owner_response |
string or null | Business owner's response |
count |
integer | Number of reviews returned |
organizationId |
string | The organization ID that was queried |
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 |
Why 7 days? Reviews don't change as frequently as Instagram posts, so we cache longer to save API quota.
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`);
console.log(`Cached: ${data.cached}`);
console.log(`Cache expires in: ${data.cache_expires_in_days} days`);
data.data.forEach(review => {
console.log(`⭐ ${review.rating}/5 - ${review.author}`);
console.log(`Comment: ${review.comment}`);
if (review.owner_response) {
console.log(`Owner replied: ${review.owner_response}`);
}
});
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);
Missing organizationId (400 Bad Request):
{
"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"
}
Invalid Count (400 Bad Request):
{
"error": "Invalid count",
"message": "Count must be between 1 and 50",
"provided": 100
}
No Reviews Found (404 Not Found):
{
"error": "No reviews found",
"message": "Could not fetch reviews for organization 0xinvalid",
"organizationId": "0xinvalid"
}
cached field to monitor API quota usageTwo main endpoints for your Shopify app:
GET https://scraper.capula.co/api/scrape?username={user}&count={num}&type={photos|reels}
GET https://scraper.capula.co/api/reviews?organizationId={id}&count={num}
The API handles: - ✅ Fetching data from Instagram and Google - ✅ Smart caching (24h for Instagram, 7d for Reviews) - ✅ Serving optimized images - ✅ Rate limit management - ✅ Error handling
Your Shopify app just makes simple HTTP GET requests and receives ready-to-use data.