How to Manage Google Business Profile Photos via API

Upload cover, logo, interior, and gallery photos to a Google Business Profile location programmatically, list what's there, delete outdated images, and keep photo sets consistent across every location.

Two ways a photo reaches the profile

Profile mediaphoto post format
EndpointPOST .../google_business/create_mediaPOST /api/posts with format: "photo"
CategoryAny: COVER, LOGO, INTERIOR, MENU, …Always ADDITIONAL (the general gallery)
DescriptionUp to 2,000 charactersNone
Scheduling, drafts, approvals, queuesNo, immediateYes, same as any post
Listed byGET .../google_business/mediaGET /api/posts

Use profile media when the category matters: setting the cover photo, replacing the logo, tagging interior and exterior shots so Google files them correctly. Use the photo post format when the photo is part of a content calendar and should go through the same scheduling and approval flow as posts.

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

Google’s photo requirements

Requirement
FormatJPG or PNG
File size10 KB to 5 MB
Minimum resolution250 × 250 px
Recommended720 × 720 px; cover photos 16:9, 1024 × 576 px
ContentWell-lit, in focus, no heavy filters or overlaid text

Google reviews every photo before it appears publicly. An image that violates the content policy is rejected silently from the public listing; the API call itself succeeds.

Upload a photo with a category

Google fetches the file from the URL you give it, so media_url has to be a public HTTPS address that stays valid for a while. A short-lived signed URL or an address on a private network fails.

Terminal window
curl -X POST "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/create_media" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": "accounts/113344/locations/558899",
"media_url": "https://cdn.acme.example/storefront-2026.jpg",
"category": "EXTERIOR",
"description": "Storefront on Market Street after the September refit"
}'

Available categories:

CategoryUsed for
COVERThe large photo at the top of the listing
PROFILEThe small square identity photo
LOGOBrand logo
EXTERIOR / INTERIORBuilding and premises
PRODUCTProducts on sale
AT_WORKStaff, services being performed
FOOD_AND_DRINK / MENUDishes and printed menus
COMMON_AREA / ROOMSLodging and shared spaces
TEAMSTeam photos
ADDITIONALEverything else (default)

COVER, PROFILE, and LOGO are single-slot categories: uploading a new one replaces the current one on the listing. Google decides how the cover is cropped, so upload it at 16:9 with the subject centred.

Terminal window
curl -X POST "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/create_media" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": "accounts/113344/locations/558899",
"media_url": "https://cdn.acme.example/logo-2026.png",
"category": "LOGO"
}'
Terminal window
curl "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/media?location_id=accounts/113344/locations/558899&page_size=100" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"mediaItems": [
{
"name": "accounts/113344/locations/558899/media/AF1QipN…",
"mediaFormat": "PHOTO",
"locationAssociation": { "category": "EXTERIOR" },
"googleUrl": "https://lh3.googleusercontent.com/…",
"createTime": "2026-09-16T09:12:00Z",
"dimensions": { "widthPixels": 1600, "heightPixels": 1200 },
"insights": { "viewCount": "1284" }
}
],
"nextPageToken": ""
}

name is the handle for deletion. Page size goes up to 250; pass page_token from the response to continue. Customer-uploaded photos appear in this list too, with no locationAssociation, so filter on it to see only the business’s own uploads.

Delete a photo

Terminal window
curl -X DELETE "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/delete_media" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": "accounts/113344/locations/558899",
"media_name": "accounts/113344/locations/558899/media/AF1QipN…"
}'

Only media the business uploaded can be deleted. Customer photos have to be flagged to Google through the listing, which the API does not expose.

Push one photo set to every location

A rebrand or a seasonal campaign usually means the same cover and logo on every location. Loop the placements:

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 assets = [
{ media_url: "https://cdn.acme.example/cover-autumn.jpg", category: "COVER" },
{ media_url: "https://cdn.acme.example/logo-2026.png", category: "LOGO" },
];
for (const loc of locations) {
for (const asset of assets) {
await fetch(`${BASE}/api/profiles/${PROFILE}/google_business/create_media`, {
method: "POST",
headers,
body: JSON.stringify({ location_id: loc.id, ...asset }),
});
}
}

Location-specific photos (the actual storefront) stay per location; only the brand assets are shared.

Errors

StatusCause
400location_id or media_url missing, an unknown category, or a description over 2,000 characters
404The profile cannot see that location_id, or media_name does not exist
422Google could not fetch the URL, the file fails the format or size rules, or the category is not allowed for the location’s type

Full parameter tables are on the Google Business API reference.

Ready to get started?

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