# Google Business management

Run Google Business Profile listings through Pozy, from one location to hundreds: posts, reviews, hours, photos, services, menus and verification.

A Google Business Profile listing is what people see when they search for a business on Google Search and Maps: the hours, the photos, the reviews, the "Order" and "Book" buttons. Pozy lets you manage all of it through the API, for one shop or a chain of locations, with the same account you use to publish posts there.

## What you can do

| Job | Page |
| --- | --- |
| Publish updates, events and offers that appear on the listing | [Posts](https://pozy.ai/docs/google-business/posts) |
| Read and answer reviews, and get an event when a new one arrives | [Reviews](https://pozy.ai/docs/google-business/reviews) |
| Change hours, holiday hours, description, phone, website and categories | [Location details](https://pozy.ai/docs/google-business/locations) |
| Set amenities such as Wi-Fi or outdoor seating, and the Book or Order buttons | [Location details](https://pozy.ai/docs/google-business/locations#attributes) |
| Add and remove photos and videos | [Photos and videos](https://pozy.ai/docs/google-business/media) |
| Keep the service list and food menus current | [Services and menus](https://pozy.ai/docs/google-business/services-menus) |
| Verify a new or unverified location | [Verification](https://pozy.ai/docs/google-business/verifications) |
| Read views, calls, direction requests and search keywords | [Platform insights](https://pozy.ai/docs/analytics/platform-insights) |

Connection details, character limits and media rules for posting are on the [Google Business Profile platform page](https://pozy.ai/docs/platforms/google-business-profile).

## How accounts map to locations

You connect Google Business Profile with Google sign-in on a [profile](https://pozy.ai/docs/build/profiles). The person picks a location, and that location becomes one Pozy account (`platform: "googlebusiness"`).

- **One account, one selected location.** Every `/v1/accounts/{accountId}/gmb-*` endpoint acts on the account's selected location.
- **Other locations under the same Google login.** Pass `locationId` as a query parameter to target any other location the login manages, without connecting it separately.
- **Several locations at connect time.** In a [headless connect](https://pozy.ai/docs/connect/headless), [`POST /v1/connect/googlebusiness/select-location`](https://pozy.ai/docs/api/selectGoogleBusinessLocation) accepts a `locations` array and creates one account per location. Any that could not be connected come back in `failed`.

## Work with many locations

### List the locations

[`GET /v1/accounts/{accountId}/gmb-locations`](https://pozy.ai/docs/api/getGmbLocations) returns up to 100 locations by default (raise `limit` up to 500, filter with `search`) and the one currently selected.

```bash cURL
curl "https://api.pozy.ai/v1/accounts/6713b0e5d1f4c69a2b8e3f75/gmb-locations?search=lisbon" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/accounts/6713b0e5d1f4c69a2b8e3f75/gmb-locations?search=lisbon", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

for (const loc of data.locations) console.log(loc.id, loc.name, loc.address);
```
```python Python
import os
import requests

H = {"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"}
resp = requests.get(
    "https://api.pozy.ai/v1/accounts/6713b0e5d1f4c69a2b8e3f75/gmb-locations",
    headers=H,
    params={"search": "lisbon"},
    timeout=30,
)
for loc in resp.json()["locations"]:
    print(loc["id"], loc["name"], loc["address"])
```

```json
{
  "locations": [
    { "id": "4821930576104832190", "name": "Padaria Rosa Chiado", "accountId": "accounts/118273645509", "accountName": "Padaria Rosa", "address": "Rua Garrett 12, Lisboa", "category": "Bakery" },
    { "id": "5930184726391058277", "name": "Padaria Rosa Alfama", "accountId": "accounts/118273645509", "accountName": "Padaria Rosa", "address": "Rua dos Remédios 40, Lisboa", "category": "Bakery" }
  ],
  "hasMore": false,
  "selectedLocationId": "4821930576104832190",
  "cached": true
}
```

### Target a location

Pass `?locationId=5930184726391058277` on any `gmb-*` call. When posting, put `locationId: "locations/5930184726391058277"` in `platformSpecificData`, one `platforms` entry per location ([multi-location posts](https://pozy.ai/docs/google-business/posts#post-to-several-locations)).

### Change the default (optional)

[`PUT /v1/accounts/{accountId}/gmb-locations`](https://pozy.ai/docs/api/updateGmbLocation) with `selectedLocationId` changes which location the account uses when you leave `locationId` out. Include `googleAccountId` (the `accountId` resource name from the list); large Google accounts need it.

### Give a location its own profile (optional)

Agencies often keep one Pozy profile per client. [`POST /v1/accounts/{accountId}/gmb-locations/assign`](https://pozy.ai/docs/api/assignGoogleBusinessLocation) creates a new account for one location on another profile, reusing the existing Google sign-in. No second consent screen is needed.

```bash cURL
curl -X POST https://api.pozy.ai/v1/accounts/6713b0e5d1f4c69a2b8e3f75/gmb-locations/assign \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "6717f4c9b5d8a03e6f2c7d19",
    "selectedLocationId": "locations/5930184726391058277",
    "googleAccountId": "accounts/118273645509"
  }'
```

The new account's `selectedLocationId` comes back as bare digits. Post to it with its own `accountId` and no `locationId`.

## Verified locations only

Google only shows reviews, accepts most edits and returns full listing data for locations that are verified and matched to a published Maps place. Google calls this Voice of Merchant. Before you build anything on a location, read [`GET /v1/accounts/{accountId}/gmb-verifications`](https://pozy.ai/docs/api/getGoogleBusinessVerifications) and check `voiceOfMerchantState.hasVoiceOfMerchant`. If it is `false`, start with [Verification](https://pozy.ai/docs/google-business/verifications).

## Errors you will see everywhere

Every `gmb-*` endpoint passes Google's own complaint through when Google rejects a request.

| Status | Meaning | Fix |
| --- | --- | --- |
| `400` with code `gbp_bad_request` | Google rejected a field, a mask or a value | Read `error`, fix the field and retry |
| `401` with code `token_invalid` | Google revoked or expired the sign-in | [Reconnect](https://pozy.ai/docs/connect/account-health) the account |
| `403` | The Google login has no permission on this location | Ask an owner or manager of the listing to connect |
| `404` | The `accountId` is not one of your accounts | Check the id |

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/accounts/{accountId}/gmb-locations`](https://pozy.ai/docs/api/getGmbLocations) | List Google Business Profile locations |
| [`PUT /v1/accounts/{accountId}/gmb-locations`](https://pozy.ai/docs/api/updateGmbLocation) | Update Google Business Profile location |
| [`POST /v1/accounts/{accountId}/gmb-locations/assign`](https://pozy.ai/docs/api/assignGoogleBusinessLocation) | Assign Google Business Profile location to another profile |
| [`GET /v1/connect/googlebusiness/locations`](https://pozy.ai/docs/api/listGoogleBusinessLocations) | List Google Business Profile locations |
| [`POST /v1/connect/googlebusiness/select-location`](https://pozy.ai/docs/api/selectGoogleBusinessLocation) | Select Google Business Profile location |
| [`GET /v1/analytics/googlebusiness/performance`](https://pozy.ai/docs/api/getGoogleBusinessPerformance) | Get Google Business Profile performance metrics |
| [`GET /v1/analytics/googlebusiness/search-keywords`](https://pozy.ai/docs/api/getGoogleBusinessSearchKeywords) | Get Google Business Profile search keywords |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`account.connected`](https://pozy.ai/docs/webhooks/events#account-connected) | Account connected. |
| [`account.disconnected`](https://pozy.ai/docs/webhooks/events#account-disconnected) | Account disconnected. |
| [`review.new`](https://pozy.ai/docs/webhooks/events#review-new) | Review new. |
| [`review.updated`](https://pozy.ai/docs/webhooks/events#review-updated) | Review updated. |
| [`review.deleted`](https://pozy.ai/docs/webhooks/events#review-deleted) | Review deleted. |
| [`review.restored`](https://pozy.ai/docs/webhooks/events#review-restored) | Review restored. |
