Skip to content
Dashboard

Guest Portal Guides

A guide is a list of local recommendations in the guest portal: “Coffee in Vail”, “Things to do downtown”. Each guide list holds places (a café, a restaurant, an activity), each with a name, a short recommendation, an address and optional photo and links. These are the lists managed under Settings → Guest content → Guides in the app. Every field is listed on the operation’s page in the API Reference.

OperationRequestPermission
listGuideListsGET /guide-listsview_guides
getGuideListGET /guide-lists/{id}view_guides
createGuideListPOST /guide-listsmodify_guides
updateGuideListPATCH /guide-lists/{id}modify_guides
addGuidePlacePOST /guide-placesmodify_guides
importGuidePlacePOST /guide-places/importmodify_guides
updateGuidePlacePATCH /guide-places/{id}modify_guides
removeGuidePlaceDELETE /guide-places/{id}modify_guides
deleteGuideListDELETE /guide-lists/{id}delete_guide
reorderGuideListsPOST /guide-lists/reordermodify_guides
reorderGuidePlacesPOST /guide-lists/{guideListId}/places/reordermodify_guides

Paths are relative to your region’s base URL, for example https://api-us.suiteop.com/api/v1. See Authentication for permissions.

modify_guides does not include view_guides. A key that edits guides usually needs both, because the list and place IDs come from the reads. Attaching a list to portals is an entity-scope write and needs modify_properties as well as modify_guides.

deleteGuideList deletes a list for good, and there is no archive. To take a list out of view without deleting it, remember that a list with no scopes appears in no guest portal, but with showOnBookingSites: true it is still published on every booking site of the organization. So remove its scopes and set showOnBookingSites to false.

  • Guide list: nickname is the label shown in the Guides tab. The guest-facing heading is a translation referenced by nameId; a list with no heading shows guests its nickname instead. category is a coarse filter value: food_and_drink, things_to_do, shopping, nightlife, outdoors, transit, services or essentials.
  • Place: one recommendation in a list. titleText and shortDescriptionText are plain English and are auto-translated into the portal’s other languages.
  • Scopes: the portals, properties, property groups and tags a list is attached to, written with the entity-scope operations and entityType: "guide_list". isActive on a list is true once it has at least one scope of any kind.
  • Booking sites: showOnBookingSites: true also publishes the list on the organization’s booking sites: those listing a property the list reaches, or all of them when the list has no scopes.

GET /guide-lists returns the same lists, in the same order, as the Guides tab. It is not paginated.

Terminal window
curl "https://api-us.suiteop.com/api/v1/guide-lists?portalSettingsId=2b7c4e10-…" \
-H "Authorization: Bearer sk_live_your_key_here"

portalSettingsId (optional, from listPortals) keeps only lists scoped directly to that portal. A list that reaches the portal only through a property, group or tag scope is not matched.

Each row carries id, nickname, category, showOnBookingSites, sortOrder, itemCount, portalSettingsIds (the portals it is scoped to directly), scopeCount, isActive, createdAt and updatedAt. An OAuth token whose user is limited to some properties sees scopeCount and portalSettingsIds narrowed to the scopes that reach its own properties.

GET /guide-lists/{id} returns the list with every place in guest order. Place names and recommendations come back as English text. The list’s guest-facing heading does not: pass nameId to GET /translations/{translationId}/entries (see Translations). A list in another organization or outside your property scope returns 404.

{
"data": {
"id": "5d1e8a40-…",
"nickname": "Vail — Coffee",
"nameId": "a93c0f12-…",
"category": "food_and_drink",
"showOnBookingSites": false,
"coverImageUrl": null,
"itemCount": 1,
"createdAt": "2026-10-09T09:12:44.512Z",
"updatedAt": "2026-10-09T09:12:44.512Z",
"places": [
{
"id": "c40b7e95-…",
"guideListId": "5d1e8a40-…",
"titleText": "Loaded Joe's",
"shortDescriptionText": "Our favourite breakfast burrito, five minutes' walk from the village.",
"imageUrl": null,
"priceCents": 2,
"websiteUrl": "https://example.com",
"rating": 4.6,
"ratingsCount": 812,
"googlePlaceId": null,
"googleMapsUrl": null,
"categories": ["cafe"],
"address": {
"formattedAddress": "141 E Meadow Dr, Vail, CO 81657, USA",
"streetAddress": "141 E Meadow Dr",
"city": "Vail",
"state": "CO",
"postalCode": "81657",
"countryCode": "US",
"latitude": 39.6413,
"longitude": -106.3734
},
"sortOrder": 0,
"createdAt": "2026-10-09T09:13:02.020Z",
"updatedAt": "2026-10-09T09:13:02.020Z"
}
]
},
"meta": { "requestId": "…" }
}

