> ## Documentation Index
> Fetch the complete documentation index at: https://docs.social-api.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Base URL: https://api.social-api.ai/v1. Send your API key in the Authorization header: Authorization: Bearer <your API key>.
> Every error response from /v1 is JSON: { "error": { "code", "message", "meta" (optional) }, "request_id" }. Branch on error.code; use error.message only for logs.
> A 429 response carries a Retry-After header, in seconds, when the limit has a known wait.
> Paginated list endpoints return next_cursor, at the top level or inside pagination (see /guides/pagination). Pass it as the cursor query parameter to get the next page. An absent or empty next_cursor means you have the last page.
> Treat every ID as an opaque string: do not parse it or assume a format.
> Support differs by platform and by operation. Check /platforms/overview before assuming an operation works on a platform. An unsupported operation returns 501 with error.code resource.not_supported.

# Google Business Profile publishing

> Publish, schedule, and delete Google Business Profile local posts (updates, events, offers, alerts) with text, one image, and a call-to-action button.

## 1. Overview

SocialAPI publishes local posts to one connected Google Business Profile location: updates, events, offers, and alerts. A local post appears on the location's Business Profile in Google Search and Google Maps. Each connected `google` account is one location, so a post with one Google target publishes on one location. For everything outside publishing, see the [Google Business Profile API](/platforms/google) page.

| Feature | Supported | Notes |
| - | - | - |
| Create post | Yes | Text, plus at most 1 JPEG or PNG image |
| Update post | Partial | Before publishing only: `PATCH /v1/posts/{pid}` edits a `draft`, `scheduled`, or `failed` post. A published local post cannot be edited: delete it and publish a new one |
| Delete post | Yes | Removes the local post from Google |
| Schedule | Yes | Google has no scheduling of its own. SocialAPI holds the post and creates it on Google at `scheduled_at` |
| First comment | No | Google Business Profile has no comments. SocialAPI ignores `first_comment` on Google targets |
| Multiple images, video | No | Pre-publish validation rejects them with `400 publishing.validation_failed` |
| Posting caps | Yes | 25 posts per hour and 50 per 24 hours for each account: see [Posting velocity caps](/guides/rate-limits#posting-velocity-caps) |

***

## 2. Supported media

| Content type | How to trigger | Notes |
| - | - | - |
| Text only | No `media` | The post shows only its text |
| Single image | One image in `media`, as a `media_id` or `url` source | JPEG or PNG, up to 5 MB |

* **Text**: `text` holds up to 1,500 characters. A longer `text` fails pre-publish validation with `400 publishing.validation_failed`.
* **Images**: 1 image per post, JPEG or PNG, up to 5 MB. A second media item fails validation with `400 publishing.validation_failed` ("carousel/album posts are not supported on google").
* **Video**: not accepted. A video item fails validation with `400 publishing.validation_failed`.
* **Media sources**: Google fetches the image from a URL, so use a `media_id` (a file in your [media library](/guides/media)) or a public `url`. A `platform_attachment_id` item returns `422 publishing.media.unsupported_source`.

***

## 3. Create post

Send `POST /v1/posts` with a Google account in `targets` and `"publish_now": true`. Without `publish_now` or `scheduled_at`, SocialAPI saves the post as a draft.

* `text` becomes the body of the local post. It is required: a request without it returns `400 validation.field_required`.
* `title` is not used by Google Business Profile posts. Put an event's name in `platform_data.google.event.title`.
* `platform_data.google` (or the target's own `platform_data`) carries the post type and its extra fields. A key in a target's `platform_data` wins over the same key in `platform_data.google`.

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Our spring menu is here: wild garlic pizza, burrata, and lemon tart.",
    "media": [{ "source_type": "media_id", "source": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }],
    "publish_now": true,
    "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
    "platform_data": {
      "google": {
        "topicType": "STANDARD",
        "callToAction": { "actionType": "ORDER", "url": "https://joes-pizza.example/order" }
      }
    }
  }'
```

Response (HTTP 201):

```json theme={null}
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "text": "Our spring menu is here: wild garlic pizza, burrata, and lemon tart.",
  "status": "publishing",
  "media": [{ "source_type": "media_id", "source": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }],
  "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
  "hidden": false,
  "retry_count": 0,
  "created_at": "2026-10-13T09:00:00Z",
  "updated_at": "2026-10-13T09:00:00Z",
  "targets": [
    {
      "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
      "platform": "google",
      "status": "pending",
      "text": "Our spring menu is here: wild garlic pizza, burrata, and lemon tart.",
      "media": [{ "source_type": "media_id", "source": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }],
      "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"]
    }
  ]
}
```

SocialAPI publishes in the background. The target moves from `pending` to `publishing`, then to `published` or `failed`, and the post ends as `published`, `partial`, or `failed`. Poll `GET /v1/posts/{pid}` or subscribe to the `post.published`, `post.partial`, and `post.failed` [webhook events](/guides/webhook-events). Publishing or scheduling costs 1 post credit; a draft is free (see [Plans and credits](/guides/plans)).

### Platform data fields

Pass these in `platform_data.google` or in a target's `platform_data`:

| Field | Type | Default | Description |
| - | - | - | - |
| `topicType` | string | `STANDARD` | Post type: `STANDARD` (an update), `EVENT`, `OFFER`, or `ALERT`. Pre-publish validation rejects any other value |
| `callToAction` | object | none | A button on the post: `{ "actionType": "...", "url": "..." }`. Every post type accepts it |
| `callToAction.actionType` | string | none | Required in `callToAction`. One of `BOOK`, `ORDER`, `SHOP`, `LEARN_MORE`, `SIGN_UP`, `CALL`. `GET_OFFER` is rejected: use `topicType: "OFFER"` instead |
| `callToAction.url` | string | none | The page the button opens. `CALL` takes no `url`: the button dials the phone number listed on the location |
| `event` | object | none | Required by Google when `topicType` is `EVENT`. Holds `title` and `schedule` (`startDate`, `endDate`, `startTime`, `endTime`). Dates are `{ "year": 2026, "month": 11, "day": 7 }` objects |
| `offer` | object | none | Required by Google when `topicType` is `OFFER`. Its fields `couponCode`, `redeemOnlineUrl`, and `termsConditions` are optional |
| `alertType` | string | none | Required by Google when `topicType` is `ALERT`. Google's only value is `COVID_19` |
| `languageCode` | string | none | Language of the post text, as a BCP-47 code such as `en` or `fr` |

SocialAPI validates `topicType` and `callToAction`: an invalid value fails pre-publish validation with `400 publishing.validation_failed`. When you set `skip_validation`, the target fails instead with `validation.invalid_call_to_action`, and `error.message` gives the reason. SocialAPI forwards any additional keys in `platform_data.google` that name one of Google's writable local post fields (`event`, `offer`, `alertType`, `languageCode`) to Google without validating them, so Google's own rules apply to their content. SocialAPI drops every other key, including keys meant for other platforms in the same post. See Google's [LocalPost reference](https://developers.google.com/my-business/reference/rest/v4/accounts.locations.localPosts) for the full shape of `event` and `offer`.

### Event post example

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Ten years of wood-fired pizza. Free tasting all evening.",
    "publish_now": true,
    "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
    "platform_data": {
      "google": {
        "topicType": "EVENT",
        "event": {
          "title": "10th anniversary tasting",
          "schedule": {
            "startDate": { "year": 2026, "month": 11, "day": 7 },
            "endDate": { "year": 2026, "month": 11, "day": 7 }
          }
        },
        "callToAction": { "actionType": "BOOK", "url": "https://joes-pizza.example/book" }
      }
    }
  }'
```

