RelayAPI

Google Business API

Schedule and automate Google Business Profile posts with RelayAPI — standard posts, events, offers, photos, and call-to-action buttons.

Quick Reference

PropertyValue
Platform keygooglebusiness
Auth methodOAuth 2.0
Text limit1,500 characters
Images per post1
Videos per postNot supported
Image formatsJPEG, PNG
Image max size5 MB
Post typesStandard, Event, Offer
Call to actionYes (Learn More, Book, Order, Shop, Sign Up, Call)
SchedulingYes
AnalyticsYes (views, clicks)

SDK sourceTypeScript · Python · Go · Java · REST API

Before You Start

Google Business Profile posts are limited to 1 photo per post — no videos, no multi-image galleries. Event and Offer posts require a schedule with start and end dates. Posts do not have a direct public URL — they appear on Google Search and Maps. The API uses the My Business API v4, which requires the account to be verified on Google Business Profile.

Quick Start

Create a standard post on Google Business Profile:

import Relay from '@relayapi/sdk';

const client = new Relay();
const post = await client.posts.create({
  content: 'We just launched our new spring menu! Come visit us today.',
  targets: ['googlebusiness'],
  scheduled_at: 'now',
});

console.log(post.id); // post_abc123
from relay import Relay

client = Relay()
post = client.posts.create(
    content='We just launched our new spring menu! Come visit us today.',
    targets=['googlebusiness'],
    scheduled_at='now',
)

print(post.id)  # post_abc123
client := relaygo.NewClient()

post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("We just launched our new spring menu! Come visit us today."),
    Targets:     relaygo.F([]string{"googlebusiness"}),
    ScheduledAt: relaygo.F("now"),
})

fmt.Println(post.ID) // post_abc123
RelayClient client = RelayOkHttpClient.fromEnv();

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("We just launched our new spring menu! Come visit us today.")
    .addTarget("googlebusiness")
    .scheduledAt("now")
    .build());

System.out.println(post.id()); // post_abc123
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "We just launched our new spring menu! Come visit us today.",
    "targets": ["googlebusiness"],
    "scheduled_at": "now"
  }'

Content Types

Standard Post

A simple update with text and an optional photo.

