Posts
Publish announcements, events, and offers to Google and Facebook via client.posts.
All post methods live under client.posts. Methods that take a
location_id accept numeric or base64-encoded IDs; see
Location IDs.
Posts publish to two sites: GOOGLE and FACEBOOK. Post types are
ANNOUNCEMENT, EVENT, OFFER, COVID19, and PRODUCT. Call-to-action
types are BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, and GET_OFFER;
cta_type and cta_url must always be provided together.
Asynchronous publishing
Publishing is asynchronous. Create calls return status: INPROGRESS; poll
posts.retrieve() (or posts.bulk_retrieve() for bulk campaigns) until the
status is SUCCESS, and check publishDetails[].submissionError for
per-site failures.
Per-site messages
Every create method accepts message as either a plain string, which
publishes the same text to every site, or a dict mapping site to text:
message = { "GOOGLE": "Book your summer checkup today!", "FACEBOOK": "Summer checkups are here. Tag a friend who needs one!",}When message is a dict, it must contain an entry for every site in
sites; the SDK raises ValidationError before any network call if one is
missing.
posts.bulk_publish
Publish one post across many locations on Google and Facebook in one call.
This is the recommended way to post: sites defaults to both, the single
message (or per-site dict) is expanded into one entry per site, location IDs
are encoded automatically, and the payload is validated client-side before
any network call.
client.posts.bulk_publish( *, name: str, location_ids: list[str | int], message: str | dict[str, str], sites: list[str] | None = None, post_type: str = "ANNOUNCEMENT", cta_type: str | None = None, cta_url: str | None = None, media_url: str | None = None, scheduled_dates: dict | None = None, context_info: dict | None = None, additional_fields: dict | None = None,) -> APIObjectresult = client.posts.bulk_publish( name="Holiday hours", location_ids=[16808, 16809, 16810], message="Open until 10pm through the holidays!", media_url="https://cdn.example.com/holiday.jpg", cta_type="LEARN_MORE", cta_url="https://example.com/holiday-hours",)print(result.socialPost.id, result.socialPost.status) # INPROGRESS, publishes asyncposts.create_announcement
Create an ANNOUNCEMENT post: a plain message with optional CTA and media.
sites defaults to ["GOOGLE"].
client.posts.create_announcement( *, name: str, location_ids: list[str | int], message: str | dict[str, str], sites: list[str] | None = None, cta_type: str | None = None, cta_url: str | None = None, media_url: str | None = None, scheduled_dates: dict | None = None, additional_fields: dict | None = None,) -> APIObjectresult = client.posts.create_announcement( name="Grand Opening", location_ids=[16808], message="Acme Dental is now open!", sites=["GOOGLE"], cta_type="LEARN_MORE", cta_url="https://acmedental.example.com/opening",)print(result.socialPost.id)posts.create_event
Create an EVENT post with a title and a start/end window. Google requires
the event title; the SDK raises ValidationError if it is missing.
start_day and end_day are dates (YYYY-MM-DD); start_time and
end_time are display strings like "10:00am".
client.posts.create_event( *, name: str, location_ids: list[str | int], message: str | dict[str, str], title: str, start_day: str, end_day: str, start_time: str | None = None, end_time: str | None = None, sites: list[str] | None = None, cta_type: str | None = None, cta_url: str | None = None, media_url: str | None = None, scheduled_dates: dict | None = None, additional_fields: dict | None = None,) -> APIObjectresult = client.posts.create_event( name="Free checkup day", location_ids=[16808], message="Join us for free dental checkups!", title="Free Checkup Day", start_day="2026-08-01", end_day="2026-08-01", start_time="10:00am", end_time="4:00pm", cta_type="BOOK", cta_url="https://acmedental.example.com/book",)print(result.socialPost.id)posts.create_offer
Create an OFFER post with a coupon, discount, and terms. title labels
the offer and is required; start_day and end_day (YYYY-MM-DD) bound
its validity.
client.posts.create_offer( *, name: str, location_ids: list[str | int], message: str | dict[str, str], title: str, coupon_code: str | None = None, discount: str | None = None, redeem_url: str | None = None, terms_conditions: str | None = None, start_day: str | None = None, end_day: str | None = None, sites: list[str] | None = None, cta_type: str | None = None, cta_url: str | None = None, media_url: str | None = None, scheduled_dates: dict | None = None, additional_fields: dict | None = None,) -> APIObjectresult = client.posts.create_offer( name="Summer whitening sale", location_ids=[16808], message="20% off teeth whitening all week!", title="Summer Whitening Sale", coupon_code="SUMMER20", discount="20%", redeem_url="https://acmedental.example.com/sale", terms_conditions="One per patient. Not valid with other offers.", start_day="2026-08-01", end_day="2026-08-07",)print(result.socialPost.id)posts.create
Create a post from a raw body dict. Fields are flat camelCase (not wrapped
in an input key), and locationIds must already be base64-encoded. Prefer
the typed creates above for keyword arguments, automatic ID encoding, and
client-side validation.
client.posts.create(body: dict) -> APIObjectRequired: postName, locationIds (base64), postType, postSites.
result = client.posts.create({ "postName": "Grand Opening", "locationIds": ["TG9jYXRpb246MTY4MDg="], "postType": "ANNOUNCEMENT", "postSites": ["GOOGLE"], "postMessage": [{"site": "GOOGLE", "message": "We are now open!"}],})print(result.socialPost.id)posts.retrieve
Get a post with its content, per-site publish status, and analytics. Poll
this after a create until status is SUCCESS.
client.posts.retrieve(post_id: str) -> APIObjectpost = client.posts.retrieve("U29jaWFsUG9zdDo0NDEyMg==")print(post.status)for detail in post.publishDetails: print(detail.site, detail.submissionError)posts.delete
Delete a post and remove it from every site it was published to.
client.posts.delete(post_id: str) -> APIObjectclient.posts.delete("U29jaWFsUG9zdDo0NDEyMg==")posts.list_for_location
List post campaigns targeting a location. Offset-paginated: the result has
.records plus .pageInfo with totalPages, totalRecords, and
hasNextPage. tag defaults to "all" (the API errors when it is
omitted).
client.posts.list_for_location( location_id: str | int, *, tag: str = "all", page: int | None = None, per_page: int | None = None, filters: dict | None = None, sort_fields: dict | None = None,) -> APIObjectresult = client.posts.list_for_location(16808, page=1, per_page=10)for post in result.records: print(post.id, post.name)print(result.pageInfo.totalRecords)posts.bulk_retrieve
Get a bulk (multi-location) campaign with per-location publish status and analytics.
client.posts.bulk_retrieve(bulk_post_id: str) -> APIObjectbulk = client.posts.bulk_retrieve("QnVsa1Bvc3Q6OTk=")print(bulk.status)posts.bulk_list_for_location
List bulk (multi-location) campaigns that include a location. Same
parameters and result shape as posts.list_for_location.
client.posts.bulk_list_for_location( location_id: str | int, *, tag: str = "all", page: int | None = None, per_page: int | None = None, filters: dict | None = None, sort_fields: dict | None = None,) -> APIObjectresult = client.posts.bulk_list_for_location(16808, page=1, per_page=10)for campaign in result.records: print(campaign.id, campaign.name)