Google Business connections
A connection is one Google identity — a Google account or an agency manager account — that you (or one of your own clients, through the hosted connect flow) authorized. It is what holds the permission and what you can revoke; its businesses (locations) are listed beneath it. You can also create a new business under a connection (below).
GET /gmb/connections
Section titled “GET /gmb/connections”Lists your connections. It reads MauKirim’s own table: no Google call and no budget.
| Scope | read or gmb |
| Auth | Authorization: Bearer mk_live_… |
| Pagination | none |
| Query param | Type | Meaning |
|---|---|---|
externalId |
string | Only the connections made for one of your own clients — your id for them, as you passed it when creating a connect session. An empty value (?externalId=) keeps only the connections you made yourself |
curl https://app.maukirim.com/api/v1/gmb/connections \ -H "Authorization: Bearer mk_live_…"const response = await fetch("https://app.maukirim.com/api/v1/gmb/connections", { headers: { Authorization: `Bearer ${process.env.MAUKIRIM_KEY}` },})const { connections } = await response.json()$curl = curl_init('https://app.maukirim.com/api/v1/gmb/connections');curl_setopt_array($curl, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('MAUKIRIM_KEY')], CURLOPT_RETURNTRANSFER => true,]);$connections = json_decode(curl_exec($curl), true)['connections'];{ "ok": true, "connections": [ { "id": "gmc_2m9dq7zk0p", "label": "Agency account", "googleEmail": "agency@example.com", "externalId": "", "status": "active", "lastError": null, "lastSyncedAt": "2026-10-14T01:05:00.000Z", "createdAt": "2026-10-14T01:04:30.000Z", "locationCount": 3 } ]}| Field | Meaning |
|---|---|
id |
The connection id (gmc_…); filter GET /gmb/locations with connectionId |
label |
The name you gave it when connecting |
googleEmail |
The Google account, for display |
externalId |
Your id for the end user who connected it through the hosted flow, or "" for a connection you made yourself |
status |
active, needs_reauth (Google refused the permission; reconnect in the dashboard), revoked (you disconnected it) or suspended (switched off by an operator) |
lastError |
A short code for the last failure, or null |
lastSyncedAt |
When its businesses were last read from Google |
locationCount |
Businesses under it that are switched on. Businesses a connect or sync finds arrive switched off and cost no allowance; turn on the ones you want |
The response never contains a token, the saved permission or any Google account identifier beyond the email address.
Connecting is a person’s act
Section titled “Connecting is a person’s act”There is no API endpoint that connects a Google account for you, on purpose: connecting needs the owner of the Google account to approve Google’s consent screen in a browser. An API key cannot do that, and neither can an MCP assistant. There are two ways to get a connection:
- You connect one yourself, signed in to the dashboard (Services → Google Business → Connect Google account). See Google Business Profile.
- One of your own clients connects theirs, through a link your server creates: Connect your clients’ Google Business Profiles and connect sessions.
Reconnecting after needs_reauth is also the owner’s act, in the dashboard (or a new connect session for a
client).
POST /gmb/connections/{id}/locations
Section titled “POST /gmb/connections/{id}/locations”Creates a new business on Google under this connection’s Google account and imports it into MauKirim at once.
201 Created. Use it to open a branch for a client, or to put a business that has no listing yet on Google.
| Scope | gmb |
| Path | id — the connection id (gmc_…) |
| Header | Idempotency-Key (optional, strongly recommended): 8–128 characters of letters, digits and _ . : -. It is passed to Google as the request id, so a retry can never create a second business |
| Field | Required | Rule |
|---|---|---|
title |
yes | The business name, 1–100 characters |
categories |
yes | 1–10 category resource names such as categories/gcid:coffee_shop. The first is the primary category. Find names with categories |
address |
yes | An object: regionCode (required, two letters such as ID), addressLines (required, 1–5 lines of text), and optional locality (city), postalCode, administrativeArea (province or state), languageCode |
phone |
no | Up to 32 characters, digits and + ( ) . - space, starting with + or a digit |
websiteUri |
no | An absolute http or https URL, up to 2000 characters |
description |
no | Up to 750 characters |
languageCode |
no | The business’s language, for example id |
regularHours |
no | Google’s regularHours shape ({ "periods": [ … ] }) |
accountName |
only if needed | accounts/123. Needed only when the Google identity manages several Business Profile accounts; leave it out otherwise. Without it, and with several accounts, the call is 400 invalid_input and the message lists them |
validateOnly |
no | true has Google check the listing and creates nothing (see below) |
Any other field is 400 invalid_input. MauKirim checks the whole listing before it calls Google, so an invalid
one spends no budget.
Check first with validateOnly. "validateOnly": true asks Google to validate the listing exactly as it
would for a real create, and nothing is created at Google or in MauKirim. The answer is
{ "ok": true, "replayed": false, "validated": true, "location": null }. A listing Google would refuse comes
back as 400 invalid_input. A validation does not count against your business allowance.
curl -X POST https://app.maukirim.com/api/v1/gmb/connections/gmc_2m9dq7zk0p/locations \ -H "Authorization: Bearer mk_live_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: create-client-17-branch-2" \ -d '{ "title": "Kopi Baru", "categories": ["categories/gcid:coffee_shop", "categories/gcid:cafe"], "phone": "+62 21 5550 123", "websiteUri": "https://kopibaru.example", "address": { "regionCode": "ID", "locality": "Jakarta", "postalCode": "10110", "addressLines": ["Jl. Melati 1"] } }'const response = await fetch(`https://app.maukirim.com/api/v1/gmb/connections/${connectionId}/locations`, { method: "POST", headers: { Authorization: `Bearer ${process.env.MAUKIRIM_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "create-client-17-branch-2", }, body: JSON.stringify({ title: "Kopi Baru", categories: ["categories/gcid:coffee_shop"], address: { regionCode: "ID", locality: "Jakarta", postalCode: "10110", addressLines: ["Jl. Melati 1"] }, }),})const { location } = await response.json()$curl = curl_init("https://app.maukirim.com/api/v1/gmb/connections/$connectionId/locations");curl_setopt_array($curl, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('MAUKIRIM_KEY'), 'Content-Type: application/json', 'Idempotency-Key: create-client-17-branch-2', ], CURLOPT_POSTFIELDS => json_encode([ 'title' => 'Kopi Baru', 'categories' => ['categories/gcid:coffee_shop'], 'address' => ['regionCode' => 'ID', 'locality' => 'Jakarta', 'postalCode' => '10110', 'addressLines' => ['Jl. Melati 1']], ]), CURLOPT_RETURNTRANSFER => true,]);$location = json_decode(curl_exec($curl), true)['location'];{ "ok": true, "replayed": false, "validated": false, "location": { "id": "gml_4t7nw1c9de", "connectionId": "gmc_2m9dq7zk0p", "title": "Kopi Baru", "addressSummary": "Jl. Melati 1, Jakarta 10110", "primaryCategory": "Coffee shop", "phone": "+62 21 5550 123", "websiteUri": "https://kopibaru.example/", "placeId": "ChIJ…", "verificationState": "unverified", "status": "active", "lastSyncedAt": "2026-10-14T03:10:00.000Z" }}The location is the same object GET /gmb/locations/{id} returns, with an id
you can use in every other call straight away. Repeating the request with the same Idempotency-Key returns the
stored answer with "replayed": true and creates no second business; the same key with a different body is
409 gmb_idempotency_conflict.
It is created switched on, counts against your allowance, and is refused first. (Unlike a business found by a sync, which arrives off and
costs nothing until you choose it.) Your package allows a fixed number of active
businesses (see Google Business Profile). When you are at
the limit the call is 409 gmb_location_limit before anything is created on Google, so you never end up with
a business that exists on Google but that MauKirim will not show. Disconnect or delete a business, or change
package, to make room. A validation is not refused for the limit.
A new business is not verified. Google shows a freshly created business as unverified: many of its public
changes only take effect once it is verified. Continue with the verification endpoints
(options, start, then enter the code), and add its photos and
posts afterwards.
The call costs one Google call, plus one more to find the Google account when you leave out accountName
(see rate limits). Google may need approval for the Business Information API in the
deployment’s Google project; if it is not enabled the answer is 503 gmb_api_unavailable.
Errors
Section titled “Errors”| HTTP | Code | Condition |
|---|---|---|
| 400 | invalid_input |
A field that cannot be set, a missing or invalid title, categories or address, an invalid phone, website or accountName, several Google accounts and no accountName, a malformed Idempotency-Key, or Google rejected the listing |
| 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 this connection may not create businesses (or it manages no Business Profile account) |
| 404 | gmb_connection_not_found |
No such connection, or it is not this account’s. Identical for both |
| 409 | gmb_location_limit |
The package’s business allowance is full; nothing was created |
| 409 | gmb_reconnect_required / gmb_idempotency_conflict |
The connection needs reconnecting; the key was used with a different body |
| 429 | gmb_rate_limited |
No budget now (Retry-After); also an identical request with the same key still running |
| 502 | gmb_upstream_error |
Google failed |
| 503 | gmb_api_unavailable |
The Business Information API is not enabled or approved for the deployment’s Google project |
DELETE /gmb/connections/{id}
Section titled “DELETE /gmb/connections/{id}”Disconnects one connection: this is how you offboard a client.
| Scope | gmb |
| Path | id — the connection id (gmc_…) |
MauKirim asks Google to forget the saved permission (best effort: a Google outage never stops you leaving), marks
the connection revoked, and switches every business under it to disabled, so they stop counting against
your allowance. History and usage stay, and the public listings themselves are not touched. Read calls on the
stored copy of a disabled business still work; anything that would call Google answers gmb_location_disabled.
curl -X DELETE https://app.maukirim.com/api/v1/gmb/connections/gmc_2m9dq7zk0p \ -H "Authorization: Bearer mk_live_…"const response = await fetch(`https://app.maukirim.com/api/v1/gmb/connections/${connectionId}`, { method: "DELETE", headers: { Authorization: `Bearer ${process.env.MAUKIRIM_KEY}` },})$curl = curl_init("https://app.maukirim.com/api/v1/gmb/connections/$connectionId");curl_setopt_array($curl, [ CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('MAUKIRIM_KEY')], CURLOPT_RETURNTRANSFER => true,]);$result = json_decode(curl_exec($curl), true);{ "ok": true }To bring the client back, create a new connect session with the same externalId: the same Google account for
the same externalId reuses the connection and its businesses come back.
Errors
Section titled “Errors”| HTTP | Code | Condition |
|---|---|---|
| 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 |
(GET) Neither read nor gmb; (DELETE) no gmb; forbidden for an inactive account |
| 404 | gmb_connection_not_found |
(DELETE) No such connection, or it is not this account’s. Identical for both |
| 500 | server_error |
Unexpected |