# Google Business posts

Publish updates, events and offers to a Google Business Profile location, add a call-to-action button, post to many locations at once and edit published posts.

Posts on a Google listing show up in Search and Maps next to the business, not in a feed. They are a good place for this week's special, an event or a discount code. You publish them with the same [`POST /v1/posts`](https://pozy.ai/docs/api/createPost) call as every other platform, with `platform: "googlebusiness"` and a few Google-only settings in `platformSpecificData`.

## Post types

| `topicType` | Use it for | Needs |
| --- | --- | --- |
| `STANDARD` (default) | News, updates, a new product | Nothing extra |
| `EVENT` | Anything with dates: an opening, a workshop, live music | An `event` with `title` and `schedule` |
| `OFFER` | Promotions and discounts | An `offer`; an `event` is optional and sets the offer's validity period |

Any type can add a `callToAction` button.

## Publish an update

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Pumpkin and walnut loaves are back for the autumn. Baked fresh every morning from 7:30.",
    "mediaItems": [{ "type": "image", "url": "https://pozy.ai/media/autumn-loaf.jpg" }],
    "platforms": [{
      "platform": "googlebusiness",
      "accountId": "6713b0e5d1f4c69a2b8e3f75",
      "platformSpecificData": {
        "callToAction": { "type": "ORDER", "url": "https://example.com/order" }
      }
    }],
    "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: "Pumpkin and walnut loaves are back for the autumn. Baked fresh every morning from 7:30.",
    mediaItems: [{ type: "image", url: "https://pozy.ai/media/autumn-loaf.jpg" }],
    platforms: [{
      platform: "googlebusiness",
      accountId: "6713b0e5d1f4c69a2b8e3f75",
      platformSpecificData: { callToAction: { type: "ORDER", url: "https://example.com/order" } },
    }],
    publishNow: true,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

console.log(data);
```
```python Python
import os
import requests

H = {"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"}
resp = requests.post(
    "https://api.pozy.ai/v1/posts",
    headers=H,
    json={
        "content": "Pumpkin and walnut loaves are back for the autumn. Baked fresh every morning from 7:30.",
        "mediaItems": [{"type": "image", "url": "https://pozy.ai/media/autumn-loaf.jpg"}],
        "platforms": [{
            "platform": "googlebusiness",
            "accountId": "6713b0e5d1f4c69a2b8e3f75",
            "platformSpecificData": {"callToAction": {"type": "ORDER", "url": "https://example.com/order"}},
        }],
        "publishNow": True,
    },
    timeout=30,
)
print(resp.status_code, resp.json())
```

To schedule it, replace `publishNow` with `scheduledFor` and a `timezone` ([Scheduling](https://pozy.ai/docs/publishing/scheduling)). Leave out `mediaItems` for a text-only post, though posts with an image or a button get more attention.

### Call-to-action buttons

| `callToAction.type` | Button |
| --- | --- |
| `LEARN_MORE` | Links to more information |
| `BOOK` | Booking or reservation page |
| `ORDER` | Online ordering |
| `SHOP` | Online shop |
| `SIGN_UP` | Registration or newsletter |
| `CALL` | Calls the business |

`url` must be a working HTTPS link. Avoid link shorteners; Google may not follow them.

## Publish an event

Set `topicType: "EVENT"` and an `event` with a `title` and a `schedule`. Dates are `{ year, month, day }` objects and times are `{ hours, minutes }` in 24-hour time. Times are optional. Pozy also accepts ISO 8601 strings for all four and converts them.

```json
{
  "platform": "googlebusiness",
  "accountId": "6713b0e5d1f4c69a2b8e3f75",
  "platformSpecificData": {
    "topicType": "EVENT",
    "event": {
      "title": "Sourdough workshop",
      "schedule": {
        "startDate": { "year": 2026, "month": 11, "day": 14 },
        "startTime": { "hours": 10, "minutes": 0 },
        "endDate": { "year": 2026, "month": 11, "day": 14 },
        "endTime": { "hours": 13, "minutes": 0 }
      }
    },
    "callToAction": { "type": "SIGN_UP", "url": "https://example.com/workshop" }
  }
}
```

Google shows the title and dates as the heading of the post.

## Publish an offer

Set `topicType: "OFFER"` and an `offer` with any of `couponCode`, `redeemOnlineUrl` and `termsConditions`. All three are optional, but an offer with none of them gives people nothing to act on. Add an `event` to give the offer a heading and an end date.

```json
{
  "platform": "googlebusiness",
  "accountId": "6713b0e5d1f4c69a2b8e3f75",
  "platformSpecificData": {
    "topicType": "OFFER",
    "event": {
      "title": "10% off all cakes",
      "schedule": {
        "startDate": { "year": 2026, "month": 12, "day": 1 },
        "endDate": { "year": 2026, "month": 12, "day": 23 }
      }
    },
    "offer": {
      "couponCode": "XMAS10",
      "redeemOnlineUrl": "https://example.com/cakes",
      "termsConditions": "Orders placed by 23 December. One code per order."
    }
  }
}
```

## Post to several locations

If one Google login manages several locations, you can post to all of them in one request. Repeat the `platforms` entry with the same `accountId` and a different `locationId` (`locations/` followed by the location id from [`GET .../gmb-locations`](https://pozy.ai/docs/api/getGmbLocations)). An entry without `locationId` goes to the account's selected location.

```json
{
  "content": "Both shops are open on the holiday Monday, 8:00 to 14:00.",
  "platforms": [
    { "platform": "googlebusiness", "accountId": "6713b0e5d1f4c69a2b8e3f75",
      "platformSpecificData": { "locationId": "locations/4821930576104832190" } },
    { "platform": "googlebusiness", "accountId": "6713b0e5d1f4c69a2b8e3f75",
      "platformSpecificData": { "locationId": "locations/5930184726391058277" } }
  ],
  "publishNow": true
}
```

The response has one `platforms` entry per location, each with its own status and link.

## Edit a published post

[`POST /v1/posts/{postId}/edit`](https://pozy.ai/docs/api/editPost) with `platform: "googlebusiness"` and the new `content` changes the text of a live post. There is no edit window and no limit on edits, and the post keeps its id. Only the text changes: the image, button, event and offer details stay as published. To change those, publish a new post.

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts/6718a5d0c6e9b14f7a3d8e2a/edit \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "googlebusiness",
    "content": "Pumpkin and walnut loaves are back. Baked fresh every morning from 7:30, while they last."
  }'
```