const post = await client.posts.create({
  content: 'We just launched our new spring menu!',
  targets: ['googlebusiness'],
  scheduled_at: 'now',
});
post = client.posts.create(
    content='We just launched our new spring menu!',
    targets=['googlebusiness'],
    scheduled_at='now',
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("We just launched our new spring menu!"),
    Targets:     relaygo.F([]string{"googlebusiness"}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("We just launched our new spring menu!")
    .addTarget("googlebusiness")
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "We just launched our new spring menu!",
    "targets": ["googlebusiness"],
    "scheduled_at": "now"
  }'

Standard Post with Photo

const post = await client.posts.create({
  content: 'Check out our renovated dining area!',
  targets: ['googlebusiness'],
  media: [
    { url: 'https://cdn.example.com/photo.jpg', type: 'image' },
  ],
  scheduled_at: 'now',
});
post = client.posts.create(
    content='Check out our renovated dining area!',
    targets=['googlebusiness'],
    media=[
        {'url': 'https://cdn.example.com/photo.jpg', 'type': 'image'},
    ],
    scheduled_at='now',
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Check out our renovated dining area!"),
    Targets:     relaygo.F([]string{"googlebusiness"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{{
        URL:  relaygo.F("https://cdn.example.com/photo.jpg"),
        Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage),
    }}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Check out our renovated dining area!")
    .addTarget("googlebusiness")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Check out our renovated dining area!",
    "targets": ["googlebusiness"],
    "media": [
      {"url": "https://cdn.example.com/photo.jpg", "type": "image"}
    ],
    "scheduled_at": "now"
  }'

Post with Call to Action

Add a button linking to your website, booking page, or online shop.

const post = await client.posts.create({
  content: "Book your table for Valentine's Day!",
  targets: ['googlebusiness'],
  scheduled_at: 'now',
  target_options: {
    googlebusiness: {
      call_to_action: {
        type: 'BOOK',
        url: 'https://example.com/book',
      },
    },
  },
});
post = client.posts.create(
    content="Book your table for Valentine's Day!",
    targets=['googlebusiness'],
    scheduled_at='now',
    target_options={
        'googlebusiness': {
            'call_to_action': {
                'type': 'BOOK',
                'url': 'https://example.com/book',
            },
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Book your table for Valentine's Day!"),
    Targets:     relaygo.F([]string{"googlebusiness"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "googlebusiness": map[string]interface{}{
            "call_to_action": map[string]interface{}{
                "type": "BOOK",
                "url":  "https://example.com/book",
            },
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Book your table for Valentine's Day!")
    .addTarget("googlebusiness")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("googlebusiness", JsonValue.from(Map.of(
            "call_to_action", Map.of(
                "type", "BOOK",
                "url", "https://example.com/book"
            )
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Book your table for Valentine'\''s Day!",
    "targets": ["googlebusiness"],
    "scheduled_at": "now",
    "target_options": {
      "googlebusiness": {
        "call_to_action": {
          "type": "BOOK",
          "url": "https://example.com/book"
        }
      }
    }
  }'

Event Post

Promote a specific event with a title and schedule. Requires topic_type: "EVENT" and an event object with a schedule.

const post = await client.posts.create({
  content: 'Join us for live music every Friday night!',
  targets: ['googlebusiness'],
  scheduled_at: 'now',
  target_options: {
    googlebusiness: {
      topic_type: 'EVENT',
      event: {
        title: 'Friday Night Live Music',
        schedule: {
          startDate: { year: 2026, month: 4, day: 3 },
          startTime: { hours: 19, minutes: 0 },
          endDate: { year: 2026, month: 4, day: 3 },
          endTime: { hours: 23, minutes: 0 },
        },
      },
    },
  },
});
post = client.posts.create(
    content='Join us for live music every Friday night!',
    targets=['googlebusiness'],
    scheduled_at='now',
    target_options={
        'googlebusiness': {
            'topic_type': 'EVENT',
            'event': {
                'title': 'Friday Night Live Music',
                'schedule': {
                    'startDate': {'year': 2026, 'month': 4, 'day': 3},
                    'startTime': {'hours': 19, 'minutes': 0},
                    'endDate': {'year': 2026, 'month': 4, 'day': 3},
                    'endTime': {'hours': 23, 'minutes': 0},
                },
            },
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Join us for live music every Friday night!"),
    Targets:     relaygo.F([]string{"googlebusiness"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "googlebusiness": map[string]interface{}{
            "topic_type": "EVENT",
            "event": map[string]interface{}{
                "title": "Friday Night Live Music",
                "schedule": map[string]interface{}{
                    "startDate": map[string]interface{}{"year": 2026, "month": 4, "day": 3},
                    "startTime": map[string]interface{}{"hours": 19, "minutes": 0},
                    "endDate":   map[string]interface{}{"year": 2026, "month": 4, "day": 3},
                    "endTime":   map[string]interface{}{"hours": 23, "minutes": 0},
                },
            },
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Join us for live music every Friday night!")
    .addTarget("googlebusiness")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("googlebusiness", JsonValue.from(Map.of(
            "topic_type", "EVENT",
            "event", Map.of(
                "title", "Friday Night Live Music",
                "schedule", Map.of(
                    "startDate", Map.of("year", 2026, "month", 4, "day", 3),
                    "startTime", Map.of("hours", 19, "minutes", 0),
                    "endDate", Map.of("year", 2026, "month", 4, "day", 3),
                    "endTime", Map.of("hours", 23, "minutes", 0)
                )
            )
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Join us for live music every Friday night!",
    "targets": ["googlebusiness"],
    "scheduled_at": "now",
    "target_options": {
      "googlebusiness": {
        "topic_type": "EVENT",
        "event": {
          "title": "Friday Night Live Music",
          "schedule": {
            "startDate": {"year": 2026, "month": 4, "day": 3},
            "startTime": {"hours": 19, "minutes": 0},
            "endDate": {"year": 2026, "month": 4, "day": 3},
            "endTime": {"hours": 23, "minutes": 0}
          }
        }
      }
    }
  }'

Offer Post

Promote a discount or deal with optional coupon code and redemption URL.

const post = await client.posts.create({
  content: '20% off all drinks this weekend!',
  targets: ['googlebusiness'],
  scheduled_at: 'now',
  target_options: {
    googlebusiness: {
      topic_type: 'OFFER',
      event: {
        title: 'Weekend Happy Hour',
        schedule: {
          startDate: { year: 2026, month: 4, day: 4 },
          endDate: { year: 2026, month: 4, day: 5 },
        },
      },
      offer: {
        couponCode: 'HAPPY20',
        redeemOnlineUrl: 'https://example.com/redeem',
        termsConditions: 'Valid in-store only. Cannot be combined with other offers.',
      },
    },
  },
});
post = client.posts.create(
    content='20% off all drinks this weekend!',
    targets=['googlebusiness'],
    scheduled_at='now',
    target_options={
        'googlebusiness': {
            'topic_type': 'OFFER',
            'event': {
                'title': 'Weekend Happy Hour',
                'schedule': {
                    'startDate': {'year': 2026, 'month': 4, 'day': 4},
                    'endDate': {'year': 2026, 'month': 4, 'day': 5},
                },
            },
            'offer': {
                'couponCode': 'HAPPY20',
                'redeemOnlineUrl': 'https://example.com/redeem',
                'termsConditions': 'Valid in-store only. Cannot be combined with other offers.',
            },
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("20% off all drinks this weekend!"),
    Targets:     relaygo.F([]string{"googlebusiness"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "googlebusiness": map[string]interface{}{
            "topic_type": "OFFER",
            "event": map[string]interface{}{
                "title": "Weekend Happy Hour",
                "schedule": map[string]interface{}{
                    "startDate": map[string]interface{}{"year": 2026, "month": 4, "day": 4},
                    "endDate":   map[string]interface{}{"year": 2026, "month": 4, "day": 5},
                },
            },
            "offer": map[string]interface{}{
                "couponCode":       "HAPPY20",
                "redeemOnlineUrl":  "https://example.com/redeem",
                "termsConditions":  "Valid in-store only. Cannot be combined with other offers.",
            },
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("20% off all drinks this weekend!")
    .addTarget("googlebusiness")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("googlebusiness", JsonValue.from(Map.of(
            "topic_type", "OFFER",
            "event", Map.of(
                "title", "Weekend Happy Hour",
                "schedule", Map.of(
                    "startDate", Map.of("year", 2026, "month", 4, "day", 4),
                    "endDate", Map.of("year", 2026, "month", 4, "day", 5)
                )
            ),
            "offer", Map.of(
                "couponCode", "HAPPY20",
                "redeemOnlineUrl", "https://example.com/redeem",
                "termsConditions", "Valid in-store only. Cannot be combined with other offers."
            )
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "20% off all drinks this weekend!",
    "targets": ["googlebusiness"],
    "scheduled_at": "now",
    "target_options": {
      "googlebusiness": {
        "topic_type": "OFFER",
        "event": {
          "title": "Weekend Happy Hour",
          "schedule": {
            "startDate": {"year": 2026, "month": 4, "day": 4},
            "endDate": {"year": 2026, "month": 4, "day": 5}
          }
        },
        "offer": {
          "couponCode": "HAPPY20",
          "redeemOnlineUrl": "https://example.com/redeem",
          "termsConditions": "Valid in-store only. Cannot be combined with other offers."
        }
      }
    }
  }'

Media Requirements

Images

PropertyRequirement
Max per post1
FormatsJPEG, PNG
Max file size5 MB
Recommended1200 x 900 px (4:3 landscape)

Videos

PropertyRequirement
SupportNot supported via localPosts API

target_options Fields

All fields go inside target_options.googlebusiness on your post request.

FieldTypeDefaultDescription
contentstringOverride post content for Google Business specifically
mediaobject[]Override media for Google Business specifically
topic_typestring"STANDARD"Post type: "STANDARD", "EVENT", or "OFFER"
call_to_actionobjectButton with type and url. Types: LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL
eventobjectRequired for EVENT and OFFER. Contains title and schedule with start/end dates
offerobjectFor OFFER posts. Contains couponCode, redeemOnlineUrl, termsConditions
language_codestringLanguage code for the post (e.g., "en")
location_idstringOverride the target location (if managing multiple locations)

Common Errors

ErrorCauseFix
Content too longText exceeds 1,500 charactersShorten the content. Use target_options.googlebusiness.content for a shorter version.
Too many mediaMore than 1 image was attachedRemove extras. Google Business allows only 1 image per post.
Video not supportedA video was attachedRemove the video. Google Business localPosts API does not support video uploads.
Event requiredEVENT or OFFER type without an event scheduleAdd an event object with a schedule containing start and end dates.
Invalid CTA typeUnrecognized call_to_action typeUse one of: LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL.

Known Quirks

  • No video support — the localPosts API only supports photos, not videos.
  • 1 image per post — no multi-image galleries or carousels.
  • No direct public URL — posts appear on Google Search and Maps but don't have a standalone page URL.
  • EVENT and OFFER posts require a schedule — you must provide start and end dates.
  • CALL action type uses the location's phone number — no URL is needed.
  • Account must be verified on Google Business Profile before the API can be used.
  • Posts may take a few minutes to appear on Google Search and Maps after creation.

Automations

Google Business Profile API requires contact-form approval. Policies forbid agencies/end-clients from sharing one API project — each end-client needs their own GBP project. The Q&A notification types (NEW_QUESTION, etc.) were deprecated on 2025-11-03 with the Q&A API discontinuation.

Triggers

TypeFires on
googlebusiness_new_reviewNew customer review
googlebusiness_updated_reviewReview content/rating edited
googlebusiness_new_customer_mediaCustomer uploads a photo/video
googlebusiness_duplicate_locationDuplicate-location alert
googlebusiness_voice_of_merchant_updatedVoM score change
googlebusiness_google_updateGoogle-sourced edit to your listing

Send nodes

NodeEndpointRequired fields
googlebusiness_reply_to_reviewPUT mybusiness.googleapis.com/v4/{review.name}/replycomment, review_name
googlebusiness_post_updatePOST mybusiness.googleapis.com/v4/{location}/localPostssummary, optional media_url / call_to_action / topic_type

Found something wrong? Help us improve this page.

On this page

Submit an Issue
Requires a GitHub account.View repo