***

## 4. Update post

A local post cannot be edited after it is published. To change a published post, delete it (section 5) and publish a new one.

Before publishing, `PATCH /v1/posts/{pid}` edits a post in `draft`, `scheduled`, or `failed` status. The change applies when the post publishes:

```bash theme={null}
curl -X PATCH https://api.social-api.ai/v1/posts/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Our spring menu is here: wild garlic pizza, burrata, and lemon tart. Order before 9 pm."
  }'
```

The response (HTTP 200) is the updated post, in the same shape as the create response. A `PATCH` on a published post returns `409 post.state_invalid` ("Only draft, scheduled, or failed posts can be edited"). See [Scheduling](/guides/scheduling) for rescheduling and the full list of editable fields.

***

## 5. Delete post

`DELETE /v1/posts/{pid}` deletes the local post from Google for every published target, and removes a draft, scheduled, or failed post from SocialAPI:

```bash theme={null}
curl -X DELETE https://api.social-api.ai/v1/posts/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Response (HTTP 200):

```json theme={null}
{
  "success": true,
  "deleted": true,
  "results": [
    { "platform": "google", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "outcome": "deleted" }
  ]
}
```

If Google refuses the delete, or the local post no longer exists on Google, the target's `outcome` is `failed` with Google's reason in `message`, and SocialAPI keeps the post (`"deleted": false`).

To remove the post from one location of a multi-target post, send `POST /v1/posts/{pid}/unpublish` with that target's `account_id`:

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts/7c9e6679-7425-40de-944b-e07fc1f90ae7/unpublish \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }'
```

***

## 6. Retrieve posts

`GET /v1/posts` lists posts, filtered by platform or account:

