Das Google Business Profile ist längst keine optionale Ergänzung mehr, sondern der erste Kontaktpunkt vieler Nutzerinnen und Nutzer. Für einen einzelnen Standort reicht das Web-Dashboard aus. Wer aber mehrere Standorte verwaltet, Profilinformationen aus einem System heraus befüllen oder auf neue Bewertungen automatisiert reagieren will, kommt an der API nicht vorbei.
Im Herbst 2021 verabschiedete Google die Marke „Google My Business" und ersetzte sie durch „Google Business Profile". Technisch bedeutete das nicht einfach nur einen neuen Namen, sondern eine stärker aufgeteilte API-Landschaft: Standortinformationen, Account-Verwaltung, Performance-Daten, Reviews und Posts liegen heute nicht mehr unter einem einzigen Endpunkt. Einige Funktionen wurden in spezialisierte APIs ausgelagert, andere laufen weiterhin über v4- beziehungsweise v4.9-Endpunkte.
Für die Praxis ist deshalb weniger die Umbenennung entscheidend als die Architektur dahinter: Wer Google Business Profile automatisieren will, muss wissen, welcher Anwendungsfall über welchen Endpoint läuft — und wo Google bereits migrierte, eingeschränkte oder weiterhin bestehende API-Ressourcen anbietet.
Das Dashboard von Google genügt, solange die Verwaltung überschaubar bleibt. Drei Szenarien, in denen die API den Unterschied macht:
Verbände und Filialnetzwerke mit verteilten Standorten können Öffnungszeiten und Sonderschließungen zentral steuern, statt sie dutzendweise einzeln einzupflegen. CMS-Integrationen ermöglichen es, Profilinformationen direkt aus dem eigenen System heraus aktuell zu halten — Änderungen landen automatisch in Google, ohne dass jemand ein zweites Interface öffnen muss. Und wer auf Kundenbewertungen zeitnah reagieren will, kann den Abruf und das Beantworten neuer Reviews vollständig automatisieren.
Der Einstieg beginnt in der Google Cloud Console: Projekt anlegen, OAuth-2.0-Zugangsdaten erstellen und die benötigten Business-Profile-APIs aktivieren. Wichtig: Der API-Zugang ist nicht automatisch frei verfügbar. Für produktive Integrationen muss das Cloud-Projekt explizit freigeschaltet werden. Google verlangt dafür ein verifiziertes Business Profile, das seit mindestens 60 Tagen aktiv ist, sowie eine im Profil hinterlegte Website, die das Unternehmen repräsentiert. Ein Projekt mit 0 QPM gilt als nicht freigeschaltet; 300 QPM als aktiv. Den Antrag stellt man über das offizielle GBP API Contact Form in der Google-Entwicklerdokumentation.
Für Tests eignet sich der OAuth Playground, sobald das Projekt korrekt eingerichtet ist. Als OAuth-Scope wird https://www.googleapis.com/auth/business.manage benötigt. In produktiven Systemen tauscht man den Refresh Token serverseitig regelmäßig gegen einen neuen Access Token: Tokens, Client Secrets und Refresh Tokens gehören nie ins Repository.
Alle Requests gegen die Business Profile API erfordern einen Bearer Token:
curl -X POST https://oauth2.googleapis.com/token \
-d "client_id={CLIENT_ID}" \
-d "client_secret={CLIENT_SECRET}" \
-d "refresh_token={REFRESH_TOKEN}" \
-d "grant_type=refresh_token"
Die Antwort enthält das Feld access_token, das für etwa eine Stunde gültig ist. Alle folgenden Requests tragen es im Header als Authorization: Bearer {ACCESS_TOKEN}.
// auth.js
require('dotenv').config();
const { google } = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URI
);
oauth2Client.setCredentials({
refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
});
module.exports = oauth2Client;
npm install googleapis axios google-auth-library dotenv
Standortlisten laufen über die Business Information API, nicht über die Account Management API, die für die Verwaltung von Accounts und Nutzerzugängen zuständig ist.
# Alle Standorte eines Accounts auflisten
curl -X GET \
"https://mybusinessbusinessinformation.googleapis.com/v1/accounts/{ACCOUNT_ID}/locations?readMask=name,title,regularHours,phoneNumbers,websiteUri" \
-H "Authorization: Bearer {ACCESS_TOKEN}"
# Details eines einzelnen Standorts abrufen
curl -X GET \
"https://mybusinessbusinessinformation.googleapis.com/v1/locations/{LOCATION_ID}?readMask=name,title,regularHours,specialHours,phoneNumbers,categories" \
-H "Authorization: Bearer {ACCESS_TOKEN}"
Der readMask-Parameter ist kein optionales Extra. Er definiert, welche Felder die API zurückliefern soll, und wird von Google für diesen Request verlangt. Das zwingt dazu, bewusst nur die Daten anzufordern, die tatsächlich gebraucht werden.
// locations.js
const { google } = require('googleapis');
const auth = require('./auth');
const businessInfo = google.mybusinessbusinessinformation({ version: 'v1', auth });
async function getLocations(accountName) {
const { data } = await businessInfo.accounts.locations.list({
parent: accountName, // z. B. "accounts/123456789"
readMask: 'name,title,regularHours,phoneNumbers,websiteUri',
});
return data.locations || [];
}
async function getLocationDetails(locationName) {
const { data } = await businessInfo.locations.get({
name: locationName, // z. B. "locations/987654321"
readMask: 'name,title,regularHours,specialHours,phoneNumbers,categories',
});
return data;
}
module.exports = { getLocations, getLocationDetails };
curl -X PATCH \
"https://mybusinessbusinessinformation.googleapis.com/v1/locations/{LOCATION_ID}?updateMask=regularHours" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"regularHours": {
"periods": [
{
"openDay": "MONDAY",
"closeDay": "MONDAY",
"openTime": { "hours": 9, "minutes": 0 },
"closeTime": { "hours": 18, "minutes": 0 }
},
{
"openDay": "FRIDAY",
"closeDay": "FRIDAY",
"openTime": { "hours": 9, "minutes": 0 },
"closeTime": { "hours": 17, "minutes": 0 }
}
]
}
}'
Nicht-durchgängige Öffnungszeiten (etwa Vormittag und Nachmittag mit Mittagspause) bildet man ab, indem man für denselben Tag einfach zwei period-Objekte einträgt. Google zeigt das im Profil automatisch korrekt als „08:00–12:00, 15:00–18:00":
"periods": [
{
"openDay": "MONDAY", "closeDay": "MONDAY",
"openTime": { "hours": 8, "minutes": 0 },
"closeTime": { "hours": 12, "minutes": 0 }
},
{
"openDay": "MONDAY", "closeDay": "MONDAY",
"openTime": { "hours": 15, "minutes": 0 },
"closeTime": { "hours": 18, "minutes": 0 }
}
]
Für Sonderschließungen (bspw. Feiertage, Betriebsurlaub) ersetzt man regularHours durch specialHours und übergibt dort datumsspezifische Zeiträume mit isClosed: true.
// update-hours.js
const { google } = require('googleapis');
const auth = require('./auth');
const businessInfo = google.mybusinessbusinessinformation({ version: 'v1', auth });
async function updateRegularHours(locationName, periods) {
const { data } = await businessInfo.locations.patch({
name: locationName,
updateMask: 'regularHours',
requestBody: { regularHours: { periods } },
});
return data;
}
const standardHours = [
{ openDay: 'MONDAY', closeDay: 'MONDAY', openTime: { hours: 9 }, closeTime: { hours: 18 } },
{ openDay: 'TUESDAY', closeDay: 'TUESDAY', openTime: { hours: 9 }, closeTime: { hours: 18 } },
{ openDay: 'WEDNESDAY', closeDay: 'WEDNESDAY', openTime: { hours: 9 }, closeTime: { hours: 18 } },
{ openDay: 'THURSDAY', closeDay: 'THURSDAY', openTime: { hours: 9 }, closeTime: { hours: 18 } },
{ openDay: 'FRIDAY', closeDay: 'FRIDAY', openTime: { hours: 9 }, closeTime: { hours: 17 } },
];
updateRegularHours('locations/987654321', standardHours)
.then(r => console.log('Aktualisiert:', r.name))
.catch(console.error);
Die Reviews-Funktionalität liegt auf dem v4-Endpunkt, der parallel zu den neueren APIs weiterläuft.
# Bewertungen abrufen
curl -X GET \
"https://mybusiness.googleapis.com/v4/accounts/{ACCOUNT_ID}/locations/{LOCATION_ID}/reviews?pageSize=20" \
-H "Authorization: Bearer {ACCESS_TOKEN}"
# Auf eine Bewertung antworten
curl -X PUT \
"https://mybusiness.googleapis.com/v4/accounts/{ACCOUNT_ID}/locations/{LOCATION_ID}/reviews/{REVIEW_ID}/reply" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"comment": "Vielen Dank für Ihr Feedback. Wir freuen uns auf Ihren nächsten Besuch."}'
Für die Automatisierung (alle neuen Bewertungen abrufen, filtern, beantworten) braucht es eine Schleife, die in curl nicht abzubilden ist. Zu beachten: Antworten durchlaufen einen Moderationsstatus (PENDING, APPROVED, REJECTED). Wer automatisiert antwortet, sollte prüfen, ob die Antwort tatsächlich den Status APPROVED erreicht hat, bevor der Prozess als erfolgreich gilt:
// reviews.js
const axios = require('axios');
const auth = require('./auth');
async function getAccessToken() {
const { token } = await auth.getAccessToken();
return token;
}
async function getReviews(accountId, locationId) {
const token = await getAccessToken();
const { data } = await axios.get(
`https://mybusiness.googleapis.com/v4/accounts/${accountId}/locations/${locationId}/reviews`,
{ headers: { Authorization: `Bearer ${token}` }, params: { pageSize: 20 } }
);
return data.reviews || [];
}
async function replyToReview(accountId, locationId, reviewId, comment) {
const token = await getAccessToken();
const { data } = await axios.put(
`https://mybusiness.googleapis.com/v4/accounts/${accountId}/locations/${locationId}/reviews/${reviewId}/reply`,
{ comment },
{ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
);
return data; // enthält u. a. updateTime und reviewReplyState
}
async function replyToUnansweredReviews(accountId, locationId) {
const reviews = await getReviews(accountId, locationId);
const unanswered = reviews.filter(r => !r.reviewReply);
for (const review of unanswered) {
const isPositive = ['FIVE', 'FOUR'].includes(review.starRating);
const comment = isPositive
? 'Vielen Dank für Ihre positive Rückmeldung! Wir freuen uns auf Ihren nächsten Besuch.'
: 'Vielen Dank für Ihr Feedback. Ihre Anmerkungen nehmen wir ernst und würden uns freuen, das Gespräch persönlich fortzusetzen.';
const reply = await replyToReview(accountId, locationId, review.reviewId, comment);
console.log(`Antwort übermittelt: ${review.reviewId}`, reply.updateTime, reply.reviewReplyState);
}
}
module.exports = { getReviews, replyToReview, replyToUnansweredReviews };
In medizinischen Kontexten oder bei kritischen Bewertungen ist eine menschliche Prüfung vor dem Absenden ratsam — vollständige Automatisierung funktioniert, ersetzt aber keine redaktionelle Einschätzung.
Google Posts erscheinen direkt im Business Profile und erreichen Nutzerinnen und Nutzer noch vor dem ersten Klick auf die Website. Die LocalPosts-API unterstützt die Typen STANDARD, EVENT, OFFER und ALERT. Produktinformationen gehören je nach Szenario in andere Google-Business- beziehungsweise Merchant-Kontexte und sind kein eigener LocalPost-Typ.
Reguläre Posts verlieren nach einiger Zeit ihre prominente Sichtbarkeit; Event- und Offer-Posts sind stärker an ihr jeweiliges Enddatum gebunden. Neben sofort veröffentlichten Beiträgen lassen sich Posts auch zeitlich planen; für wiederkehrende Szenarien unterstützt die API zusätzlich Recurrence-Felder, was für Agenturen und Organisationen mit vielen Standorten besonders interessant ist: Redaktionelle Inhalte lassen sich zentral vorbereiten und standortbezogen ausspielen.
curl -X POST \
"https://mybusiness.googleapis.com/v4/accounts/{ACCOUNT_ID}/locations/{LOCATION_ID}/localPosts" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"languageCode": "de",
"summary": "Unser Tag der offenen Tür findet am 15. Juli statt.",
"callToAction": {
"actionType": "LEARN_MORE",
"url": "https://example.com/open-day"
},
"topicType": "EVENT",
"event": {
"title": "Tag der offenen Tür",
"schedule": {
"startDate": { "year": 2025, "month": 7, "day": 15 },
"startTime": { "hours": 10, "minutes": 0 },
"endDate": { "year": 2025, "month": 7, "day": 15 },
"endTime": { "hours": 17, "minutes": 0 }
}
}
}'
// posts.js
const axios = require('axios');
const auth = require('./auth');
async function getAccessToken() {
const { token } = await auth.getAccessToken();
return token;
}
async function createLocalPost(accountId, locationId, postData) {
const token = await getAccessToken();
const { data } = await axios.post(
`https://mybusiness.googleapis.com/v4/accounts/${accountId}/locations/${locationId}/localPosts`,
postData,
{ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
);
return data;
}
module.exports = { createLocalPost };
Bestimmte Attribute (branchenspezifische Felder für Medizin, Gastronomie oder Hotels) sind teilweise ausschließlich über das Dashboard zugänglich. Wer Fotos hochladen will, braucht einen separaten Multipart-Upload-Flow. Und der Antragsprozess für den API-Zugang dauert im Regelfall mehrere Wochen.
Für Agenturen und Verbände, die regelmäßig viele Profile pflegen, überwiegt der Einrichtungsaufwand trotzdem schnell. Was einmal in Code gegossen ist, läuft für zehn Standorte genauso wie für hundert.