Skip to content

Google Business locations — list, read, update, create and delete

Businesses are the Google Business Profile locations reachable through the Google accounts you connected in the dashboard. Each has a MauKirim id (gml_…) used in every other call. These endpoints need a package that includes the connector; without it every one answers 402 gmb_plan_required. What else you can do with a business is on its own pages: posts, photos, insights, attributes and categories, verification, managers, reviews and the Google door.

A business can also be created — POST /gmb/connections/{id}/locations, documented with the connections it is made under — and deleted from Google (below).

Businesses found on connect or sync arrive switched off. When a Google account is connected or synced, every business MauKirim finds for the first time is imported with status: "disabled" and disabledBy: "user". A switched-off business costs no allowance: only active businesses count against your package’s limit. You choose which ones to turn on, now or later, with POST /gmb/locations/activation. Nothing is lost by leaving one off: it stays in your list, a later sync keeps its details fresh without turning it on, and turning it on later takes one call.

Lists your businesses from MauKirim’s own table, ordered by title. It makes no Google call and spends no budget, so it is safe to call often.

Scope read or gmb
Auth Authorization: Bearer mk_live_…
Query param Type Default Validation
connectionId string — Only the businesses of one connection (gmc_…)
externalId string — Only the businesses connected for one of your own clients through the hosted connect flow — your id for them. An empty value (?externalId=) keeps only the businesses you connected yourself
status string — active, removed_upstream or disabled; anything else is 400 invalid_input
q string — Case-insensitive match in the title and address
limit integer 50 1–200
offset integer 0 0 or more
Terminal window
curl "https://app.maukirim.com/api/v1/gmb/locations?status=active&q=kopi&limit=2" \
-H "Authorization: Bearer mk_live_…"
const response = await fetch("https://app.maukirim.com/api/v1/gmb/locations?status=active&q=kopi&limit=2", {
headers: { Authorization: `Bearer ${process.env.MAUKIRIM_KEY}` },
})
const { total, locations } = await response.json()
$curl = curl_init('https://app.maukirim.com/api/v1/gmb/locations?status=active&q=kopi&limit=2');
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('MAUKIRIM_KEY')],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($curl), true);
{
"ok": true,
"total": 3,
"locations": [
{
"id": "gml_8f3kq2x1ab",
"connectionId": "gmc_2m9dq7zk0p",
"connectionLabel": "Agency account",
"connectionEmail": "agency@example.com",
"connectionStatus": "active",
"externalId": "",
"title": "Kopi Nusantara 1-1",
"addressSummary": "Jl. Melati 1, Jakarta, DKI Jakarta 10110",
"primaryCategory": "Coffee shop",
"phone": "+62 21 5550 100",
"websiteUri": "https://kopi1.example.com",
"placeId": "place_1_0",
"verificationState": "verified",
"status": "active",
"disabledBy": null,
"lastSyncedAt": "2026-10-14T01:05:00.000Z"
}
]
}

total is the number of businesses matching the filters, not the page size: page with offset until offset + locations.length >= total.

Field Meaning
id MauKirim business id. Use it in every other call
connectionId, connectionLabel, connectionEmail, connectionStatus The Google account the business is reached through (the list includes these; the single read does not)
externalId Your id for the end user whose Google account this business came through (see Connect your clients), or "" for a business you connected yourself. Present in the list; the single read does not carry it
title, addressSummary, primaryCategory, phone, websiteUri, placeId Public profile data as of the last read. Any can be null
verificationState Whether Google lets this business owner manage it publicly: verified or unverified, or null if Google did not say
status active, removed_upstream or disabled — see statuses
disabledBy Who switched a disabled business off; null for any other status. user: it is waiting for your choice (every newly found business starts here) or you switched it off — turn it on with POST /gmb/locations/activation. operator: MauKirim support switched it off and it cannot be turned on from the API. connection: its Google connection was lost, so reconnect it
lastSyncedAt When MauKirim last read it from Google

To find the businesses waiting for a choice, list with ?status=disabled (and externalId for one of your own clients’ businesses). The single read, GET /gmb/locations/{id}, carries disabledBy too.

