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.

TypeScript
const photos = await client.fetchLocationPhotos(1800289); for (const p of photos) {console.log(p.id, p.categoryName, p.starred, p.url);}

Parameters

ParameterTypeDescription
locationIdstring | numberLocation to read photos for.

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.

TypeScript
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

ParameterTypeDescription
photostringPublic JPEG or PNG URL the platform fetches and stores. The field is photo, not url. Required.
type'LOGO' | 'COVER' | 'ADDITIONAL'Which slot the image fills. Required.
partnerMediaIdstringYour own identifier for the file, kept alongside it for partner integrations.

Parameters

ParameterTypeDescription
locationIdstring | numberLocation to attach media to.
photosPhotoInput[]Images to attach. Must not be empty.

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.

TypeScript
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

ParameterTypeDescription
requestIdstringThe request ID from a queued addLocationPhotos call.

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.

TypeScript
// 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

ParameterTypeDescription
locationIdstring | numberLocation the photos belong to.
mediaIdsstring[]Photo IDs to star or unstar, from fetchLocationPhotos or an upload response. Sent on the wire as photoIds.
starredbooleantrue to star the given photos, false to unstar them.

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.

TypeScript
const result = await client.removeLocationPhotos(1800289, ['TWVkaWFGaWxlOjU4Nzg5MzI=',]); console.log(result.removedCount); // 1

Parameters

ParameterTypeDescription
locationIdstring | numberLocation the photos belong to.
photoIdsstring[]Photo IDs to detach, from fetchLocationPhotos or an upload response.

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.

refresh_photos.ts
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');}