Skip to content

Google Business posts — list, create, update and delete

Posts are the updates, events and offers that appear on a business’s Google listing. They are served by Google’s My Business API (v4), the same one as reviews. A post is public the moment it is created, so MauKirim validates it against Google’s rules first and an invalid post spends no budget. All endpoints address the business by its MauKirim id (gml_…) and need a package that includes the connector.

Every call here is one metered Google call; see rate limits.

{
"id": "p1",
"topicType": "STANDARD",
"summary": "Diskon 20% hari ini",
"state": "LIVE",
"callToAction": { "actionType": "ORDER", "url": "https://toko.example/order" },
"mediaUrl": "https://cdn.example/a.jpg",
"event": null,
"offer": null,
"searchUrl": "https://g.page/post/p1",
"createdAt": "2026-10-14T03:00:00.000Z",
"updatedAt": "2026-10-14T03:00:00.000Z"
}
Field Meaning
id The post id to use in update and delete. Not Google’s full resource name
topicType STANDARD, EVENT or OFFER
state Google’s state, for example LIVE or REJECTED; null if Google did not say
callToAction { actionType, url } or null (url is null for CALL)
mediaUrl The photo’s Google-hosted URL (or the source URL until Google has processed it), or null
event { title, startDate, endDate } (YYYY-MM-DD) for an EVENT; otherwise null
offer { couponCode, redeemOnlineUrl, termsConditions } for an OFFER; otherwise null
searchUrl Where the post appears on Google Search, when Google provides it
createdAt, updatedAt ISO timestamps from Google, or null

The body of a create or an update is a JSON object with only these fields; any other field is refused (400 invalid_input), so state cannot be set from here.

Field Type Rule
summary string Required. 1–1,500 characters
topicType string STANDARD (default), EVENT or OFFER
languageCode string Up to 16 characters, for example id or en
callToAction object { "actionType": "BOOK" | "ORDER" | "SHOP" | "LEARN_MORE" | "SIGN_UP" | "CALL", "url": "https://…" }. Every type but CALL needs an https url; CALL must not have one (Google uses the business’s phone)
mediaUrl string One photo, a public https URL that Google fetches
event object Required for EVENT, refused otherwise. title 1–58 characters, startDate and endDate as real YYYY-MM-DD dates with startDate not after endDate; optional startTime and endTime as HH:MM
offer object Only for OFFER. Optional couponCode (up to 58), redeemOnlineUrl (https) and termsConditions (up to 5,000)

An OFFER post cannot carry event dates through this endpoint. If Google insists on a date range for offers and refuses the post with 400 invalid_input, create it through the google door (api: "reviews", path /localPosts) with Google’s own body. We have not been able to confirm Google’s offer rules for every case, so we say so rather than promise.

Scope read or gmb
Query pageSize 1–100 (default 20), pageToken from the previous page
Terminal window
curl "https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab/posts?pageSize=20" \
-H "Authorization: Bearer mk_live_…"
const response = await fetch(`https://app.maukirim.com/api/v1/gmb/locations/${id}/posts`, {
headers: { Authorization: `Bearer ${process.env.MAUKIRIM_KEY}` },
})
const { posts, nextPageToken } = await response.json()
$curl = curl_init("https://app.maukirim.com/api/v1/gmb/locations/$id/posts");
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('MAUKIRIM_KEY')],
CURLOPT_RETURNTRANSFER => true,
]);
$posts = json_decode(curl_exec($curl), true)['posts'];
{ "ok": true, "posts": [ { "id": "p1", "topicType": "STANDARD", "summary": "Diskon 20% hari ini", "…": "…" } ], "nextPageToken": null }

Publishes a post. 201 Created.

Scope gmb
Body application/json, see above
Header Idempotency-Key (optional, strongly recommended): 8–128 characters of letters, digits and _ . : -
Terminal window
curl -X POST https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab/posts \
-H "Authorization: Bearer mk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: post-gml_8f3kq2x1ab-promo-oct" \
-d '{
"summary": "Diskon 20% hari ini",
"callToAction": { "actionType": "ORDER", "url": "https://toko.example/order" },
"mediaUrl": "https://cdn.example/a.jpg"
}'
const response = await fetch(`https://app.maukirim.com/api/v1/gmb/locations/${id}/posts`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MAUKIRIM_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `post-${id}-promo-oct`,
},
body: JSON.stringify({
topicType: "EVENT",
summary: "Grand opening",
event: { title: "Grand opening", startDate: "2026-12-01", endDate: "2026-12-02", startTime: "09:00" },
}),
})
const { post, replayed } = await response.json()
$curl = curl_init("https://app.maukirim.com/api/v1/gmb/locations/$id/posts");
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MAUKIRIM_KEY'),
'Content-Type: application/json',
"Idempotency-Key: post-$id-promo-oct",
],
CURLOPT_POSTFIELDS => json_encode(['summary' => 'Diskon 20% hari ini', 'callToAction' => ['actionType' => 'CALL']]),
CURLOPT_RETURNTRANSFER => true,
]);
$post = json_decode(curl_exec($curl), true)['post'];
{ "ok": true, "replayed": false, "post": { "id": "p2", "topicType": "STANDARD", "state": "LIVE", "…": "…" } }

The same Idempotency-Key with the same body returns the stored answer with "replayed": true and no second post; the same key with a different body is 409 gmb_idempotency_conflict. Without a key, a retry after a timeout can publish the post twice. A key is shared by every write on your account for 24 hours, so use a different key for each different post.


Changes an existing post. The body takes the same fields as a create, but only the ones you send are changed and nothing is defaulted — in particular topicType — so sending just a new summary to an event or an offer keeps it an event or an offer. An empty body is refused (invalid_input). Returns { "ok": true, "post": { … } }.

Scope gmb
Path id the business; postId from the list or the create answer
Terminal window
curl -X PATCH https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab/posts/p1 \
-H "Authorization: Bearer mk_live_…" -H "Content-Type: application/json" \
-d '{ "summary": "Diskon 30% sampai Jumat" }'

A change is idempotent by nature (the same body gives the same post), so it takes no Idempotency-Key.


Removes the post from the public listing. It cannot be undone. Returns { "ok": true }.

Scope gmb
Terminal window
curl -X DELETE https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab/posts/p1 -H "Authorization: Bearer mk_live_…"

A postId that is not a plain id (letters, digits and _ . = -) is refused before Google is called.

HTTP Code Condition
400 invalid_input A field outside the list above, a value outside its limits, event without EVENT (or missing for EVENT), a CALL button with a url, a malformed Idempotency-Key, or Google rejected the post
401 api_key_invalid / api_key_revoked / api_key_expired
402 gmb_plan_required The package does not include the connector
403 api_key_scope_denied Writes need gmb; reads need read or gmb
403 gmb_forbidden Google says the connected account may not post for this business
404 gmb_location_not_found Not your business, no such business, it left Google, or (update, delete) Google has no such post
409 gmb_reconnect_required The connection must be reconnected in the dashboard
409 gmb_location_disabled The business or its connection is switched off
409 gmb_idempotency_conflict The key was used with a different body
429 gmb_rate_limited No budget now (Retry-After); also a request whose key is still running
502 gmb_upstream_error Google failed
503 gmb_api_unavailable The Business Profile API is not enabled or approved for the deployment’s Google project