HTTP Code Condition
400 invalid_input A bad status, limit or offset
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 The key has neither read nor gmb; forbidden for an inactive account
500 server_error Unexpected

Chooses which businesses are switched on. Businesses found when a Google account is connected or synced arrive switched off and cost no allowance; this is how you turn the ones you want on, and turn others off again.

Scope gmb
Auth Authorization: Bearer mk_live_…
Body JSON object with activate and/or deactivate: lists of business ids (gml_…), at most 200 each. At least one id in total; no other field
Idempotency Not needed. Setting a state a business already has changes nothing, so a retry is safe
Terminal window
curl -X POST https://app.maukirim.com/api/v1/gmb/locations/activation \
-H "Authorization: Bearer mk_live_…" \
-H "Content-Type: application/json" \
-d '{"activate": ["gml_8f3kq2x1ab", "gml_4d7wn0c9ze"], "deactivate": ["gml_1a2b3c4d5e"]}'
const response = await fetch("https://app.maukirim.com/api/v1/gmb/locations/activation", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MAUKIRIM_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ activate: ["gml_8f3kq2x1ab", "gml_4d7wn0c9ze"], deactivate: ["gml_1a2b3c4d5e"] }),
})
const { activated, deactivated, active, maxLocations } = await response.json()
$curl = curl_init('https://app.maukirim.com/api/v1/gmb/locations/activation');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MAUKIRIM_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'activate' => ['gml_8f3kq2x1ab', 'gml_4d7wn0c9ze'],
'deactivate' => ['gml_1a2b3c4d5e'],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($curl), true);
{
"ok": true,
"activated": ["gml_8f3kq2x1ab", "gml_4d7wn0c9ze"],
"deactivated": ["gml_1a2b3c4d5e"],
"active": 9,
"maxLocations": 10
}
Field Meaning
activated Businesses this call switched on. One that was already on is left out
deactivated Businesses this call switched off. One that was already off is left out
active How many businesses are on after the change
maxLocations Your package’s allowance of active businesses; null when it has none

It is atomic against your allowance. Switching off frees allowance and is applied in the same call, so a call that turns three on and two off needs room for one more. If the result would exceed the allowance the call answers 409 gmb_location_limit and changes nothing: no business is half switched. Switch some others off, or upgrade, and send it again.

What cannot be switched on from here. A business MauKirim support switched off (disabledBy: "operator") is 409 gmb_location_disabled; one whose Google connection was lost (disabledBy: "connection") is 409 gmb_reconnect_required, because only the account owner can reconnect it; one that has left Google (removed_upstream) is 404 gmb_location_not_found.

Switching off is local and reversible: the public Google listing is not touched, nothing is deleted, and the business can be switched on again later (while the allowance has room). A business that is off is not read for reviews, posts, photos or any other call: those answer 409 gmb_location_disabled until you turn it on.

The call makes no Google request and spends no budget.

HTTP Code Condition
400 invalid_input The body is not a JSON object, has a field other than activate / deactivate, names no business, has a malformed or more than 200 ids, or lists the same id in both
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 The key lacks gmb
404 gmb_location_not_found An id is not your business (the same answer as for one that does not exist), or it has left Google. Nothing was changed
409 gmb_location_limit Switching on would exceed the allowance; nothing was changed
409 gmb_location_disabled / gmb_reconnect_required A business was switched off by MauKirim support, or its connection was lost; nothing was changed
500 server_error Unexpected

Reads one business.

Scope read or gmb
Path id — the business id (gml_…)
Query live — 1 or true to read it from Google now

Stored copy (default). Free. It answers even for a business that is disabled or removed_upstream:

Terminal window
curl https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab \
-H "Authorization: Bearer mk_live_…"
{
"ok": true,
"live": false,
"location": {
"id": "gml_8f3kq2x1ab",
"connectionId": "gmc_2m9dq7zk0p",
"title": "Kopi Nusantara 1-1",
"addressSummary": "Jl. Melati 1, Jakarta, DKI Jakarta 10110",
"primaryCategory": "Coffee shop",
"phone": "+62 21 5550 100",
"websiteUri": "https://kopi1.example.com",
"placeId": "place_1_0",
"verificationState": "verified",
"status": "active",
"lastSyncedAt": "2026-10-14T01:05:00.000Z"
}
}