If the post went to two accounts, add `accountId` to pick one. A post sent to several locations from one account always edits that account's first entry. A `404` means the post no longer exists on Google: it was deleted on Google, or it is an event or offer whose end date has passed.

## Limits

| Property | Limit |
| --- | --- |
| Text | 1,500 characters |
| Images | 1 per post |
| Video | Not supported |
| Image format | JPEG or PNG. WebP is converted for you. GIF is rejected. |
| Image size | Up to 5 MB. Larger images are compressed before sending. |
| Image dimensions | At least 400 x 300 px. 1200 x 900 px (4:3) crops best. |
| `languageCode` | Optional BCP 47 code. It labels the post and does not translate it. |

Image URLs must be public HTTPS links that return the file itself. [Upload media](https://pozy.ai/docs/publishing/media) to get one.

Google does not offer per-post analytics. Use location-level metrics from [Platform insights](https://pozy.ai/docs/analytics/platform-insights) instead.

## Common errors

| Error | Cause | Fix |
| --- | --- | --- |
| `207` with `post.status: "failed"` | The post was saved but Google rejected it. The reason is in `platforms[].errorMessage`. | Fix and create a new post. A failed post is not retried. |
| "Request contains an invalid argument" on an event | `topicType: "EVENT"` without an `event`, or a schedule missing a date | Add `event.title`, `startDate` and `endDate` |
| "Image too small" | Under 400 x 300 px | Use a larger image |
| "Invalid image format" | Not JPEG or PNG, or a corrupt file | Re-export as JPEG or PNG |
| Post published but not visible | Google is reviewing it, or the location is not verified | Allow up to 48 hours and check [verification](https://pozy.ai/docs/google-business/verifications) |

> **207 counts as success in most HTTP clients:**
> 
> `207` is a 2xx status, so `fetch(...).ok` is `true`. Check the status code and `post.status` before you report success.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`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/posts/{postId}`](https://pozy.ai/docs/api/getPost) | Get post |
| [`GET /v1/accounts/{accountId}/gmb-locations`](https://pozy.ai/docs/api/getGmbLocations) | List Google Business Profile locations |

## 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.partial`](https://pozy.ai/docs/webhooks/events#post-partial) | Post partial. |
| [`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. |
