How to Update Google Business Profile Hours via API
Set regular hours, holiday hours, and kitchen or delivery hours on a Google Business Profile location programmatically, mark a temporary closure, and roll the change across every location.
The three hour blocks
A Google Business Profile location carries hours in three fields, and Google treats them independently:
| Field | What it holds | Shown when |
|---|---|---|
regularHours | The weekly schedule, one period per open stretch | Every week |
specialHours | Date-specific overrides, open or closed | Only on those dates, replacing regular hours |
moreHours | Named extra sets: kitchen, delivery, pickup, drive-through, senior hours | Alongside regular hours, under their own label |
All three are updated through one endpoint, PATCH /api/profiles/:profile_id/google_business/update_hours, with a fields array naming which blocks the request replaces. A block named in fields is replaced wholesale, so send the complete schedule every time, not a diff.
Every call needs the location’s location_id, which is the placement id from List placements: accounts/{accountId}/locations/{locationId}.
Read the current hours
Hours are part of the location resource, so read them with the location endpoint:
curl "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/location?location_id=accounts/113344/locations/558899" \ -H "Authorization: Bearer YOUR_API_KEY"{ "name": "locations/558899", "title": "Acme Coffee", "regularHours": { "periods": [ { "openDay": "MONDAY", "openTime": { "hours": 9 }, "closeDay": "MONDAY", "closeTime": { "hours": 17 } } ] }, "specialHours": { "specialHourPeriods": [] }, "moreHours": [], "openInfo": { "status": "OPEN" }}Google returns times as { "hours": 9, "minutes": 30 } objects. The update endpoint accepts either that shape or a "09:30" string, so you can edit the response and send it back, or build periods from strings.
Update regular hours
curl -X PATCH "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/update_hours" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location_id": "accounts/113344/locations/558899", "fields": ["regularHours"], "regularHours": { "periods": [ { "openDay": "MONDAY", "openTime": "07:00", "closeDay": "MONDAY", "closeTime": "18:00" }, { "openDay": "TUESDAY", "openTime": "07:00", "closeDay": "TUESDAY", "closeTime": "18:00" }, { "openDay": "WEDNESDAY", "openTime": "07:00", "closeDay": "WEDNESDAY", "closeTime": "18:00" }, { "openDay": "THURSDAY", "openTime": "07:00", "closeDay": "THURSDAY", "closeTime": "18:00" }, { "openDay": "FRIDAY", "openTime": "07:00", "closeDay": "FRIDAY", "closeTime": "21:00" }, { "openDay": "SATURDAY", "openTime": "08:00", "closeDay": "SATURDAY", "closeTime": "21:00" } ] } }'Rules that follow from Google’s period model:
- A day with no period is closed. Sunday above has none, so the profile shows “Closed” on Sunday.
- Split shifts are two periods on the same day. A restaurant open 11:00 to 14:30 and 17:00 to 22:00 sends two
MONDAYentries. - Hours past midnight set
closeDayto the next day. Friday 18:00 to Saturday 02:00 isopenDay: FRIDAY, closeDay: SATURDAY, closeTime: "02:00". - Open 24 hours is
openTime: "00:00"andcloseTime: "24:00"on the same day.
Set holiday hours
Special hours override the regular schedule on specific dates only. Send a specialHourPeriods list; each entry is either closed: true or an open and close time:
curl -X PATCH "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/update_hours" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location_id": "accounts/113344/locations/558899", "fields": ["specialHours"], "specialHours": { "specialHourPeriods": [ { "startDate": { "year": 2026, "month": 12, "day": 24 }, "openTime": "08:00", "closeTime": "14:00" }, { "startDate": { "year": 2026, "month": 12, "day": 25 }, "closed": true }, { "startDate": { "year": 2026, "month": 12, "day": 26 }, "closed": true }, { "startDate": { "year": 2027, "month": 1, "day": 1 }, "closed": true } ] } }'Google prompts profile owners to confirm holiday hours ahead of major holidays and flags listings that never do. Setting them through the API before the prompt appears keeps the listing from showing “Hours might differ” to customers.
To clear all special hours, send fields: ["specialHours"] with an empty specialHourPeriods array. Past dates are ignored by Google, so there is no need to prune them.
Add kitchen, delivery, or pickup hours
moreHours is an array of named hour sets. Each has a hoursTypeId and its own periods, in the same shape as regular hours:
curl -X PATCH "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/update_hours" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location_id": "accounts/113344/locations/558899", "fields": ["moreHours"], "moreHours": [ { "hoursTypeId": "DELIVERY", "periods": [ { "openDay": "MONDAY", "openTime": "11:00", "closeDay": "MONDAY", "closeTime": "21:30" }, { "openDay": "TUESDAY", "openTime": "11:00", "closeDay": "TUESDAY", "closeTime": "21:30" } ] } ] }'Which hoursTypeId values a location accepts depends on its primary category. Fetch the category with view=FULL from the available categories endpoint; the response lists the moreHoursTypes Google allows for it, each with a hoursTypeId and display name. A type the category does not support is rejected.
Mark a temporary closure
A closure is not an hours change. It is the openInfo.status field on the location, updated through the location endpoint:
curl -X PATCH "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/update_location" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "location_id": "accounts/113344/locations/558899", "fields": ["openInfo"], "openInfo": { "status": "CLOSED_TEMPORARILY" } }'Send "status": "OPEN" to reopen. The regular hours stay stored while the location is closed, so nothing needs to be re-entered. CLOSED_PERMANENTLY also exists and is what it says.
Roll hours out across every location
Multi-location businesses and agencies rarely change one location at a time. List the placements once and loop:
const BASE = "https://api.postproxy.dev";const headers = { Authorization: `Bearer ${process.env.POSTPROXY_API_KEY}`, "Content-Type": "application/json",};
const { data: locations } = await fetch(`${BASE}/api/profiles/${PROFILE}/placements`, { headers }) .then((r) => r.json());
const christmas = { specialHourPeriods: [ { startDate: { year: 2026, month: 12, day: 25 }, closed: true }, { startDate: { year: 2026, month: 12, day: 26 }, closed: true }, ],};
for (const loc of locations) { const res = await fetch(`${BASE}/api/profiles/${PROFILE}/google_business/update_hours`, { method: "PATCH", headers, body: JSON.stringify({ location_id: loc.id, fields: ["specialHours"], specialHours: christmas }), }); if (!res.ok) console.error(loc.name, await res.text());}Locations with different regular schedules keep them; special hours only touch the listed dates.
Errors
| Status | Cause |
|---|---|
400 | location_id missing, or fields names a block that is not in the body |
404 | The profile cannot see that location_id |
422 | Google rejected the payload: an unsupported hoursTypeId, a malformed period, or a location whose category does not allow the change |
Full parameter tables and the other management endpoints are on the Google Business API reference.