Photos
Attach a logo, cover and gallery photos to a location, star the hero image, and remove stale files with the listingsapi-js SDK.
Photos are attached to a location by public URL: you host the image, the
platform fetches and stores it, and publishers pick it up from there. A
location keeps one logo, one cover, and any number of additional photos, of
which at most four can be starred. All photo methods live directly on the
ListingsAPI client instance and are scoped to a location ID. Numeric
location IDs are encoded automatically, so 1800289 and
'TG9jYXRpb246MTgwMDI4OQ==' are interchangeable.
Uploads have two shapes of success. A small batch is stored inline and comes
back with the finished media; a large batch is queued instead, and you get a
request ID to poll. Branch on whether request.requestId is present rather
than counting your own images.
fetchLocationPhotos
List every media file attached to a location: logo, cover, and additional images. Call it before an upload to see what is missing, and to collect the photo IDs the star and remove methods take.
const photos = await client.fetchLocationPhotos(1800289); for (const p of photos) {console.log(p.id, p.categoryName, p.starred, p.url);}Parameters
Resolves to the mediaFilesOfLocation array, defaulting to an empty array.
Each file carries id, url, thumbnailUrl, categoryName (Logo,
Cover, or Additional), categoryId, fileType, starred, and
viewsCount. A location with no media returns an empty array, which is the
normal response for a freshly created location and not an error.
addLocationPhotos
Attach one or more images to a location. Each item needs a publicly reachable
JPEG or PNG URL in photo, plus the slot it fills in type.
const result = await client.addLocationPhotos(1800289, [{ photo: 'https://cdn.example.com/brightsmile/logo.png', type: 'LOGO' },{ photo: 'https://cdn.example.com/brightsmile/storefront.jpg', type: 'ADDITIONAL' },]); if (result.request?.requestId) {// Queued: poll fetchPhotoUploadStatus with this idconsole.log('queued', result.request.requestId);} else {for (const p of result.photos ?? []) { console.log('stored', p.id, p.type);}}Photo options
Parameters
Resolves to the addLocationPhotos payload: success, photos, request,
errors, and clientMutationId. On an inline upload, photos holds the new
media with each one's id, databaseId, url, thumbnailUrl, type, and
starred, and request is an empty object. On a queued upload, photos is
empty and request.requestId is set. Both are successes.
Images must be JPEG or PNG, and publisher size rules apply per slot. A
location keeps a single logo and a single cover, so re-uploading either
replaces the existing image. Set type deliberately: ADDITIONAL is where
gallery images belong.
fetchPhotoUploadStatus
Poll a queued upload until it settles. Use the request.requestId that
addLocationPhotos handed back.
let job = await client.fetchPhotoUploadStatus('e6c2d9d9-9a18-4015-88cf-9a4e19a6f49a'); while (job.status === 'PROCESSING') {await new Promise((resolve) => setTimeout(resolve, 5000));job = await client.fetchPhotoUploadStatus('e6c2d9d9-9a18-4015-88cf-9a4e19a6f49a');} for (const p of job.photos ?? []) {if (p.status === 'ERROR') { console.log('rejected:', p.errorMessage);}}Parameters
The payload carries requestId, a job status of PROCESSING, SUCCESS, or
ERROR, and a photos array whose entries each hold mediaId,
locationId, their own status, and an errorMessage.
The job status reports that processing finished, not that every image
landed. A SUCCESS job can still contain rejected photos, so once it leaves
PROCESSING, walk photos and read each entry. Back off a few seconds
between polls: a tight loop is a fast way to get rate limited. Only poll IDs
from a recent upload, because an expired one never settles.
starLocationPhotos
Star additional photos so they get priority placement on publishers that
support featured imagery, or unstar them by passing false.
// Feature a photoconst result = await client.starLocationPhotos(1800289,['TWVkaWFGaWxlOjU4Nzg5MzI='],true,); console.log(result.success, result.photos[0].starred); // Reverse itawait client.starLocationPhotos(1800289, ['TWVkaWFGaWxlOjU4Nzg5MzI='], false);Parameters
Resolves to the starUnstarLocationPhotos payload: success, photos, and
a singular error object rather than an errors array. Each returned photo
carries id, databaseId, url, thumbnailUrl, type, and its new
starred value.
Only ADDITIONAL photos can be starred; the logo and cover are already
primary. A location can hold at most four starred photos, and the check
counts the ones already starred plus the ones in your call, so going past four
fails the whole request.
removeLocationPhotos
Detach photos from a location by their IDs. Use it to retire stale gallery images or to undo a bad import.
const result = await client.removeLocationPhotos(1800289, ['TWVkaWFGaWxlOjU4Nzg5MzI=',]); console.log(result.removedCount); // 1Parameters
Resolves to the removeLocationPhotos payload: removedCount, errors, and
clientMutationId. A removedCount of zero with a null errors means none
of the IDs you sent were attached to that location, which is how you detect
IDs that were already gone.
Only ADDITIONAL photos are removable here. Replace a logo or cover by
uploading a new one instead. Removing media detaches it from the location; it
does not necessarily purge the file from the account media library.
Typical gallery refresh
import { ListingsAPI } from 'listingsapi-js'; const client = new ListingsAPI(); // reads LISTINGSAPI_KEY from envconst LOCATION_ID = 1800289; // Brightsmile Dental // 1. See what is already attachedconst existing = await client.fetchLocationPhotos(LOCATION_ID);const hasLogo = existing.some((p) => p.categoryName === 'Logo'); // 2. Upload only what is missing, plus the new hero shotconst uploaded = await client.addLocationPhotos(LOCATION_ID, [...(hasLogo ? [] : [{ photo: 'https://cdn.example.com/brightsmile/logo.png', type: 'LOGO' as const }]),{ photo: 'https://cdn.example.com/brightsmile/hero-2026.jpg', type: 'ADDITIONAL' as const },]); // 3. Feature the new hero shotconst hero = (uploaded.photos ?? []).find((p) => p.type === 'ADDITIONAL');if (hero) {await client.starLocationPhotos(LOCATION_ID, [hero.id], true);} // 4. Retire last season's imagesconst stale = existing.filter((p) => p.categoryName === 'Additional' && !p.starred).map((p) => p.id);if (stale.length > 0) {const removed = await client.removeLocationPhotos(LOCATION_ID, stale);console.log('removed ' + removed.removedCount + ' stale photos');}Related resources
- Locations, look up location IDs
- Error handling, typed errors thrown by every method
- Upload a logo, cover and photos, the full walkthrough
- REST reference: get photos, upload photos, bulk upload status, star photos, remove photos