# Google Business Profile

Publish updates, events and offers to a Google Business Profile location, reply to reviews and read location performance and search keywords.

A Google Business Profile post shows up in Google Search, Google Maps and the business's knowledge panel, not in a feed. Posts help local ranking and fade after about a week, so a weekly rhythm works best. In Pozy the platform id is `googlebusiness`, and one connected account is one location.

This page covers publishing, reviews and analytics. Listing management (hours, photos, services, menus, attributes, verification) lives in the [Google Business](https://pozy.ai/docs/google-business) section.

## What you can do

| Feature | Supported | Where |
| --- | --- | --- |
| Standard updates (text, text with image) | Yes | [Post types](#post-types) |
| Call-to-action buttons | Yes | [Call to action](#call-to-action) |
| Event posts | Yes | [Events and offers](#events-and-offers) |
| Offer posts | Yes | [Events and offers](#events-and-offers) |
| Posting to several locations from one account | Yes, with `locationId` | [Several locations](#several-locations) |
| Scheduling | Yes | [Scheduling](https://pozy.ai/docs/publishing/scheduling) |
| Edit a published post | Body text only | [Editing](#edit-a-published-post) |
| Reviews | List, reply, delete a reply | [Inbox](#inbox) |
| Location analytics | Impressions, clicks, calls, directions, bookings, search keywords | [Analytics](#analytics) |
| Listing management | Hours, media, services, menus, attributes, verification | [Google Business](https://pozy.ai/docs/google-business) |
| Video posts, DMs, comments, Q&A | No | Not exposed by Google |

## Connect

The location must be verified in Google. Request a connect URL and send the user to `authUrl`:

```bash cURL
curl "https://api.pozy.ai/v1/connect/googlebusiness?profileId=66a1f0c2a4b9d3e8f1a2b3c4&redirect_url=https://app.example.com/connected" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/connect/googlebusiness?profileId=66a1f0c2a4b9d3e8f1a2b3c4&redirect_url=https:%2F%2Fapp.example.com%2Fconnected", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// Send the user's browser to data.authUrl
```
```python Python
import os, requests

resp = requests.get(
    "https://api.pozy.ai/v1/connect/googlebusiness",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    params={"profileId": "66a1f0c2a4b9d3e8f1a2b3c4", "redirect_url": "https://app.example.com/connected"},
)
print(resp.json()["authUrl"])
```

After Google's consent screen the user picks the location to connect. Pozy hosts that picker by default. To build it yourself, pass `headless=true` and call [List locations](https://pozy.ai/docs/api/listGoogleBusinessLocations) and [Select location](https://pozy.ai/docs/api/selectGoogleBusinessLocation). Once done, the user lands on your `redirect_url` with `connected=googlebusiness`, `profileId` and `accountId` appended. See [Hosted connect](https://pozy.ai/docs/connect/hosted-connect) and [Headless connect](https://pozy.ai/docs/connect/headless).

| Connect error | Cause | Fix |
| --- | --- | --- |
| `402` on the connect URL call | The workspace needs a payment method before another account can connect | Add a card, then call again. See [Billing](https://pozy.ai/docs/billing/billing) |
| Error listing locations | Google refused: the token is invalid or the user has no access to that Business Profile | Restart the connect flow so the user grants access again |
| `404` on select location | The `locationId` is not one this connection manages | Use an `id` from the list call of the same flow |

## Post types

A post with no `topicType` is a standard update. Send one image at most, and no video.

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Open all weekend, 8am to 8pm. The autumn menu starts Friday.",
    "mediaItems": [{ "type": "image", "url": "https://pozy.ai/media/autumn-menu.jpg" }],
    "platforms": [{ "platform": "googlebusiness", "accountId": "66b2e19d8c3f5a7e9d0b1c2d" }],
    "publishNow": true
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: "Open all weekend, 8am to 8pm. The autumn menu starts Friday.",
    mediaItems: [{ type: "image", url: "https://pozy.ai/media/autumn-menu.jpg" }],
    platforms: [{ platform: "googlebusiness", accountId: "66b2e19d8c3f5a7e9d0b1c2d" }],
    publishNow: true,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.post.platforms[0].platformPostId);
```
```python Python
resp = requests.post(
    "https://api.pozy.ai/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "content": "Open all weekend, 8am to 8pm. The autumn menu starts Friday.",
        "mediaItems": [{"type": "image", "url": "https://pozy.ai/media/autumn-menu.jpg"}],
        "platforms": [{"platform": "googlebusiness", "accountId": "66b2e19d8c3f5a7e9d0b1c2d"}],
        "publishNow": True,
    },
)
print(resp.json()["post"]["platforms"][0]["platformPostId"])
```

Text-only posts work, but posts with an image or a button get more visibility. To schedule instead, replace `publishNow` with `scheduledFor` and `timezone`.

### Call to action

`callToAction` adds a button under the post. `url` must be HTTPS.

```json
{
  "platform": "googlebusiness",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "callToAction": { "type": "BOOK", "url": "https://example.com/book" }
  }
}
```

| `type` | Button |
| --- | --- |
| `LEARN_MORE` | Link to more information |
| `BOOK` | Booking or reservation |
| `ORDER` | Online ordering |
| `SHOP` | Online store |
| `SIGN_UP` | Registration |
| `CALL` | Phone call to the business |

### Events and offers

`topicType` picks the post type. `EVENT` needs an `event` with a `title` and a `schedule`. `OFFER` takes an `offer` and an optional `event` for the offer period.

```json
{
  "platform": "googlebusiness",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "topicType": "EVENT",
    "event": {
      "title": "Grand opening weekend",
      "schedule": {
        "startDate": { "year": 2026, "month": 11, "day": 14 },
        "startTime": { "hours": 9, "minutes": 0 },
        "endDate": { "year": 2026, "month": 11, "day": 15 },
        "endTime": { "hours": 17, "minutes": 0 }
      }
    },
    "callToAction": { "type": "LEARN_MORE", "url": "https://example.com/opening" }
  }
}
```

```json
{
  "platform": "googlebusiness",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "topicType": "OFFER",
    "event": {
      "title": "20% off all pastries",
      "schedule": {
        "startDate": { "year": 2026, "month": 11, "day": 1 },
        "endDate": { "year": 2026, "month": 11, "day": 30 }
      }
    },
    "offer": {
      "couponCode": "AUTUMN20",
      "redeemOnlineUrl": "https://example.com/order",
      "termsConditions": "In store and online. One per customer."
    }
  }
}
```

Dates are `{ year, month, day }` and times are `{ hours, minutes }` in 24-hour time. ISO 8601 strings are also accepted for all four and converted for you. The event title and dates show as the post heading on Google. More detail, including multi-location posting, is on [Google Business posts](https://pozy.ai/docs/google-business/posts).

### Platform fields

| Field | Type | Notes |
| --- | --- | --- |
| `topicType` | `STANDARD`, `EVENT` or `OFFER` | Default `STANDARD` |
| `callToAction` | `{ type, url }` | Button under the post |
| `event` | `{ title, schedule }` | Required for `EVENT`, optional period for `OFFER` |
| `offer` | `{ couponCode, redeemOnlineUrl, termsConditions }` | All optional |
| `locationId` | string, `locations/{id}` | Post to another location the account manages |
| `languageCode` | string | BCP 47 code. Metadata only, it does not translate |

### Several locations

An account that manages several locations can post to the others with `locationId`. Get the ids with [List Google Business Profile locations](https://pozy.ai/docs/api/getGmbLocations). Change the account's default location with [Update location](https://pozy.ai/docs/api/updateGmbLocation). See [Google Business posts](https://pozy.ai/docs/google-business/posts) for posting one update to many locations.

## Edit a published post

[Edit published post](https://pozy.ai/docs/api/editPost) with `platform: "googlebusiness"` replaces the body text. The image, button, event and offer details stay as published. There is no edit window and no edit limit. The `id` in the response is Google's post id, not the Pozy post id you put in the path.

A `404` on edit means the post no longer exists on Google: it was deleted in Google's dashboard, or it was an event or offer whose end date passed. Publish a new post instead.

## Media requirements

| Images | Requirement |
| --- | --- |
| Per post | 1 |
| Video | Not supported |
| Formats | JPEG, PNG. WebP is converted for you. GIF is not supported |
| Max size | 5 MB. Larger images are compressed before sending |
| Minimum | 400 x 300 px |
| Recommended | 1200 x 900 px (4:3). Google crops, and 4:3 survives the crop best |

The URL must be public HTTPS and return the file with no login or redirect. Upload through [Media](https://pozy.ai/docs/publishing/media) if you are not sure.

## Limits

| Limit | Value |
| --- | --- |
| Post text | 1,500 characters |
| Images | 1 per post |
| Visibility | Roughly 7 days, then Google archives the post |
| Time to appear | Posts can take 24 to 48 hours while Google reviews them |
| Analytics history | Up to 18 months back |

## Analytics

Google has retired per-post insights, so Business Profile analytics are per location.

[Get performance metrics](https://pozy.ai/docs/api/getGoogleBusinessPerformance) returns daily values and totals for each metric. Pass `accountId` and a date range (it defaults to the last 30 days), and optionally `metrics` as a comma-separated list. Google's data runs 2 to 3 days behind.

```bash cURL
curl "https://api.pozy.ai/v1/analytics/googlebusiness/performance?accountId=66b2e19d8c3f5a7e9d0b1c2d&startDate=2026-09-01&endDate=2026-09-30" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/analytics/googlebusiness/performance?accountId=66b2e19d8c3f5a7e9d0b1c2d&startDate=2026-09-01&endDate=2026-09-30", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.metrics.WEBSITE_CLICKS);
```
```python Python
resp = requests.get(
    "https://api.pozy.ai/v1/analytics/googlebusiness/performance",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    params={"accountId": "66b2e19d8c3f5a7e9d0b1c2d", "startDate": "2026-09-01", "endDate": "2026-09-30"},
)
print(resp.json()["metrics"]["WEBSITE_CLICKS"])
```

| Metric | Meaning |
| --- | --- |
| `BUSINESS_IMPRESSIONS_DESKTOP_SEARCH`, `BUSINESS_IMPRESSIONS_MOBILE_SEARCH` | Views in Google Search |
| `BUSINESS_IMPRESSIONS_DESKTOP_MAPS`, `BUSINESS_IMPRESSIONS_MOBILE_MAPS` | Views in Google Maps |
| `WEBSITE_CLICKS` | Clicks to the website |
| `CALL_CLICKS` | Taps to call |
| `BUSINESS_DIRECTION_REQUESTS` | Direction requests |
| `BUSINESS_CONVERSATIONS` | Message conversations started |
| `BUSINESS_BOOKINGS` | Bookings |
| `BUSINESS_FOOD_ORDERS`, `BUSINESS_FOOD_MENU_CLICKS` | Food orders and menu clicks |

[Get search keywords](https://pozy.ai/docs/api/getGoogleBusinessSearchKeywords) returns the searches that showed the listing, aggregated by month (`startMonth` and `endMonth` as `YYYY-MM`, default the last 3 months). Keywords below Google's privacy threshold come back with `impressions: 0` and `impressionsThreshold` set. Each page holds up to 100 keywords; pass `nextPageToken` back as `pageToken` for more.

## Inbox

Reviews are the only inbox surface. List them with [List reviews](https://pozy.ai/docs/api/listInboxReviews) and `platform=googlebusiness`, filter by `accountId`, `minRating`, `maxRating` and `hasReply`, then answer with [Reply to review](https://pozy.ai/docs/api/replyToInboxReview). The review `id` is Google's full resource name, so it also identifies the location.

```bash
curl "https://api.pozy.ai/v1/inbox/reviews?platform=googlebusiness&accountId=66b2e19d8c3f5a7e9d0b1c2d&hasReply=false" \
  -H "Authorization: Bearer $POZY_API_KEY"
```

New, edited, removed and restored reviews arrive as webhooks. See [Reviews](https://pozy.ai/docs/inbox/reviews) and [Google Business reviews](https://pozy.ai/docs/google-business/reviews).

## Errors you may see

| Error | Cause | Fix |
| --- | --- | --- |
| "Image not found" | The image URL is not reachable or needs a login | Use a public HTTPS URL that loads the raw file |
| "Invalid image format" | Not JPEG or PNG, or the file is corrupted | Re-export as JPEG or PNG |
| "Image too small" | Under 400 x 300 px | Use at least 400 x 300 px, ideally 1200 x 900 px |
| Post not visible on Google | Still in Google's review, or the location is not verified | Wait up to 48 hours and check [verification](https://pozy.ai/docs/google-business/verifications) |
| Button does nothing | The `callToAction.url` is invalid or shortened | Use a full HTTPS URL |
| `401` with `token_invalid` | Google revoked or expired the token | Reconnect the account. See [Account health](https://pozy.ai/docs/connect/account-health) |

[Errors](https://pozy.ai/docs/getting-started/errors) covers the error format.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/connect/{platform}`](https://pozy.ai/docs/api/getConnectUrl) | Get OAuth connect URL |
| [`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/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/posts`](https://pozy.ai/docs/api/createPost) | Create post |
| [`POST /v1/posts/{postId}/edit`](https://pozy.ai/docs/api/editPost) | Edit published post |
| [`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 |
| [`GET /v1/inbox/reviews`](https://pozy.ai/docs/api/listInboxReviews) | List reviews |
| [`POST /v1/inbox/reviews/{reviewId}/reply`](https://pozy.ai/docs/api/replyToInboxReview) | Reply to review |
| [`DELETE /v1/inbox/reviews/{reviewId}/reply`](https://pozy.ai/docs/api/deleteInboxReviewReply) | Delete review reply |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`post.published`](https://pozy.ai/docs/webhooks/events#post-published) | Post published. |
| [`post.failed`](https://pozy.ai/docs/webhooks/events#post-failed) | Post failed. |
| [`post.platform.published`](https://pozy.ai/docs/webhooks/events#post-platform-published) | Post platform published. |
| [`post.platform.failed`](https://pozy.ai/docs/webhooks/events#post-platform-failed) | Post platform failed. |
| [`post.platform.deleted`](https://pozy.ai/docs/webhooks/events#post-platform-deleted) | Post platform deleted. |
| [`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. |
