Locations
📋 Overview
The Locations API allows you to retrieve all business locations associated with a Google Business Profile account. Only verified locations with Place IDs are returned, ensuring you only work with active, manageable locations.
🔍 List Locations
Endpoint
GET /v1/e/integrations/google/business/account/locations
Authentication
Authorization: Bearer {jwt_token}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token | String | ✅ Yes | Token ID from OAuth callback |
nextPageToken | String | ❌ No | Pagination token from previous response |
Request Example
curl -X GET "https://api.dashclicks.com/v1/e/integrations/google/business/account/locations?token=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response Format
{
"success": true,
"locations": [
{
"name": "accounts/1234567890/locations/9876543210",
"id": "9876543210",
"title": "DashClicks Downtown Office",
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"metadata": {
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4"
}
},
{
"name": "accounts/1234567890/locations/1234567891",
"id": "1234567891",
"title": "DashClicks Westside Branch",
"placeId": "ChIJ3S-JXmauEmsRUcIaWtf3dI8",
"metadata": {
"placeId": "ChIJ3S-JXmauEmsRUcIaWtf3dI8"
}
}
],
"nextPageToken": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
success | Boolean | Request success status |
locations | Array | List of location objects |
locations[].name | String | Full resource name (accounts/{accountId}/locations/{locationId}) |
locations[].id | String | Location ID (extracted from name) |
locations[].title | String | Business location name |
locations[].placeId | String | Google Places ID for this location |
locations[].metadata.placeId | String | Place ID from metadata (same as placeId) |
nextPageToken | String|null | Token for next page (null if last page) |
🔄 How It Works
1. Token Retrieval
const businessToken = await GoogleBusinessToken.findById(token);
if (!businessToken) {
return res.json({
success: false,
message: 'Invalid token provided.',
});
}
2. Access Token Refresh
const { refresh_token } = businessToken;
const data = {
refresh_token: refresh_token,
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
grant_type: 'refresh_token',
};
const resp = await axios.post('https://oauth2.googleapis.com/token', data);
const { access_token } = resp.data;
Why refresh every time? Access tokens expire after ~1 hour, so we fetch a fresh token on each request.
3. Fetch Locations
const headers = {
Authorization: `Bearer ${access_token}`,
};
const locationRes = await axios.get(
`https://mybusinessbusinessinformation.googleapis.com/v1/accounts/-/locations?readMask=name,metadata,title${
req.query.nextPageToken ? `&pageToken=${req.query.nextPageToken}` : ''
}`,
{ headers },
);
API URL Breakdown:
- Base:
https://mybusinessbusinessinformation.googleapis.com/v1 - Endpoint:
/accounts/-/locations-means "all accounts accessible by this user"
- readMask:
name,metadata,title- Specifies which fields to return (reduces payload size)
- pageToken: For pagination (optional)
4. Filter Verified Locations
let locations = locationRes.data.locations || [];
// Filter for only individual locations with a placeId (Verified Business)
locations = locations.filter(location => location.metadata?.placeId);
Why filter? Unverified or duplicate locations don't have placeId and can't be managed through the API.
5. Transform Response
locations = locations.map(location => {
location.id = location.name.split('/')[1]; // Extract location ID
location.placeId = location.metadata?.placeId; // Flatten placeId
return location;
});
const nextPageToken = locationRes.data.nextPageToken;
res.json({
success: true,
locations,
nextPageToken: nextPageToken ? nextPageToken : null,
});
📄 Pagination
Why Pagination?
Businesses with multiple locations (franchises, chains) may have 100+ locations. Google returns results in pages of ~100 locations.
First Page Request
curl -X GET "https://api.dashclicks.com/v1/e/integrations/google/business/account/locations?token=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer {jwt_token}"
Subsequent Page Request
curl -X GET "https://api.dashclicks.com/v1/e/integrations/google/business/account/locations?token=507f1f77bcf86cd799439011&nextPageToken=CiQ2MDE3..." \
-H "Authorization: Bearer {jwt_token}"
Pagination Logic
let allLocations = [];
let nextPageToken = null;
do {
const url = `/v1/e/integrations/google/business/account/locations?token=${token}${
nextPageToken ? `&nextPageToken=${nextPageToken}` : ''
}`;
const response = await axios.get(url, {
headers: { Authorization: `Bearer ${jwt_token}` },
});
allLocations = [...allLocations, ...response.data.locations];
nextPageToken = response.data.nextPageToken;
} while (nextPageToken); // Continue until no more pages
console.log(`Total locations: ${allLocations.length}`);
🎯 Use Cases
1. List All Locations
// Fetch all locations for a connected Google Business account
const tokenId = '507f1f77bcf86cd799439011'; // From OAuth callback
const response = await axios.get(
`/v1/e/integrations/google/business/account/locations?token=${tokenId}`,
{
headers: { Authorization: `Bearer ${jwt_token}` },
},
);
if (response.data.success) {
const locations = response.data.locations;
console.log(`Found ${locations.length} verified locations`);
locations.forEach(location => {
console.log(`- ${location.title} (ID: ${location.id})`);
});
}
2. Extract Place IDs for Google Places API
// Get Place IDs to fetch reviews, photos, ratings from Google Places API
const response = await axios.get(
`/v1/e/integrations/google/business/account/locations?token=${tokenId}`,
{
headers: { Authorization: `Bearer ${jwt_token}` },
},
);
const placeIds = response.data.locations.map(loc => loc.placeId);
// Use with Google Places API
for (const placeId of placeIds) {
const placeDetails = await fetchGooglePlaceDetails(placeId);
console.log(`${placeDetails.name}: ${placeDetails.rating} stars`);
}
3. Paginated Retrieval
// Fetch all locations across multiple pages
async function getAllLocations(tokenId, jwtToken) {
let allLocations = [];
let nextPageToken = null;
do {
const params = { token: tokenId };
if (nextPageToken) {
params.nextPageToken = nextPageToken;
}
const response = await axios.get('/v1/e/integrations/google/business/account/locations', {
params,
headers: { Authorization: `Bearer ${jwtToken}` },
});
if (!response.data.success) {
throw new Error('Failed to fetch locations');
}
allLocations = [...allLocations, ...response.data.locations];
nextPageToken = response.data.nextPageToken;
console.log(
`Fetched ${response.data.locations.length} locations (total: ${allLocations.length})`,
);
} while (nextPageToken);
return allLocations;
}
// Usage
const locations = await getAllLocations('507f1f77bcf86cd799439011', jwt_token);
console.log(`Total verified locations: ${locations.length}`);
4. Location Dropdown for UI
// Build a dropdown list for users to select a location
const response = await axios.get(
`/v1/e/integrations/google/business/account/locations?token=${tokenId}`,
{
headers: { Authorization: `Bearer ${jwt_token}` },
},
);
const locationOptions = response.data.locations.map(loc => ({
value: loc.id,
label: loc.title,
placeId: loc.placeId,
}));
// Render in UI
<Select options={locationOptions} placeholder="Select a location..." />;
5. Sync Locations to Database
// Periodically sync Google Business locations to your database
async function syncLocations(accountId, tokenId, jwtToken) {
const response = await axios.get(
`/v1/e/integrations/google/business/account/locations?token=${tokenId}`,
{
headers: { Authorization: `Bearer ${jwtToken}` },
},
);
if (!response.data.success) {
throw new Error('Failed to fetch locations');
}
const locations = response.data.locations;
// Store in database
for (const location of locations) {
await LocationModel.findOneAndUpdate(
{
accountId: accountId,
googleLocationId: location.id,
},
{
accountId: accountId,
googleLocationId: location.id,
title: location.title,
placeId: location.placeId,
lastSynced: new Date(),
},
{ upsert: true },
);
}
console.log(`Synced ${locations.length} locations to database`);
}
⚠️ Error Handling
1. Invalid Token ID
Response:
{
"success": false,
"message": "Invalid token provided."
}
Cause: Token ID doesn't exist in database
Solution: Use the token ID returned from OAuth callback
2. Token Invalidated
Response:
{
"success": false,
"errno": 400,
"message": "TOKEN_INVALIDATED"
}
Cause: User revoked access or token expired
Solution: Re-authenticate via /auth/login endpoint
try {
const response = await axios.get(url, { headers });
} catch (error) {
if (error.response?.data?.message === 'TOKEN_INVALIDATED') {
// Redirect to re-authenticate
window.location.href = `/v1/e/integrations/google/business/auth/login?forward_url=${encodeURIComponent(
window.location.href,
)}`;
}
}
3. API Rate Limit Exceeded
Response:
{
"error": {
"code": 429,
"message": "Quota exceeded for quota metric 'Queries' and limit 'Queries per day'",
"status": "RESOURCE_EXHAUSTED"
}
}
Cause: Exceeded Google API quota
Solution:
- Implement caching to reduce API calls
- Request quota increase from Google Cloud Console
4. No Locations Found
Response:
{
"success": true,
"locations": [],
"nextPageToken": null
}
Causes:
- No verified locations exist
- User doesn't have access to any locations
- All locations are unverified (filtered out)
Solution: Guide user to verify their business on Google Business Profile
🔍 Location Fields Deep Dive
name
Format: accounts/{accountId}/locations/{locationId}
Example: accounts/1234567890/locations/9876543210
Usage: Full resource identifier for Google API operations
// Extract account ID
const accountId = location.name.split('/')[1]; // "1234567890"
// Extract location ID
const locationId = location.name.split('/')[3]; // "9876543210"
id
Format: Numeric string
Example: "9876543210"
Usage: Shorthand location identifier (extracted from name)
// Already extracted in response
console.log(location.id); // "9876543210"
title
Format: Free text
Example: "DashClicks Downtown Office"
Usage: Display name for the location
placeId
Format: Google Places ID (starts with ChIJ)
Example: "ChIJN1t_tDeuEmsRUsoyG83frY4"
Usage:
- Link to Google Maps
- Fetch reviews via Google Places API
- Get place details (address, phone, hours, etc.)
Google Maps URL:
const mapsUrl = `https://www.google.com/maps/place/?q=place_id:${location.placeId}`;
metadata.placeId
Note: Same as top-level placeId (added for convenience)
🛠️ API Read Mask
What is readMask?
A field mask that limits which fields Google returns in the API response. This reduces payload size and speeds up requests.
Current Read Mask
readMask=name,metadata,title
Available Fields (Not Currently Requested)
storeCode- Store/location codelanguageCode- Primary languagephoneNumbers- Phone numberscategories- Business categoriesstorefrontAddress- Physical addresswebsiteUri- Website URLregularHours- Business hoursprofile- Business description
Expanding Read Mask
To get more fields, update the controller:
const locationRes = await axios.get(
`https://mybusinessbusinessinformation.googleapis.com/v1/accounts/-/locations?readMask=name,metadata,title,phoneNumbers,storefrontAddress,websiteUri`,
{ headers },
);
Response with expanded mask:
{
"name": "accounts/1234567890/locations/9876543210",
"title": "DashClicks Downtown Office",
"phoneNumbers": {
"primaryPhone": "+1-555-123-4567"
},
"storefrontAddress": {
"addressLines": ["123 Main St"],
"locality": "Los Angeles",
"administrativeArea": "CA",
"postalCode": "90001",
"regionCode": "US"
},
"websiteUri": "https://www.dashclicks.com",
"metadata": {
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4"
}
}
💡 Best Practices
1. Handle Pagination
Always check for nextPageToken and fetch all pages if you need complete location lists:
let allLocations = [];
let hasMorePages = true;
let nextPageToken = null;
while (hasMorePages) {
const response = await fetchLocations(token, nextPageToken);
allLocations = [...allLocations, ...response.locations];
if (response.nextPageToken) {
nextPageToken = response.nextPageToken;
} else {
hasMorePages = false;
}
}
2. Cache Location Data
Location data changes infrequently. Cache results to reduce API calls:
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
const cacheKey = `google_business_locations_${accountId}`;
const cached = await redis.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return JSON.parse(cached.data);
}
// Fetch fresh data
const locations = await fetchLocations(tokenId, jwtToken);
await redis.set(
cacheKey,
JSON.stringify({
data: locations,
timestamp: Date.now(),
}),
);
3. Handle Token Invalidation Gracefully
async function fetchLocationsWithRetry(tokenId, jwtToken, forward_url) {
try {
return await fetchLocations(tokenId, jwtToken);
} catch (error) {
if (error.response?.data?.message === 'TOKEN_INVALIDATED') {
// Clear stored token
localStorage.removeItem('googleBusinessToken');
// Redirect to re-authenticate
window.location.href = `/v1/e/integrations/google/business/auth/login?forward_url=${encodeURIComponent(
forward_url,
)}`;
}
throw error;
}
}
4. Validate Place IDs
Before using Place IDs with other APIs, verify they're valid:
const locations = response.data.locations;
// All locations returned have placeIds (already filtered)
const validPlaceIds = locations
.filter(loc => loc.placeId && loc.placeId.startsWith('ChIJ'))
.map(loc => loc.placeId);
5. Show Loading States
Location fetches can take 2-5 seconds, especially with pagination:
// Show loading indicator
setLoading(true);
try {
const locations = await getAllLocations(tokenId, jwtToken);
setLocations(locations);
} catch (error) {
showError('Failed to load locations');
} finally {
setLoading(false);
}
🔗 Related Documentation
📊 Response Examples
Single Location
{
"success": true,
"locations": [
{
"name": "accounts/1234567890/locations/9876543210",
"id": "9876543210",
"title": "DashClicks Headquarters",
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"metadata": {
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4"
}
}
],
"nextPageToken": null
}
Multiple Locations with Pagination
{
"success": true,
"locations": [
{
"name": "accounts/1234567890/locations/9876543210",
"id": "9876543210",
"title": "Location 1",
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"metadata": {
"placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4"
}
}
// ... 99 more locations
],
"nextPageToken": "CiQ2MDE3LTA1LTAzVDEyOjUwOjQ1LjY0Nlo6NzI5Mzk2OTY5OTI3MDAwMDAwMBI-ChQKEhIQCg4yMDE3MDUwMy0xMDAwMBC"
}
No Locations (Empty Result)
{
"success": true,
"locations": [],
"nextPageToken": null
}
Error Response
{
"success": false,
"message": "Invalid token provided."
}