Live (?live=1). MauKirim re-reads the business from Google (one metered call), updates the stored copy, and also returns the two fields the list does not carry: the description and the opening hours.

Terminal window
curl "https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab?live=1" \
-H "Authorization: Bearer mk_live_…"
const response = await fetch(`https://app.maukirim.com/api/v1/gmb/locations/${id}?live=1`, {
headers: { Authorization: `Bearer ${process.env.MAUKIRIM_KEY}` },
})
const { location, description, regularHours } = await response.json()
$curl = curl_init("https://app.maukirim.com/api/v1/gmb/locations/$id?live=1");
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('MAUKIRIM_KEY')],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($curl), true);
{
"ok": true,
"live": true,
"location": { "id": "gml_8f3kq2x1ab", "title": "Kopi Nusantara 1-1", "status": "active", "…": "…" },
"description": "Fresh coffee, daily.",
"regularHours": {
"periods": [
{ "openDay": "MONDAY", "openTime": { "hours": 8 }, "closeDay": "MONDAY", "closeTime": { "hours": 17 } }
]
}
}

description and regularHours are null when the business has none. A live read of a disabled business is 409 gmb_location_disabled; of a removed_upstream one, 404 gmb_location_not_found.

HTTP Code Condition
402 gmb_plan_required The package does not include the connector
403 api_key_scope_denied Neither read nor gmb
403 gmb_forbidden (live) Google says the connected account has no rights over the business
404 gmb_location_not_found Not your business, no such business, or (live) it left Google. Identical for all of these — the API never confirms that an id is real
409 gmb_reconnect_required (live) The connection must be reconnected in the dashboard
409 gmb_location_disabled (live) The business or its connection is switched off
429 gmb_rate_limited (live) No budget now; Retry-After and retryAfterSeconds say how long
502 gmb_upstream_error (live) Google failed

Changes the public Google listing of one business. Only an allow-listed set of fields can be changed; anything else is refused rather than ignored. To change a category, find its resource name first with GET …/categories.

Scope gmb
Body application/json, at least one field
Header Idempotency-Key (optional, recommended): 8–128 characters of letters, digits and _ . : -
Field Type Limits
title string 1–100 characters
description string 0–750 characters (empty clears it)
phone string Up to 32 characters, digits and + ( ) . - space, starting with + or a digit
websiteUri string An absolute http or https URL, up to 2000 characters
regularHours object Google’s regularHours shape ({ "periods": [ … ] })
specialHours object Google’s specialHours shape ({ "specialHourPeriods": [ … ] }): holidays and other days that differ from the regular hours
openStatus string OPEN, CLOSED or CLOSED_TEMPORARILY
categories array 1–10 category resource names such as categories/gcid:coffee_shop (letters, digits and _ : . - after categories/). The first is the primary category
labels array Up to 10 texts of 1–255 characters, your own tags for the business (an empty list clears them)

Anything else — address, status, placeId… — answers 400 invalid_input. A category that Google does not know is rejected by Google as 400 invalid_input too. A body with no field is 400 invalid_input too. MauKirim checks the limits before it calls Google, so an invalid edit spends no budget.

