How to Update Google Business Profile Attributes via API

Set payment options, accessibility, amenities, and menu or booking URLs on a Google Business Profile location programmatically — discover valid attributes first, then patch booleans, enums, and URLs without clearing the rest.

What attributes are

Attributes are the structured facts Google shows under a listing: “Wheelchair accessible entrance”, “Accepts credit cards”, “Free Wi-Fi”, “Outdoor seating”, plus the menu, ordering, and appointment links. They feed Google’s filters (“open now”, “wheelchair accessible”) and the “From the business” section.

Each attribute has a Google resource name (attributes/pay_credit_card) and one of three value types:

valueTypeValue fieldExample
BOOLvalues: [true] or [false]attributes/has_wheelchair_accessible_entrance
ENUM / REPEATED_ENUMrepeatedEnumValue: { setValues: [...] }attributes/wi_fi
URLuriValues: [{ uri: "https://..." }]attributes/url_menu

The attribute IDs a location may use are decided by its primary category and its country. A hair salon cannot set “Serves vegan dishes”; a location in one region may have payment attributes another region lacks. That is why every attribute workflow starts with a lookup.

Every request below takes location_id, the placement id from List placements.

Find the attributes a location can set

Terminal window
curl "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/available_attributes?location_id=accounts/113344/locations/558899" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"attributeMetadata": [
{
"parent": "attributes/has_wheelchair_accessible_entrance",
"valueType": "BOOL",
"displayName": "Wheelchair accessible entrance",
"groupDisplayName": "Accessibility"
},
{
"parent": "attributes/wi_fi",
"valueType": "ENUM",
"displayName": "Wi-Fi",
"groupDisplayName": "Amenities",
"valueMetadata": [
{ "value": "free_wi_fi", "displayName": "Free" },
{ "value": "paid_wi_fi", "displayName": "Paid" }
]
},
{
"parent": "attributes/url_menu",
"valueType": "URL",
"displayName": "Menu link",
"groupDisplayName": "Place page URLs"
}
]
}

parent is the ID you send back. For enums, valueMetadata lists the exact tokens accepted. The list is paginated at 200 per page; pass page_token from the response to continue.

To look up attributes for a category before a location exists, or to compare categories, pass category_name and region_code instead of relying on the location:

Terminal window
curl "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/available_attributes?location_id=accounts/113344/locations/558899&category_name=categories/gcid:restaurant&region_code=US" \
-H "Authorization: Bearer YOUR_API_KEY"

Read the current attributes

Terminal window
curl "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/attributes?location_id=accounts/113344/locations/558899" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"name": "locations/558899/attributes",
"attributes": [
{ "name": "attributes/has_wheelchair_accessible_entrance", "values": [true] },
{ "name": "attributes/url_menu", "uriValues": [{ "uri": "https://acme.example/menu" }] }
]
}

Only attributes that have a value are returned. An attribute that is available but unset does not appear.

Set booleans, enums, and URLs

One request can carry any mix of types:

Terminal window
curl -X PATCH "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/update_attributes" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": "accounts/113344/locations/558899",
"attributes": [
{ "name": "attributes/pay_credit_card", "values": [true] },
{ "name": "attributes/has_wheelchair_accessible_entrance", "values": [true] },
{ "name": "attributes/wi_fi", "repeatedEnumValue": { "setValues": ["free_wi_fi"] } },
{ "name": "attributes/url_menu", "uriValues": [{ "uri": "https://acme.example/menu" }] },
{ "name": "attributes/url_appointment", "uriValues": [{ "uri": "https://acme.example/book" }] }
]
}'

A false boolean is a statement, not an absence: values: [false] shows “No wheelchair accessible entrance” on the listing. Leave the attribute out entirely if the answer is unknown.

Why partial updates are safe

Google’s underlying call is masked: it replaces exactly the attributes named in the mask and leaves the rest alone. Postproxy sets attribute_mask to the names in your attributes array by default, so the request above changes five attributes and touches nothing else. There is no need to read and resend the whole set.

Remove an attribute

Removing means naming the attribute in the mask without giving it a value. Pass attribute_mask explicitly and leave the attribute out of attributes:

Terminal window
curl -X PATCH "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/update_attributes" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": "accounts/113344/locations/558899",
"attribute_mask": ["attributes/url_appointment"],
"attributes": []
}'

The same mechanism clears several at once: list every name to remove in attribute_mask. Anything in attributes that is also in the mask is set; anything in the mask but not in attributes is cleared.

Apply the same attributes to every location

Attributes are per location, so a chain that starts taking contactless payment updates each one. Loop over placements and skip the locations whose category does not offer the attribute:

const BASE = "https://api.postproxy.dev";
const headers = {
Authorization: `Bearer ${process.env.POSTPROXY_API_KEY}`,
"Content-Type": "application/json",
};
const qs = (o) => new URLSearchParams(o).toString();
const { data: locations } = await fetch(`${BASE}/api/profiles/${PROFILE}/placements`, { headers })
.then((r) => r.json());
for (const loc of locations) {
const available = await fetch(
`${BASE}/api/profiles/${PROFILE}/google_business/available_attributes?${qs({ location_id: loc.id })}`,
{ headers }
).then((r) => r.json());
const ids = new Set(available.attributeMetadata.map((a) => a.parent));
const attributes = [
{ name: "attributes/pay_credit_card", values: [true] },
{ name: "attributes/pay_mobile_nfc", values: [true] },
].filter((a) => ids.has(a.name));
if (!attributes.length) continue;
await fetch(`${BASE}/api/profiles/${PROFILE}/google_business/update_attributes`, {
method: "PATCH",
headers,
body: JSON.stringify({ location_id: loc.id, attributes }),
});
}

The availability check costs one extra read per location and avoids 422 responses on locations where the attribute does not exist.

Errors

StatusCause
400location_id missing, an attributes entry with no value field, or a value shape that does not match the attribute’s valueType
404The profile cannot see that location_id
422Google rejected an attribute ID or enum token the location’s category does not allow

Full parameter tables are on the Google Business API reference. Attribute changes go through Google’s review like edits made in the Business Profile dashboard, so a value can take time to appear publicly.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.