```bash theme={null}
curl "https://api.social-api.ai/v1/posts?platform=google&account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=20" \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Response (HTTP 200):

```json theme={null}
{
  "data": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "text": "Our spring menu is here: wild garlic pizza, burrata, and lemon tart.",
      "status": "published",
      "hidden": false,
      "retry_count": 0,
      "created_at": "2026-10-13T09:00:00Z",
      "updated_at": "2026-10-13T09:00:06Z",
      "published_at": "2026-10-13T09:00:06Z",
      "targets": [
        {
          "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
          "platform": "google",
          "status": "published",
          "text": "Our spring menu is here: wild garlic pizza, burrata, and lemon tart.",
          "platform_post_id": "accounts/123456789/locations/987654321/localPosts/4567",
          "permalink": "https://local.google.com/place?id=123456789&use=posts&lpsid=4567",
          "published_at": "2026-10-13T09:00:06Z"
        }
      ]
    }
  ],
  "pagination": { "limit": 20, "has_more": false }
}
```

* `platform_post_id` is Google's full resource name for the local post. Treat it as an opaque string.
* `permalink` is the link Google returns to view the post.
* Google reports no engagement counts for local posts. A Google target has no `metrics` object, or one whose `likes`, `comments`, `shares`, and `saves` are all `0`. Location-level performance (views, calls, direction requests, website clicks) is at [Performance metrics](/platforms/google#performance-metrics).

***

## 7. Quirks, errors, and recovery

### Scheduling is held by SocialAPI

Google publishes a local post the moment it is created and has no scheduling of its own. With `scheduled_at`, SocialAPI keeps the post as `scheduled` and creates it on Google at that time. Until then the post exists only in SocialAPI, and you can still edit or cancel it.

### A published post cannot be edited

`PATCH /v1/posts/{pid}` works only before publishing. A published local post keeps its text, image, and button until you delete it.

### The location must allow posting

A location whose merchant does not control the profile (`details.has_voice_of_merchant` is `false`) cannot publish. `details.can_operate_local_post` reports whether the location supports local posts at all. Both flags are described on [Google Business Profile locations](/platforms/google/locations#read-a-locations-details).

### Posting caps

SocialAPI refuses a publish that would pass 25 posts in an hour or 50 posts in 24 hours for one account, with `429 publishing.velocity_limit`. A scheduled post is held and published when the window clears. See [Posting velocity caps](/guides/rate-limits#posting-velocity-caps).

### Errors on a failed target

A failed target carries an `error` object:

```json theme={null}
{
  "code": "platform.google.api_error",
  "message": "Request contains an invalid argument.",
  "category": "platform",
  "caused_by": "platform"
}
```

* `platform.google.api_error`: Google rejected the post. `message` carries Google's reason, for example an `EVENT` post with no `event.title`, an `ALERT` post with no `alertType`, or an image Google could not fetch. Fix the request and retry.
* `platform.google.access_denied`: Google refused the operation for this location, for example because posting is disabled on the profile or the connected Google account has no role on the location. `message` carries Google's reason. Reconnecting does not fix it: resolve it in Google Business Profile.
* `platform.google.auth`: Google no longer accepts the connection. [Reconnect the account](/guides/connect-accounts).
* `platform.google.rate_limit`: Google's own limit was reached. Wait, then retry the post.

### Recovery

* Retry a `failed` or `partial` post with `POST /v1/posts/{pid}/retry`. A retry costs 1 post credit.
* To change a published post, delete it and publish a new one.

All codes, with their HTTP status, are listed in [Errors](/guides/errors).

***

## 8. Full worked example

This example uploads an image, publishes an offer post with it, and checks the result.

**Step 1: Upload the image**

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/media/upload \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -F "file=@spring-offer.jpg"
```

Response (HTTP 201):

```json theme={null}
{ "media_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }
```

**Step 2: Publish the offer post**

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "20% off every pizza this week with code SPRING20.",
    "media": [{ "source_type": "media_id", "source": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }],
    "publish_now": true,
    "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
    "platform_data": {
      "google": {
        "topicType": "OFFER",
        "offer": {
          "couponCode": "SPRING20",
          "redeemOnlineUrl": "https://joes-pizza.example/order",
          "termsConditions": "Valid until October 20, 2026, on orders placed online."
        }
      }
    }
  }'
```

The response (HTTP 201) has `"status": "publishing"` and the post `id`, as in section 3.

**Step 3: Check the result**

```bash theme={null}
curl https://api.social-api.ai/v1/posts/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Response (HTTP 200) once Google has the post:

```json theme={null}
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "text": "20% off every pizza this week with code SPRING20.",
  "status": "published",
  "media": [{ "source_type": "media_id", "source": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }],
  "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
  "hidden": false,
  "retry_count": 0,
  "created_at": "2026-10-13T09:00:00Z",
  "updated_at": "2026-10-13T09:00:07Z",
  "published_at": "2026-10-13T09:00:07Z",
  "targets": [
    {
      "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
      "platform": "google",
      "status": "published",
      "text": "20% off every pizza this week with code SPRING20.",
      "media": [{ "source_type": "media_id", "source": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }],
      "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
      "platform_post_id": "accounts/123456789/locations/987654321/localPosts/4567",
      "permalink": "https://local.google.com/place?id=123456789&use=posts&lpsid=4567",
      "published_at": "2026-10-13T09:00:07Z"
    }
  ]
}
```

If the target is `failed`, read `targets[].error` (section 7), fix the request, and call `POST /v1/posts/{pid}/retry`.

***

## 9. Required OAuth scopes

Publishing uses 1 of the scopes SocialAPI's Google app requests when an account connects. You register no Google app and configure nothing.

| Scope | Used for |
| - | - |
| `https://www.googleapis.com/auth/business.manage` | Create and delete local posts on the location |

The scopes that identify the Google login are listed in [Scopes](/platforms/google#scopes).

The scopes a scoped API key needs, the credit cost of a post, and the MCP tools that publish are on [Publishing](/guides/publishing).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.