Terminal window
curl -X PATCH https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab \
-H "Authorization: Bearer mk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: edit-gml_8f3kq2x1ab-2026-10-14" \
-d '{ "title": "Kopi Nusantara — Melati", "phone": "+62 21 5550 200", "description": "Fresh coffee, daily, until 17:00." }'
# Close a business temporarily and set its categories (primary first):
curl -X PATCH https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab \
-H "Authorization: Bearer mk_live_…" -H "Content-Type: application/json" \
-H "Idempotency-Key: edit-gml_8f3kq2x1ab-closed-1" \
-d '{ "openStatus": "CLOSED_TEMPORARILY", "categories": ["categories/gcid:coffee_shop", "categories/gcid:cafe"] }'
const response = await fetch(`https://app.maukirim.com/api/v1/gmb/locations/${id}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MAUKIRIM_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `edit-${id}-2026-10-14`,
},
body: JSON.stringify({ title: "Kopi Nusantara — Melati", phone: "+62 21 5550 200" }),
})
$curl = curl_init("https://app.maukirim.com/api/v1/gmb/locations/$id");
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MAUKIRIM_KEY'),
'Content-Type: application/json',
"Idempotency-Key: edit-$id-2026-10-14",
],
CURLOPT_POSTFIELDS => json_encode(['title' => 'Kopi Nusantara — Melati', 'phone' => '+62 21 5550 200']),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($curl), true);
{
"ok": true,
"replayed": false,
"location": {
"id": "gml_8f3kq2x1ab",
"title": "Kopi Nusantara — Melati",
"phone": "+62 21 5550 200",
"status": "active",
"…": "…"
},
"description": "Fresh coffee, daily, until 17:00.",
"regularHours": null
}

The response is the business as Google now has it, so you see what was actually applied. Repeating the same request with the same Idempotency-Key returns the stored answer with "replayed": true and makes no second write at Google; the same key with a different body is 409 gmb_idempotency_conflict. The change costs one Google call (a replay costs none), so it can be answered 429 gmb_rate_limited — see rate limits.

HTTP Code Condition
400 invalid_input Not a JSON object; a field that cannot be changed; a value outside its limits; no field; a malformed Idempotency-Key. Also when Google rejects the value
402 gmb_plan_required The package does not include the connector
403 api_key_scope_denied The key lacks gmb (a read key cannot write)
403 gmb_forbidden Google says the connected account has no right to edit this business
404 gmb_location_not_found Not your business, no such business, or it left Google
409 gmb_reconnect_required The connection needs to be reconnected
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; also an identical request with the same key still running
502 gmb_upstream_error Google failed

Deletes the business from Google. Its listing, and the reviews and photos on it, go with it, and it cannot be undone from MauKirim. Opening a business again means creating it again as a new, unverified one.

Scope gmb
Path id — the business id (gml_…)
Body JSON { "confirmTitle": "<the business's exact title>" }

The body is required. confirmTitle must equal the business’s exact title as MauKirim shows it (title in GET /gmb/locations/{id}): same spelling, same capitals. This is the way a repository host asks you to type the name before deleting it; a missing or different title is 400 invalid_input and nothing is sent to Google. It has no Idempotency-Key, and repeating it after success answers 404 gmb_location_not_found, because the business is gone.

Terminal window
curl -X DELETE https://app.maukirim.com/api/v1/gmb/locations/gml_8f3kq2x1ab \
-H "Authorization: Bearer mk_live_…" \
-H "Content-Type: application/json" \
-d '{ "confirmTitle": "Kopi Nusantara 1-1" }'
const response = await fetch(`https://app.maukirim.com/api/v1/gmb/locations/${id}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MAUKIRIM_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ confirmTitle: "Kopi Nusantara 1-1" }),
})
$curl = curl_init("https://app.maukirim.com/api/v1/gmb/locations/$id");
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MAUKIRIM_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['confirmTitle' => 'Kopi Nusantara 1-1']),
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($curl), true);
{ "ok": true }

MauKirim keeps its own row for the business and sets its status to removed_upstream, so usage and history still point at something. From then on the business is not found by anything that would call Google, and it no longer counts against your allowance. If you only want to stop managing a business from MauKirim without removing it from Google, disconnect its connection instead.

The call costs one Google call (see rate limits).

HTTP Code Condition
400 invalid_input confirmTitle is missing or is not the business’s exact title; nothing was sent to Google
402 gmb_plan_required The package does not include the connector
403 api_key_scope_denied The key lacks gmb
403 gmb_forbidden Google says the connected account may not delete this business (only an owner can)
404 gmb_location_not_found Not your business, no such business, it left Google, or it was already deleted
409 gmb_reconnect_required / gmb_location_disabled The connection needs reconnecting; the business or its connection is switched off
429 gmb_rate_limited No budget now (Retry-After)
502 gmb_upstream_error Google failed