Places imported from Google, in the app or with importGuidePlace, carry a googlePlaceId; places added with addGuidePlace never do. Older places can have a null address or categories, and any address field that wasn’t stored is null.

Creating a list and attaching it to a portal

Section titled “Creating a list and attaching it to a portal”

A new list starts with no scopes, so isActive is false and it appears in no guest portal until you attach it. Created with showOnBookingSites: true, it is published on every booking site straight away. Create it first, then attach it in a separate call: the create returns the ID even if the scope write is later refused.

Terminal window
curl -X POST https://api-us.suiteop.com/api/v1/guide-lists \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Idempotency-Key: 4f0c2a9e-1b3d-4c5e-8f7a-9b0c1d2e3f4a" \
-H "Content-Type: application/json" \
-d '{
"nickname": "Vail — Coffee",
"name": "Coffee in Vail",
"category": "food_and_drink"
}'
FieldNotes
nicknameRequired, 1–200 characters. Label in the Guides tab, and the heading guests see when there is no name
nameOptional guest-facing heading in plain English, up to 200 characters. Stored as a translation
categoryOptional, one of the eight values above, or null for uncategorized
coverImageUrlOptional http(s) URL. An https image is copied into SuiteOp storage after the save (Unsplash and Google photos stay hotlinked); an http URL is stored as-is
showOnBookingSitesOptional, defaults to false

The response is 201 with id, nickname, nameId, category, showOnBookingSites, sortOrder, createdAt and updatedAt. The cover photo is not echoed back; read it with getGuideList.

Then attach the list, for example to one portal:

Terminal window
curl -X POST https://api-us.suiteop.com/api/v1/entity-scopes/add \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"entityType": "guide_list",
"entityId": "5d1e8a40-…",
"portalSettingsIds": ["2b7c4e10-…"]
}'

See Entity Scopes for the exact request shape and the other scope kinds.

PATCH /guide-lists/{id} takes { "data": { … } } with any of nickname, category, coverImageUrl and showOnBookingSites. Fields you omit are left unchanged; null clears category or coverImageUrl.

It cannot change the guest-facing heading (edit the translation behind nameId) or where the list shows (use the entity-scope operations).

POST /guide-places adds one place. Call it once per place.

const res = await fetch('https://api-us.suiteop.com/api/v1/guide-places', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SUITEOP_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
guideListId: '5d1e8a40-…',
titleText: "Loaded Joe's",
shortDescriptionText: "Our favourite breakfast burrito, five minutes' walk from the village.",
priceCents: 2,
categories: ['cafe'],
address: {
formattedAddress: '141 E Meadow Dr, Vail, CO 81657, USA',
latitude: null,
longitude: null,
},
}),
})
const { data: place } = await res.json()
FieldNotes
guideListIdRequired
addressRequired. formattedAddress is required, and latitude and longitude must be present but may be null. A place without coordinates still lists for guests but is left off the map. The other address fields are optional
titleTextPlain English, up to 200 characters
shortDescriptionTextPlain English, up to 2,000 characters
priceCentsA price level, not money. 1–4 shows as $–$$$$; other values hide the indicator
rating0–5. ratingsCount is how many ratings it is based on
categoriesGoogle Place types, for example ["cafe"] or ["restaurant", "bar"]. Values outside the published list are rejected
imageUrl, websiteUrl, googleMapsUrlhttp(s) URLs. An empty string is stored as null. An https imageUrl is copied into SuiteOp storage like coverImageUrl

addGuidePlace looks nothing up: it doesn’t search Google or fetch a photo. Send the details and links you already have, or use importGuidePlace to have SuiteOp fill them in.

A new place gets sortOrder 0, so it can list above places you added earlier. Call reorderGuidePlaces after a batch of adds to set the order.

The response is 201 with the place’s stored fields. Its name, recommendation and photo are not echoed back; read them with getGuideList.

POST /guide-places/import adds one place from its Google place ID and fills in what you leave out. It takes the same fields as addGuidePlace, except that titleText is required here (1 to 200 characters) and categories holds at most 20, plus googlePlaceId (required) and an optional googlePhotoReference:

  • Recommendation. Without shortDescriptionText, SuiteOp uses Google’s editorial summary of the place, and when Google has none, asks an AI model to write one from the name, address and categories (at most 20 here).
  • Photo. Without imageUrl, SuiteOp resolves googlePhotoReference (a places/<placeId>/photos/<photoId> name or a legacy photo token, up to 2,048 characters) to its Google photo, else picks an Unsplash photo by the place’s name. An imageUrl of null or "" counts as not sent, so an import always tries to add a photo.
  • What you send wins. A non-blank shortDescriptionText or imageUrl you send is stored as-is and skips that lookup.
Terminal window
curl -X POST https://api-us.suiteop.com/api/v1/guide-places/import \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Idempotency-Key: 9b1f3c2e-5d4a-4e8b-a7c6-2f0e1d3b5a49" \
-H "Content-Type: application/json" \
-d '{
"guideListId": "5d1e8a40-…",
"googlePlaceId": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"titleText": "Loaded Joe'"'"'s",
"categories": ["cafe"],
"address": {
"formattedAddress": "141 E Meadow Dr, Vail, CO 81657, USA",
"latitude": 39.6406,
"longitude": -106.3742
}
}'

The response is 201 with the place’s stored fields, like addGuidePlace; read the recommendation and photo it got with getGuideList. The lookups are best effort: if Google, Unsplash or the model fails or finds nothing, the place is still created, without that recommendation or photo.

Each import is paid and counts against the organization’s model-backed budget, shared by every key and token in the organization. Over it, the call returns 429 before anything is looked up. Use addGuidePlace when you already have the text and photo, and send an Idempotency-Key so a retry doesn’t import the place twice. A list deleted while the lookups run returns 404.

PATCH /guide-places/{id} takes { "data": { … } } with any place field. A changed titleText or shortDescriptionText overwrites the English text and re-translates the other languages. An empty shortDescriptionText clears it; an empty titleText is ignored. address, when sent, needs formattedAddress, latitude and longitude as on create; streetAddress, city, state, postalCode and countryCode you leave out keep their stored values. A place can’t be moved to another list: remove it and add it there.

DELETE /guide-places/{id} removes a place permanently, and its photo with it. There is no undo, so read the place first if you may need to add it back. The other places keep their order.

POST /guide-lists/{guideListId}/places/reorder sets the order guests see. Send every place ID of the list in the order you want; index 0 is shown first. It is not a move-one-place call.

Terminal window
curl -X POST https://api-us.suiteop.com/api/v1/guide-lists/5d1e8a40-…/places/reorder \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "orderedIds": ["c40b7e95-…", "71aa02d3-…", "e98f6c14-…"] }'

A duplicated ID, an ID from another list, or a set that leaves out any place returns 400 validation_error and nothing is reordered. Read the list with getGuideList immediately before you reorder, so a place added in the meantime isn’t missing from your set.

POST /guide-lists/reorder sets the order guests see the lists in. It takes { "orderedIds": [...] } with every list ID that listGuideLists returns to you, index 0 first, and answers { "success": true }. A duplicated, missing or unknown ID returns 400 validation_error and nothing moves. Lists hidden from an OAuth token limited to some properties keep their position.

DELETE /guide-lists/{id} deletes the list and every place in it, with their photos. Guests stop seeing it at once and there is no undo. It needs delete_guide, which modify_guides doesn’t include. The response is { "id": "…" }. A list in another organization, or outside the caller’s property access, returns 404. To hide a list instead, remove its scopes with the entity-scope operations; to drop one place, use removeGuidePlace.

updateGuideList and updateGuidePlace accept an optional expectedUpdatedAt. Send the updatedAt from your last read or write exactly as returned, with milliseconds. If the list or place changed since, the update returns 409 (CONFLICT) and writes nothing. Omit it to overwrite regardless.

  • Lists on booking sites with no portal. A list with showOnBookingSites: true and no scopes is served on every booking site of the organization. Only a caller with access to all properties can change such a list or its places, or change its showOnBookingSites; an OAuth token whose user is limited to some properties gets 403 authorization_error. API keys reach every property, so this never applies to them. Once the list has at least one scope, this restriction no longer applies.
  • Lists outside your property scope return 404, the same as a list that doesn’t exist.
  • Inactive is not an error. Places you add to an unscoped list are saved but reach no guest portal until the list is attached. If the list has showOnBookingSites: true, they appear on every booking site immediately.
  • Entity Scopes: attach guide lists to portals, properties, groups and tags
  • Guest Portals: find portalSettingsId values
  • Translations: read and edit the list’s guest-facing heading
  • House Manuals: the other guest-portal content you can manage through the API