Skip to main content

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

ParameterTypeRequiredDescription
tokenString✅ YesToken ID from OAuth callback
nextPageTokenString❌ NoPagination 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

FieldTypeDescription
successBooleanRequest success status
locationsArrayList of location objects
locations[].nameStringFull resource name (accounts/{accountId}/locations/{locationId})
locations[].idStringLocation ID (extracted from name)
locations[].titleStringBusiness location name
locations[].placeIdStringGoogle Places ID for this location
locations[].metadata.placeIdStringPlace ID from metadata (same as placeId)
nextPageTokenString|nullToken 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 code
  • languageCode - Primary language
  • phoneNumbers - Phone numbers
  • categories - Business categories
  • storefrontAddress - Physical address
  • websiteUri - Website URL
  • regularHours - Business hours
  • profile - 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);
}

📊 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."
}
💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM