# Get v2orgs chat Source: https://docs.obvlo.com/api-reference/ai-chat/get-v2orgs-chat /api-reference/openapi-preview.json get /v2/orgs/{orgId}/chat Returns the listings referenced by a guide card in the AI assistant experience. # Post v2orgs chat Source: https://docs.obvlo.com/api-reference/ai-chat/post-v2orgs-chat /api-reference/openapi-preview.json post /v2/orgs/{orgId}/chat Conversational AI endpoint for destination recommendations, itinerary generation, and itinerary email delivery. # API Changelog Source: https://docs.obvlo.com/api-reference/changelog A record of changes to the Obvlo API schema and endpoints. ## 2026-08-14 — API updates * Added query parameter `hostId` to `GET /v2/orgs/{orgId}/org-languages`. *** ## 2026-08-05 — API updates * Removed response property `tree[].children[].slug` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].pageId` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].guideId` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].pageType` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].segments` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].parentIds` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].locationId` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].homepageSlug` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Removed response property `tree[].children[].destinationId` from `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. *** ## 2026-07-10 — API updates * Added response property `mode` to `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Added response property `tree` to `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Added response property `siteId` to `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. * Added response property `matches` to `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug` 200 response. *** ## 2026-07-03 — Site data API (phase 1) * Added endpoint `GET /v2/orgs/{orgId}/sites/{siteId}/page-slug`. * Added support for slug tree mode (no filters) and filtered match mode (`destinationId`, `guideId`, `locationId`). * Added stable page slug mapping fields: `slug`, `segments`, `parentIds`, `homepageSlug`, and entity IDs. *** ## 2026-04-13 — API updates * Added response property `host.embeddedConfigs[].experiments` to `GET /v2/orgs/{orgId}/host-home` 200 response. * Added response property `embeddedConfigs[].experiments` to `GET /v2/orgs/{orgId}/host-profile` 200 response. *** ## 2026-03-18 — API updates * Updated API schema metadata and documentation details. *** ## 2026-03-17 — Documentation alignment * Added AI Chat preview documentation for `POST /v2/orgs/{orgId}/chat` * Marked AI Chat operations as preview in the published OpenAPI reference * Corrected endpoint pages to use the current `/v2/orgs/{orgId}/...` path format and `key` query parameter examples *** ## 2026-03-16 — API updates * Added endpoint `GET /v2/orgs/{orgId}/chat`. * Added endpoint `POST /v2/orgs/{orgId}/chat`. *** This page is updated automatically whenever the API schema changes in a deployment. Each entry links to the source commit so you can review the full diff. *** ## 2026-02-20 — Initial changelog Changelog tracking introduced. Current endpoints: * `GET /v2/orgs/{orgId}/guide-search` — Search guides with optional listings, geo-filtering, and personalisation * `GET /v2/orgs/{orgId}/listing-search` — Search listings with activity tag filtering and geo-filtering * `GET /v2/orgs/{orgId}/location-search` — Search neighbourhoods and geo areas * `GET /v2/orgs/{orgId}/host-profile` — Retrieve host branding and configuration * `GET /v2/orgs/{orgId}/host-home` — Retrieve host home content * `GET /v2/orgs/{orgId}/org-languages` — Retrieve languages configured for an organisation *** # AI Chat (Preview) Source: https://docs.obvlo.com/api-reference/endpoint/chat POST /v2/orgs/{orgId}/chat Preview conversational endpoint for recommendations, guide card listings, itinerary generation, and itinerary email delivery. This endpoint is in preview. Request and response shapes may change faster than the stable search and host endpoints. ## Overview The AI Chat endpoint powers conversational discovery in Obvlo experiences. It can: * answer chat queries with curated guide and listing cards * return guide-card listings for detail views * generate itineraries * send generated itineraries by email ## Base route ```http theme={null} POST /v2/orgs/{orgId}/chat ``` Authentication uses the standard `key` query parameter. ## Preview notes * Treat this endpoint as beta or preview in production clients. * Conversation-oriented fields such as `actions`, `hostInterests`, and itinerary payloads may evolve. * Use stable search endpoints when you need fixed, non-conversational retrieval contracts. ## POST action modes The `action` field controls how the endpoint behaves. | `action` | Purpose | | ----------------- | ---------------------------------------------------------------------- | | `chat` | Default conversational search and recommendation flow. | | `createItinerary` | Builds a day-by-day itinerary from the active profile and preferences. | | `sendEmail` | Sends a previously generated itinerary to an email address. | ## Required path and query parameters | Parameter | In | Type | Description | | --------- | ----- | ------ | ------------------------------------ | | `orgId` | path | string | Organisation ID for the request. | | `key` | query | string | Public API key for the organisation. | ## Common request fields | Field | Type | Description | | --------------------- | ------- | -------------------------------------------------------------------------------------------------- | | `message` | string | User message for the chat turn. Use `""` when triggering a non-text action. | | `profileId` | string | Optional host profile context for search and itinerary generation. Required for `createItinerary`. | | `lang` | string | Optional BCP-47 language code for response content. | | `latlng` | string | Optional `lat,lng` user location override. | | `conversationHistory` | array | Prior user and assistant messages. | | `preferences` | object | Optional guided-discovery preferences: `tripType`, `userCategory`, `interests`. | | `sessionId` | string | Optional client session identifier for tracking. | | `fingerprintId` | string | Optional end-user fingerprint identifier for tracking. | | `isInitialSearch` | boolean | Optional hint to randomise initial recommendation ranking. | ## Example requests ### Chat for recommendations ```bash theme={null} curl --request POST \ --url 'https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/chat?key=YOUR_API_KEY' \ --header 'content-type: application/json' \ --data '{ "message": "Find great cocktail bars nearby", "profileId": "YOUR_HOST_ID", "lang": "en-GB", "action": "chat" }' ``` ### Create an itinerary ```bash theme={null} curl --request POST \ --url 'https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/chat?key=YOUR_API_KEY' \ --header 'content-type: application/json' \ --data '{ "message": "", "action": "createItinerary", "profileId": "YOUR_HOST_ID", "durationDays": 3, "preferences": { "tripType": "leisure", "userCategory": "couple", "interests": ["food", "culture"] } }' ``` ### Send itinerary by email ```bash theme={null} curl --request POST \ --url 'https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/chat?key=YOUR_API_KEY' \ --header 'content-type: application/json' \ --data '{ "message": "", "action": "sendEmail", "email": "guest@example.com", "itinerary": { "name": "Weekend in London", "durationDays": 2, "days": [] } }' ``` ## Example chat response ```json theme={null} { "message": "Here are a few cocktail bars worth trying tonight.", "listings": [ { "id": "listing-1", "name": "Swift Soho", "category": "bar", "address": "12 Old Compton Street, London", "geoDistance": 420, "googleRating": 4.7, "priceLevel": 2, "openNow": true, "photo": { "url": "https://cdn.example.com/swift.jpg" }, "googleMapsUrl": "https://maps.google.com/?q=swift+soho" } ], "guides": [ { "id": "guide-1", "name": "Best Cocktail Bars", "description": "A curated shortlist of standout bars.", "coverImage": { "url": "https://cdn.example.com/guide.jpg" }, "listingCount": 8, "geoDistance": 380 } ], "toolUsed": true } ``` ## Related GET route for guide cards Guide cards in the chat UI can lazy-load their associated listings through the companion route below: ```http theme={null} GET /v2/orgs/{orgId}/chat?guideId={guideId}&lang={lang} ``` ### Guide listings example ```bash theme={null} curl --request GET \ --url 'https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/chat?key=YOUR_API_KEY&guideId=YOUR_GUIDE_ID&lang=en-GB' \ --header 'accept: application/json' ``` ### Guide listings response ```json theme={null} { "listings": [ { "id": "listing-1", "name": "Bar Termini", "category": "bar", "address": "7 Old Compton Street, London", "geoDistance": 0, "googleRating": 4.6, "priceLevel": 2, "openNow": null, "photo": { "url": "https://cdn.example.com/bar-termini.jpg" }, "googleMapsUrl": "https://maps.google.com/?q=bar+termini" } ] } ``` # Guide Search Source: https://docs.obvlo.com/api-reference/endpoint/guide-search GET /v2/orgs/{orgId}/guide-search Search curated travel guides by location, keywords, trip type, and personalisation parameters. ## Overview The `/v2/guide-search` endpoint returns a paginated list of curated travel guides from the Obvlo content engine. You can filter by geography, free-text query, and guest personalisation attributes such as trip type and interests. By default, each guide result includes its embedded listings (points of interest). You can disable this with `includeListings=false` to get a lighter response. ### Localised fields and the `lang` parameter Guide fields (`name`, `description`, `introText`, `keywords`, `attribution`, `centerLocation`, `preferences`) and all localised fields of embedded listings carry translated content. The shape of these fields depends on whether `lang` is provided: | `lang` | Field shape | Example | | ---------------------------- | --------------------------------------------------- | ------------------------------------------------------ | | Provided (e.g. `lang=en-GB`) | Plain value for the requested language. | `"name": "Top Restaurants in Shoreditch"` | | Omitted | Language map — one key per available language code. | `"name": { "en-GB": "Top Restaurants in Shoreditch" }` | When `lang` is omitted, at least the default language (`en-GB`) is always included in the map. ## Request ```http theme={null} GET /v2/orgs/{orgId}/guide-search ``` ### Required parameters | Parameter | Type | Description | | --------- | ------ | --------------------- | | `orgId` | string | Your organisation ID. | ### Optional parameters | Parameter | Type | Default | Description | | --------------------- | ------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `profileId` | string | — | Scope results to a specific host profile. | | `searchQuery` | string | — | Full-text query matched against guide names and descriptions. | | `latlng` | string | — | Geo-center for proximity ranking. Format: `latitude,longitude` (e.g. `51.5074,-0.1278`). | | `radius` | number | Profile default | Search radius in metres around `latlng`. | | `page` | integer | `1` | Page number (1-based). | | `hitsPerPage` | integer | `10` | Results per page. Maximum `20`. | | `userCategory` | string | — | Filter by user category (e.g. `couple`, `family`, `solo`). | | `tripType` | string | — | Filter by trip type (e.g. `leisure`, `business`). | | `userInterests` | string | — | Comma-separated interest tags used for personalised ranking. | | `includeListings` | boolean | `true` | Set to `false` to omit embedded listings from each guide. | | `maxListingsPerGuide` | integer | `20` | Maximum listings to embed per guide when `includeListings=true`. | | `rankBy` | string | `score` | Ranking strategy: `score` (relevance) or `distance` (nearest first, requires `latlng`). | | `lang` | string | — | BCP 47 language code (e.g. `en-GB`). When provided, localised fields in guides and their embedded listings are returned as plain values for that language instead of language maps. | ## Examples ### Basic search for an organisation ```bash Basic search theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/guide-search?key=YOUR_API_KEY" ``` ### Geo-filtered search near London ```bash Geo-filtered, near London theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/guide-search?key=YOUR_API_KEY&latlng=51.5074,-0.1278&radius=5000&hitsPerPage=5" ``` ### Personalised search for a couple on a leisure trip ```bash Personalised search theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/guide-search?key=YOUR_API_KEY&userCategory=couple&tripType=leisure&userInterests=food,culture" ``` ### Search with a specific language ```bash With lang parameter theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/guide-search?key=YOUR_API_KEY&lang=en-GB" ``` ### Search without embedded listings ```bash Without embedded listings theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/guide-search?key=YOUR_API_KEY&includeListings=false" ``` ## Response ```json theme={null} { "results": [ { "id": "guide_abc123", "contentType": "guide", "name": "Top Restaurants in Shoreditch", "description": "A curated selection of the best places to eat...", "introText": "Shoreditch is known for its vibrant food scene...", "keywords": ["restaurants", "food", "shoreditch"], "geoLocation": [51.5247, -0.0755], "featured": false, "hostIds": ["host_xyz"], "listingIds": ["listing_1", "listing_2"], "languageCodes": ["en-GB", "fr-FR"], "updatedAt": 1739800000000, "geoDistance": 1234.5, "listings": [ { "id": "listing_1", "contentType": "listing", "name": "Padella", "summary": "Acclaimed pasta restaurant in Borough Market.", "address": "6 Southwark St, London SE1 1TQ", "category": "restaurant", "geoLocation": [51.5051, -0.0906], "geoDistance": 950.0, "featured": false, "guideIds": ["guide_abc123"], "hostIds": ["host_xyz"], "score": 0.89, "searchId": "listing_1", "status": "active", "photos": [] } ] } ], "total": 42, "pages": 5 } ``` ```json theme={null} { "results": [ { "id": "guide_abc123", "contentType": "guide", "name": { "en-GB": "Top Restaurants in Shoreditch", "fr-FR": "Les meilleurs restaurants de Shoreditch" }, "description": { "en-GB": "A curated selection of the best places to eat...", "fr-FR": "Une s\u00e9lection des meilleurs endroits..." }, "introText": { "en-GB": "Shoreditch is known for its vibrant food scene...", "fr-FR": "Shoreditch est connue pour..." }, "keywords": { "en-GB": ["restaurants", "food", "shoreditch"], "fr-FR": ["restaurants", "nourriture"] }, "geoLocation": [51.5247, -0.0755], "featured": false, "hostIds": ["host_xyz"], "listingIds": ["listing_1", "listing_2"], "languageCodes": ["en-GB", "fr-FR"], "updatedAt": 1739800000000, "geoDistance": 1234.5, "listings": [ { "id": "listing_1", "contentType": "listing", "name": { "en-GB": "Padella", "fr-FR": "Padella" }, "summary": { "en-GB": "Acclaimed pasta restaurant in Borough Market.", "fr-FR": "Restaurant de p\u00e2tes reconnu au Borough Market." }, "address": { "en-GB": "6 Southwark St, London SE1 1TQ", "fr-FR": "6 Southwark St, London SE1 1TQ" }, "category": { "en-GB": "restaurant", "fr-FR": "restaurant" }, "geoLocation": [51.5051, -0.0906], "geoDistance": 950.0, "featured": false, "guideIds": ["guide_abc123"], "hostIds": ["host_xyz"], "score": 0.89, "searchId": "listing_1", "status": "active", "photos": [] } ] } ], "total": 42, "pages": 5 } ``` ### Response fields | Field | Type | Description | | --------- | ------- | ----------------------------------------------------- | | `results` | array | Array of guide objects (see below). | | `total` | integer | Total number of guides matching the query. | | `pages` | integer | Total number of pages at the requested `hitsPerPage`. | #### Guide object | Field | Type | Description | | --------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Unique guide identifier. | | `contentType` | string | Always `"guide"`. | | `name` | string \| object | Guide name. Plain string when `lang` is provided; language map otherwise. | | `description` | string \| object | Guide description. Plain string when `lang` is provided; language map otherwise. | | `introText` | string \| object | Intro text. Plain string when `lang` is provided; language map otherwise. | | `keywords` | array \| object | Keywords. Array when `lang` is provided; language map of arrays otherwise. | | `geoLocation` | array | Coordinates as `[latitude, longitude]`. | | `coverImage` | object | Cover image with multiple size variants. See [Image object](#image-object). | | `featured` | boolean | Whether the guide is featured. | | `hostIds` | array | IDs of host profiles associated with this guide. | | `listingIds` | array | IDs of listings included in this guide. | | `languageCodes` | array | Language codes for which content is available. | | `updatedAt` | integer | Last updated timestamp (Unix ms). | | `geoDistance` | number | Distance in metres from the requested `latlng` (when provided). | | `listings` | array | Embedded listing objects (when `includeListings=true`). Localised fields follow the same `lang` behaviour as top-level listing search results. | #### Image object | Field | Type | Description | | -------------- | ------ | ----------------------------------------- | | `id` | string | Unique image identifier. | | `category` | string | Image category tag. | | `attributions` | array | Attribution strings for the image. | | `images` | array | Array of `{ size, url }` variant objects. | # Get Host Home Source: https://docs.obvlo.com/api-reference/endpoint/host-home GET /v2/orgs/{orgId}/host-home Single-call bootstrap for a host home screen — returns the host profile, guides with nested listings, and nearby geo areas. ## Overview The `/v2/host-home` endpoint is designed to power the initial load of a host-scoped experience. A single request returns everything needed to render the home screen: * The host **profile** (branding, search config, embedded UI config, location anchor) * The first page of **guides**, each with nested **listings** * A page of **geo areas** (neighbourhoods) scoped to the host Geo defaults and search radius are resolved from the host profile automatically. Pass `latlng` to override the geo centre (for example, to centre results on the guest's current location), or `radius` to override the search radius. Preference filters (`userCategory`, `tripType`, `userInterests`) are applied to both guide and listing ranking. ### Localised fields and the `lang` parameter Guide fields (`name`, `description`, `introText`, `keywords`, `attribution`, `centerLocation`, `preferences`) and all localised fields of embedded listings carry translated content. Geo area (location) fields (`displayName`, `headline`, `description`, `address`, `area`, `keywords`) also carry translated content. The shape of these fields depends on whether `lang` is provided: | `lang` | Field shape | Example | | ---------------------------- | --------------------------------------------------- | ------------------------------------------------------ | | Provided (e.g. `lang=en-GB`) | Plain value for the requested language. | `"name": "Top Restaurants in Shoreditch"` | | Omitted | Language map — one key per available language code. | `"name": { "en-GB": "Top Restaurants in Shoreditch" }` | When `lang` is omitted, at least the default language (`en-GB`) is always included in the map. ## Request ```http theme={null} GET /v2/orgs/{orgId}/host-home ``` ### Required parameters | Parameter | Type | Description | | ----------- | ------ | ----------------------- | | `orgId` | string | Your organisation ID. | | `profileId` | string | ID of the host profile. | ### Optional parameters | Parameter | Type | Default | Description | | --------------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lang` | string | — | BCP 47 language code (e.g. `en-GB`, `fr-FR`). When provided, localised fields in guides, their embedded listings, and geo areas are returned as plain values for that language instead of language maps. | | `latlng` | string | — | Geo centre override as `lat,lng` (e.g. `51.508,-0.125`). Defaults to the host's stored location. | | `radius` | integer | — | Search radius in metres from the geo centre. Defaults to the host's configured search radius. | | `guideType` | string | — | Filter guides by type. `featured` returns only featured guides; omit to return all guides. | | `userCategory` | string | — | Filter by user category (e.g. `couple`, `family`, `solo`). | | `tripType` | string | — | Filter by trip type (e.g. `leisure`, `business`). | | `userInterests` | string | — | Comma-separated interest tags for personalised ranking. | | `rankBy` | string | `score` | Ranking strategy for guides and listings: `score`, `distance`, or `relevance`. | | `guidesPage` | integer | `1` | Page number for guide results (1-based). | | `guidesPerPage` | integer | `10` | Number of guides per page. Maximum `20`. | | `maxListingsPerGuide` | integer | `20` | Maximum number of listings embedded per guide. Maximum `20`. | | `geoareasPage` | integer | `1` | Page number for geo area results (1-based). | | `geoareasPerPage` | integer | `10` | Number of geo areas per page. Maximum `20`. | ## Examples ### Basic home screen load ```bash Basic home screen load theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/host-home?key=YOUR_API_KEY&profileId=YOUR_HOST_ID" ``` ### Geo-anchored load centred on the guest's location ```bash Geo-anchored load theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/host-home?key=YOUR_API_KEY&profileId=YOUR_HOST_ID&latlng=51.508,-0.125&radius=3000" ``` ### Featured guides only, single language ```bash Featured guides only theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/host-home?key=YOUR_API_KEY&profileId=YOUR_HOST_ID&guideType=featured&lang=en-GB" ``` ### Personalised load for a couple on a leisure trip ```bash Personalised load theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/host-home?key=YOUR_API_KEY&profileId=YOUR_HOST_ID&userCategory=couple&tripType=leisure&userInterests=food,culture" ``` ### Example response ```json Example response expandable theme={null} { "host": { "id": "hotel-grand", "name": "The Grand Hotel", "orgId": "YOUR_ORG_ID", "geoLocation": [51.5074, -0.1278], "primaryLocation": { "displayName": "The Grand Hotel", "address": "1 Grand Place, London", "locationId": "loc-001", "measurement": "metric" }, "searchConfig": { "searchRadius": 4000 }, "embeddedConfigs": [ { "id": "embed-config-001", "name": "Default Widget", "createdAt": 1700000000000, "selectedLayout": "Default" } ], "currencyCode": "GBP", "currencySymbol": "£", "timeZoneName": "Europe/London" }, "guides": { "results": [ { "id": "guide-001", "name": { "en-GB": "Top Restaurants" }, "description": { "en-GB": "The best places to eat in London." }, "geoLocation": [51.508, -0.125], "geoDistance": 850, "listings": [ { "id": "listing-001", "contentType": "listing", "name": { "en-GB": "Sketch" }, "summary": { "en-GB": "An iconic multi-room restaurant and arts venue." }, "address": { "en-GB": "9 Conduit St, London W1S 2XG" }, "geoLocation": [51.511, -0.142], "geoDistance": 1200, "featured": false, "guideIds": ["guide-001"], "hostIds": ["hotel-grand"], "score": 0.91, "searchId": "listing-001", "status": "active", "photos": [] } ] } ], "total": 12, "pages": 2 }, "geoareas": { "results": [ { "id": "area-001", "displayName": { "en-GB": "Soho" }, "description": { "en-GB": "A vibrant neighbourhood known for its nightlife and culture." }, "geoLocation": [51.513, -0.131], "geoDistance": 1400 } ], "total": 5, "pages": 1 } } ``` ## Response fields ### Top-level | Field | Type | Description | | ---------- | ------ | --------------------------------------------------------------------------------------------------------------------------------- | | `host` | object | Host profile, including `embeddedConfigs`. See [Get Host Profile](/api-reference/endpoint/host-profile) for full field reference. | | `guides` | object | Paginated guide results with nested listings. | | `geoareas` | object | Paginated geo area (neighbourhood) results scoped to the host. | ### `guides` | Field | Type | Description | | --------- | --------- | --------------------------------------------------------------------------------------- | | `results` | object\[] | Array of guide objects, each containing a `listings` array of embedded listing objects. | | `total` | integer | Total number of matching guides. | | `pages` | integer | Total number of pages. | Each guide and its embedded listings follow the same shapes as the [Guide Search](/api-reference/endpoint/guide-search) endpoint. ### `geoareas` | Field | Type | Description | | --------- | --------- | ------------------------------------------- | | `results` | object\[] | Array of geo area objects (neighbourhoods). | | `total` | integer | Total number of geo areas for this host. | | `pages` | integer | Total number of pages. | Each geo area follows the same shape as the [Location Search](/api-reference/endpoint/location-search) endpoint. When `lang` is omitted, all localised fields are returned as language maps with at least the default language (`en-GB`) present. If a translation is missing for the requested `lang`, the response falls back to the host's default language. # Get Host Profile Source: https://docs.obvlo.com/api-reference/endpoint/host-profile GET /v2/orgs/{orgId}/host-profile Retrieve the public-safe profile for a host property, including branding, search configuration, and location data. ## Overview The `/v2/host-profile` endpoint returns the configuration profile for a given host. It is the authoritative source for branding, UI configuration, search defaults, and geo anchor data. Sensitive fields such as API keys, analytics tokens, and integration credentials are never included in the response. This endpoint does not localise response fields and does not support the `lang` query parameter. ## Request ```http theme={null} GET /v2/orgs/{orgId}/host-profile ``` ### Required parameters | Parameter | Type | Description | | ----------- | ------ | ----------------------------------- | | `orgId` | string | Your organisation ID. | | `profileId` | string | ID of the host profile to retrieve. | ## Examples ### Retrieve a host profile ```bash Retrieve a host profile theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/host-profile?key=YOUR_API_KEY&profileId=YOUR_HOST_ID" ``` ### Example response ```json Example response expandable theme={null} { "id": "hotel-grand", "name": "The Grand Hotel", "orgId": "YOUR_ORG_ID", "geoLocation": [51.5074, -0.1278], "primaryLocation": { "displayName": "The Grand Hotel", "address": "1 Grand Place, London", "locationId": "loc-001", "measurement": "metric", "geoLocation": { "lat": 51.5074, "lng": -0.1278 } }, "searchConfig": { "searchRadius": 4000, "guideRanking": "relevance", "listingRanking": "score" }, "embeddedConfigs": [ { "id": "embed-config-001", "name": "Default Widget", "createdAt": 1700000000000, "selectedLayout": "Default" } ], "languageCodes": ["en-GB", "fr-FR"], "defaultLanguageCode": "en-GB", "currencyCode": "GBP", "currencySymbol": "£", "timeZoneName": "Europe/London", "weather": { "icon": "02d", "temperature": 14 } } ``` ## Response fields | Field | Type | Description | | --------------------- | ----------------- | ----------------------------------------------------------------------------------------------------- | | `id` | string | Host profile ID. | | `name` | string | Display name of the host. | | `orgId` | string | Organisation ID the host belongs to. | | `geoLocation` | \[number, number] | Coordinates as `[latitude, longitude]`. | | `primaryLocation` | object | Location details for the host's primary address. | | `searchConfig` | object | Default search radius and ranking preferences. | | `languageCodes` | string\[] | Language codes supported by this host's content. | | `defaultLanguageCode` | string | Default language for content. | | `currencyCode` | string | ISO 4217 currency code (e.g. `GBP`). | | `currencySymbol` | string | Display currency symbol (e.g. `£`). | | `timeZoneName` | string | IANA time zone name (e.g. `Europe/London`). | | `uiConfig` | object | UI configuration for the host interface. | | `branding` | object | Branding colours and typography. | | `logos` | object\[] | Logo assets for the host. | | `pages` | object\[] | Custom page configurations. | | `weather` | object | Current weather snapshot (`icon`, `temperature` in Celsius). | | `perks` | object | Perks/amenities header and sections. | | `socialMedia` | object\[] | Social media links. | | `travellerArchetypes` | object\[] | Configured traveller archetypes for personalisation. | | `embeddedConfigs` | object\[] | Array of embedded UI module configuration objects. Shape determined by the host's configured modules. | | `activeIntegrations` | string\[] | List of active third-party integration identifiers. | # Listing Search Source: https://docs.obvlo.com/api-reference/endpoint/listing-search GET /v2/orgs/{orgId}/listing-search Search individual listings — restaurants, attractions, and experiences — with geo, category, and personalisation filters. ## Overview The `/v2/listing-search` endpoint returns a paginated list of individual listings from the Obvlo content engine. Listings represent single points of interest such as restaurants, hotels, attractions, or experiences. You can filter by geography, free-text query, activity tags, and guest personalisation attributes. ### Localised fields and the `lang` parameter Several listing fields (`name`, `summary`, `tagline`, `address`, `category`, `activityTags`, `keywords`, `priceLevelText`, `planVisit`, `whatToExpect`, `additionalTextAttributes`, `preferences`, `integratedCategories`) carry translated content. The shape of these fields depends on whether `lang` is provided: | `lang` | Field shape | Example | | ---------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | Provided (e.g. `lang=en-GB`) | Plain value — a single string (or array/object) for the requested language. | `"summary": "A great place to eat."` | | Omitted | Language map — one key per available language code. | `"summary": { "en-GB": "A great place to eat.", "fr-FR": "Un excellent endroit." }` | When `lang` is omitted, at least the default language (`en-GB`) is always included in the map. ## Request ```http theme={null} GET /v2/orgs/{orgId}/listing-search ``` ### Required parameters | Parameter | Type | Description | | --------- | ------ | --------------------- | | `orgId` | string | Your organisation ID. | ### Optional parameters | Parameter | Type | Default | Description | | --------------- | ------ | ------- | ---------------------------------------------------------------------------------------- | | `profileId` | string | — | Scope results to a specific host profile. | | `searchQuery` | string | — | Full-text query matched against listing names and descriptions. | | `latlng` | string | — | Geo-center for proximity ranking. Format: `latitude,longitude` (e.g. `51.5074,-0.1278`). | | `radius` | string | — | Search radius in metres around `latlng`. | | `page` | string | `"1"` | Page number (1-based). | | `hitsPerPage` | string | `"10"` | Results per page. Maximum `20`. | | `userCategory` | string | — | Filter by user category (e.g. `couple`, `family`, `solo`). | | `tripType` | string | — | Filter by trip type (e.g. `leisure`, `business`). | | `userInterests` | string | — | Comma-separated interest tags used for personalised ranking. | | `activityTags` | string | — | Comma-separated activity tags to filter results (e.g. `outdoor,family-friendly`). | | `lang` | string | — | BCP 47 language code for localised response fields (e.g. `en`, `fr`, `de`). | | `rankBy` | string | `score` | Ranking strategy: `score` (relevance) or `distance` (nearest first, requires `latlng`). | ## Examples ### Basic search for an organisation ```bash Basic search theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/listing-search?key=YOUR_API_KEY" ``` ### Geo-filtered search near Paris ```bash Geo-filtered, near Paris theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/listing-search?key=YOUR_API_KEY&latlng=48.8566,2.3522&radius=2000&hitsPerPage=10" ``` ### Filter by activity tags ```bash Filter by activity tags theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/listing-search?key=YOUR_API_KEY&activityTags=outdoor,family-friendly" ``` ### Personalised search with a specific language ```bash Personalised search theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/listing-search?key=YOUR_API_KEY&userCategory=couple&tripType=leisure&lang=en-GB" ``` ### Ranked by distance ```bash Ranked by distance theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/listing-search?key=YOUR_API_KEY&latlng=51.5074,-0.1278&rankBy=distance" ``` ## Response ```json theme={null} { "results": [ { "id": "listing_abc123", "contentType": "listing", "name": "Padella", "summary": "Acclaimed pasta restaurant in Borough Market.", "tagline": "Hand-rolled pasta made fresh daily.", "address": "6 Southwark St, London SE1 1TQ", "neighborhood": "Borough Market", "category": "restaurant", "activityTags": ["food", "casual-dining"], "geoLocation": [51.5051, -0.0906], "geoDistance": 850.2, "googleRating": 4.6, "googleMapsUrl": "https://maps.google.com/?q=Padella", "priceLevel": 2, "priceLevelText": "££", "openingHours": { "openNow": true, "periods": [ { "open": { "day": 1, "time": "1200" }, "close": { "day": 1, "time": "2145" } } ] }, "photos": [ { "id": "photo_xyz", "category": "food", "attributions": ["© Padella"], "keywords": [], "images": [ { "size": "small", "url": "https://cdn.obvlo.com/photos/small/photo_xyz.jpg" }, { "size": "large", "url": "https://cdn.obvlo.com/photos/large/photo_xyz.jpg" } ] } ], "languageCodes": ["en-GB", "fr-FR"], "featured": false, "guideIds": ["guide_abc123"], "hostIds": ["host_xyz"], "score": 0.92, "searchId": "listing_abc123", "status": "active" } ], "total": 128, "pages": 13 } ``` ```json theme={null} { "results": [ { "id": "listing_abc123", "contentType": "listing", "name": { "en-GB": "Padella", "fr-FR": "Padella" }, "summary": { "en-GB": "Acclaimed pasta restaurant in Borough Market.", "fr-FR": "Restaurant de p\u00e2tes reconnu au Borough Market." }, "tagline": { "en-GB": "Hand-rolled pasta made fresh daily.", "fr-FR": "P\u00e2tes faites \u00e0 la main chaque jour." }, "address": { "en-GB": "6 Southwark St, London SE1 1TQ", "fr-FR": "6 Southwark St, London SE1 1TQ" }, "neighborhood": "Borough Market", "category": { "en-GB": "restaurant", "fr-FR": "restaurant" }, "activityTags": { "en-GB": ["food", "casual-dining"], "fr-FR": ["food", "casual-dining"] }, "geoLocation": [51.5051, -0.0906], "geoDistance": 850.2, "googleRating": 4.6, "priceLevel": 2, "priceLevelText": { "en-GB": "\u00a3\u00a3", "fr-FR": "\u00a3\u00a3" }, "photos": [], "languageCodes": ["en-GB", "fr-FR"], "featured": false, "guideIds": ["guide_abc123"], "hostIds": ["host_xyz"], "score": 0.92, "searchId": "listing_abc123", "status": "active" } ], "total": 128, "pages": 13 } ``` ### Response fields | Field | Type | Description | | --------- | ------- | ----------------------------------------------------- | | `results` | array | Array of listing objects (see below). | | `total` | integer | Total number of listings matching the query. | | `pages` | integer | Total number of pages at the requested `hitsPerPage`. | #### Listing object | Field | Type | Description | | ---------------- | ---------------- | -------------------------------------------------------------------------------------- | | `id` | string | Unique listing identifier. | | `contentType` | string | Always `"listing"`. | | `name` | string \| object | Listing name. Plain string when `lang` is provided; language map otherwise. | | `summary` | string \| object | Short description. Plain string when `lang` is provided; language map otherwise. | | `tagline` | string \| object | One-line tagline. Plain string when `lang` is provided; language map otherwise. | | `address` | string \| object | Street address. Plain string when `lang` is provided; language map otherwise. | | `neighborhood` | string | Neighbourhood or area name (not localised). | | `category` | string \| object | Primary category. Plain string when `lang` is provided; language map otherwise. | | `activityTags` | array \| object | Activity tags. Array when `lang` is provided; language map of arrays otherwise. | | `geoLocation` | array | Coordinates as `[latitude, longitude]`. | | `geoDistance` | number | Distance in metres from the requested `latlng` (when provided). | | `googleRating` | number | Google Places rating (0–5). | | `googleMapsUrl` | string | Link to Google Maps. | | `phoneNumber` | string | Contact phone number. | | `websiteUrl` | string | Official website URL. | | `priceLevel` | integer | Numeric price level (1–4). | | `priceLevelText` | string \| object | Price label (e.g. `££`). Plain string when `lang` is provided; language map otherwise. | | `openingHours` | object | Opening hours data. See [Opening hours object](#opening-hours-object). | | `photos` | array | Array of photo objects. See [Image object](#image-object). | | `languageCodes` | array | Language codes for which content is available. | | `score` | number | Relevance score (0–1) used for ranking. | | `status` | string | Listing status (e.g. `active`). | #### Opening hours object | Field | Type | Description | | --------------- | ------- | ---------------------------------------------- | | `openNow` | boolean | Whether the listing is currently open. | | `nextCloseTime` | string | ISO 8601 datetime of next closing time. | | `isUnavailable` | boolean | `true` if opening hours data is not available. | | `periods` | array | Array of `{ open, close }` period objects. | Each period has `open` and `close` objects with: | Field | Type | Description | | ------ | ------- | ---------------------------------------------- | | `day` | integer | Day of week (0 = Sunday, 6 = Saturday). | | `time` | string | 24h time string without colon (e.g. `"0900"`). | #### Image object | Field | Type | Description | | -------------- | ------ | ----------------------------------------- | | `id` | string | Unique image identifier. | | `category` | string | Image category tag. | | `attributions` | array | Attribution strings for the image. | | `images` | array | Array of `{ size, url }` variant objects. | # Location Search Source: https://docs.obvlo.com/api-reference/endpoint/location-search GET /v2/orgs/{orgId}/location-search Search geographic locations such as neighbourhoods, districts, and areas of interest by proximity and keyword. ## Overview The `/v2/location-search` endpoint returns a paginated list of geographic locations from the Obvlo content engine. Locations represent areas of interest such as neighbourhoods, districts, or city zones. You can filter by proximity and search by keyword. ### Localised fields and the `lang` parameter The fields `displayName`, `headline`, `description`, `address`, `area`, and `keywords` carry translated content. The shape of these fields depends on whether `lang` is provided: | `lang` | Field shape | Example | | ---------------------------- | --------------------------------------------------- | ------------------------------------------ | | Provided (e.g. `lang=en-GB`) | Plain value for the requested language. | `"displayName": "Shoreditch"` | | Omitted | Language map — one key per available language code. | `"displayName": { "en-GB": "Shoreditch" }` | When `lang` is omitted, at least the default language (`en-GB`) is always included in the map. ## Request ```http theme={null} GET /v2/orgs/{orgId}/location-search ``` ### Required parameters | Parameter | Type | Description | | --------- | ------ | --------------------- | | `orgId` | string | Your organisation ID. | ### Optional parameters | Parameter | Type | Default | Description | | ------------- | ------- | --------------- | --------------------------------------------------------------------------------------------------------------------------- | | `profileId` | string | — | Scope results to a specific host profile. | | `searchQuery` | string | — | Full-text query matched against location names and descriptions. | | `latlng` | string | — | Geo-center for proximity filtering. Format: `latitude,longitude` (e.g. `51.5074,-0.1278`). | | `radius` | number | Profile default | Search radius in metres around `latlng`. | | `page` | integer | `1` | Page number (1-based). | | `hitsPerPage` | integer | `10` | Results per page. Maximum `20`. | | `rankBy` | string | `distance` | Ranking strategy: `distance` (nearest first, requires `latlng`) or `score` (relevance). | | `lang` | string | — | BCP 47 language code (e.g. `en-GB`). When provided, localised fields are returned as plain values instead of language maps. | ## Examples ### Basic search for an organisation ```bash Basic search theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/location-search?key=YOUR_API_KEY" ``` ### Geo-filtered search near London ```bash Geo-filtered, near London theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/location-search?key=YOUR_API_KEY&latlng=51.5074,-0.1278&radius=10000&hitsPerPage=5" ``` ### Text search anchored to a host profile ```bash Text search theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/location-search?key=YOUR_API_KEY&profileId=YOUR_PROFILE_ID&searchQuery=shoreditch" ``` ### Sort by relevance instead of distance ```bash Sort by relevance theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/location-search?key=YOUR_API_KEY&profileId=YOUR_PROFILE_ID&rankBy=score" ``` ### Search with a specific language ```bash With lang parameter theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/location-search?key=YOUR_API_KEY&lang=en-GB" ``` ## Response ```json theme={null} { "results": [ { "id": "location_abc123", "hostIds": ["host_xyz"], "status": "published", "displayName": "Shoreditch", "headline": "London's creative quarter", "description": "A vibrant neighbourhood in east London known for its street art and independent restaurants.", "address": "Shoreditch, London, UK", "area": "East London", "keywords": ["street art", "bars", "restaurants", "nightlife"], "geoLocation": [51.5246, -0.0771], "geoDistance": 850.3, "radius": 1200, "languageCodes": ["en-GB", "fr-FR"], "photos": [ { "id": "photo_1", "category": "exterior", "attributions": [], "keywords": [], "images": [ { "size": "lg", "url": "https://cdn.obvlo.com/photos/photo_1_lg.jpg" } ] } ] } ], "total": 12, "pages": 2 } ``` ```json theme={null} { "results": [ { "id": "location_abc123", "hostIds": ["host_xyz"], "status": "published", "displayName": { "en-GB": "Shoreditch", "fr-FR": "Shoreditch" }, "headline": { "en-GB": "London's creative quarter", "fr-FR": "Le quartier cr\u00e9atif de Londres" }, "description": { "en-GB": "A vibrant neighbourhood in east London...", "fr-FR": "Un quartier dynamique dans l'est de Londres..." }, "address": { "en-GB": "Shoreditch, London, UK", "fr-FR": "Shoreditch, Londres, Royaume-Uni" }, "area": { "en-GB": "East London", "fr-FR": "Est de Londres" }, "keywords": { "en-GB": ["street art", "bars", "restaurants", "nightlife"], "fr-FR": ["art de rue", "bars", "restaurants"] }, "geoLocation": [51.5246, -0.0771], "geoDistance": 850.3, "radius": 1200, "languageCodes": ["en-GB", "fr-FR"], "photos": [] } ], "total": 12, "pages": 2 } ``` ### Response fields | Field | Type | Description | | --------- | ------- | ----------------------------------- | | `results` | array | Array of matching location objects. | | `total` | integer | Total number of matching locations. | | `pages` | integer | Total number of pages. | ### Location object fields | Field | Type | Description | | --------------- | ----------------- | -------------------------------------------------------------------------------------------- | | `id` | string | Unique location identifier. | | `hostIds` | string\[] | Host profiles this location belongs to. | | `status` | string | Publication status (always `published` in API responses). | | `displayName` | string \| object | Location name. Plain string when `lang` is provided; language map otherwise. | | `headline` | string \| object | Short headline. Plain string when `lang` is provided; language map otherwise. | | `description` | string \| object | Description. Plain string when `lang` is provided; language map otherwise. | | `address` | string \| object | Address. Plain string when `lang` is provided; language map otherwise. | | `area` | string \| object | Area or district name. Plain string when `lang` is provided; language map otherwise. | | `keywords` | array \| object | Keywords. Array when `lang` is provided; language map of arrays otherwise. | | `geoLocation` | \[number, number] | Coordinates as `[latitude, longitude]`. | | `geoDistance` | number | Distance in metres from the `latlng` centre (present when `latlng` is provided). | | `radius` | number | Radius of the location area in metres. | | `languageCodes` | string\[] | Language codes available for this location. | | `photos` | array | Array of photo objects. Each has `id`, `category`, `attributions`, `keywords`, and `images`. | # Get Organisation Languages Source: https://docs.obvlo.com/api-reference/endpoint/org-languages GET /v2/orgs/{orgId}/org-languages Retrieve the list of languages supported by an organisation, including full locale metadata for each language code. ## Overview The `/v2/org-languages` endpoint returns the full language configuration for an organisation. Use it to discover which languages are available before making localised content requests (e.g. passing `lang` to guide-search or listing-search). When `translationEnabled` is `false`, only the default language is available and the `languages` array will be empty. ## Request ```http theme={null} GET /v2/orgs/{orgId}/org-languages ``` ### Required parameters | Parameter | Type | Description | | --------- | ------ | --------------------- | | `orgId` | string | Your organisation ID. | ## Examples ### Retrieve supported languages ```bash Retrieve supported languages theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/org-languages?key=YOUR_API_KEY" ``` ### Example response — translations enabled ```json Example response (translations enabled) expandable theme={null} { "translationEnabled": true, "defaultLanguage": { "languageCode": "en-GB", "languageName": "English", "dialect": "British", "nativeName": "English", "isDefault": true, "isEnabled": true, "flagIconUrl": "https://cdn.obvlo.com/flags/gb.svg", "rtl": false, "currencyCode": "GBP", "currencySymbol": "£", "hour12": true, "measurement": "metric", "temperatureMetric": "celsius", "isLatinAlphabet": true }, "languages": [ { "languageCode": "en-GB", "languageName": "English", "dialect": "British", "nativeName": "English", "isDefault": true, "isEnabled": true, "flagIconUrl": "https://cdn.obvlo.com/flags/gb.svg", "rtl": false, "currencyCode": "GBP", "currencySymbol": "£", "hour12": true, "measurement": "metric", "temperatureMetric": "celsius", "isLatinAlphabet": true }, { "languageCode": "fr-FR", "languageName": "French", "dialect": "Standard", "nativeName": "Français", "isDefault": false, "isEnabled": true, "flagIconUrl": "https://cdn.obvlo.com/flags/fr.svg", "rtl": false, "currencyCode": "EUR", "currencySymbol": "€", "hour12": false, "measurement": "metric", "temperatureMetric": "celsius", "isLatinAlphabet": true } ] } ``` ### Example response — translations disabled ```json Example response (translations disabled) theme={null} { "translationEnabled": false, "defaultLanguage": { "languageCode": "en-GB", "languageName": "English", "dialect": "British", "nativeName": "English", "isDefault": true, "isEnabled": true, "flagIconUrl": "https://cdn.obvlo.com/flags/gb.svg", "rtl": false, "currencyCode": "GBP", "currencySymbol": "£", "hour12": true, "measurement": "metric", "temperatureMetric": "celsius", "isLatinAlphabet": true }, "languages": [] } ``` ## Response fields ### Top-level | Field | Type | Description | | -------------------- | ------- | ------------------------------------------------------------------------------------------- | | `translationEnabled` | boolean | Whether this organisation has multilingual content enabled. | | `defaultLanguage` | object | Full locale metadata for the organisation's default language. Always present. | | `languages` | array | Full locale metadata for each enabled language. Empty when `translationEnabled` is `false`. | ### Language object | Field | Type | Description | | ------------------- | ------- | --------------------------------------------------------------- | | `languageCode` | string | BCP 47 language code (e.g. `en-GB`, `fr-FR`). | | `languageName` | string | English name of the language. | | `dialect` | string | Regional dialect label (e.g. `British`, `Standard`). | | `nativeName` | string | Language name in its own script (e.g. `Français`). | | `isDefault` | boolean | `true` for the organisation's default language. | | `isEnabled` | boolean | Whether this language is enabled in the platform. | | `flagIconUrl` | string | URL to a flag icon representing the language's region. | | `rtl` | boolean | `true` if the language is right-to-left (e.g. Arabic, Hebrew). | | `currencyCode` | string | ISO 4217 currency code associated with the locale (e.g. `GBP`). | | `currencySymbol` | string | Display symbol for the currency (e.g. `£`). | | `hour12` | boolean | `true` if the locale uses a 12-hour clock. | | `measurement` | string | Unit system: `metric` or `imperial`. | | `temperatureMetric` | string | `celsius` or `fahrenheit`. | | `isLatinAlphabet` | boolean | `true` if the language uses the Latin alphabet. | # Get Site Page Slugs Source: https://docs.obvlo.com/api-reference/endpoint/site-page-slug GET /v2/orgs/{orgId}/sites/{siteId}/page-slug Resolve persisted, stable page URL slugs for a site using page hierarchy data. ## Overview The `/v2/orgs/{orgId}/sites/{siteId}/page-slug` endpoint resolves complete page URL paths from persisted site page records. Use this endpoint when you need stable links for destination, guide, and area pages without reconstructing URLs from mutable names. The API uses persisted page `slug` values and `parentId` relationships. Returned `slug` and `segments` values are microsite-relative. The resolver strips the site-root prefix (including reverse-proxy roots like `/local-guides`) so responses are not coupled to infrastructure paths. ## Request ```http theme={null} GET /v2/orgs/{orgId}/sites/{siteId}/page-slug ``` ### Required parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------- | | `orgId` | string | Your organisation ID. | | `siteId` | string | The site ID to resolve page slugs for. | ### Optional filters | Parameter | Type | Description | | --------------- | ------ | ------------------------------------------------- | | `destinationId` | string | Limit results to pages linked to a destination. | | `guideId` | string | Limit results to pages linked to a guide. | | `locationId` | string | Limit results to pages linked to a location/area. | When no filters are provided, the endpoint returns a full tree of site pages. ## Examples ### Get full site slug tree ```bash theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/sites/YOUR_SITE_ID/page-slug?key=YOUR_API_KEY" ``` ### Get matches for a specific guide ```bash theme={null} curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/sites/YOUR_SITE_ID/page-slug?key=YOUR_API_KEY&destinationId=DESTINATION_ID&guideId=GUIDE_ID" ``` ### Example tree response ```json theme={null} { "siteId": "site_123", "mode": "tree", "tree": [ { "pageId": "root_page", "pageType": "Root", "slug": "/london/", "homepageSlug": "/", "segments": ["london"], "parentIds": [], "destinationId": "destination_123", "guideId": null, "locationId": null, "children": [ { "pageId": "guide_page_1", "pageType": "Guide", "slug": "/london/food-guide/", "homepageSlug": null, "segments": ["london", "food-guide"], "parentIds": ["root_page"], "destinationId": "destination_123", "guideId": "guide_789", "locationId": null, "children": [] } ] } ] } ``` ### Example filtered response ```json theme={null} { "siteId": "site_123", "mode": "matches", "matches": [ { "pageId": "guide_page_1", "pageType": "Guide", "slug": "/london/food-guide/", "homepageSlug": null, "segments": ["london", "food-guide"], "parentIds": ["root_page"], "destinationId": "destination_123", "guideId": "guide_789", "locationId": null } ] } ``` ## Response notes * `mode` is `tree` when no filters are supplied. * `mode` is `matches` when one or more filters are supplied. * `slug` always includes a leading and trailing slash. * `slug` and `segments` are returned relative to the microsite root, not the reverse-proxy prefix. * Root pages include `homepageSlug: "/"`. * The endpoint returns all matches. It does not pick a single page when multiple pages match. # API Reference Source: https://docs.obvlo.com/api-reference/introduction Obvlo REST API for search, discovery, host bootstrap, and language configuration. The Obvlo API reference covers the stable content endpoints. ## Base URL ```text Base URL theme={null} https://api.obvlo.com ``` ## Authentication All requests require a valid `key` query parameter. Public endpoints use `orgId` as a path parameter in the URL, for example `/v2/orgs/{orgId}/guide-search`. Some endpoints also accept a `profileId` query parameter to scope results to a specific host profile. Contact [support@obvlo.com](mailto:support@obvlo.com) to obtain your credentials. ## Endpoints `GET /v2/orgs/{orgId}/guide-search` — Search curated travel guides. `GET /v2/orgs/{orgId}/listing-search` — Search individual listings. `GET /v2/orgs/{orgId}/location-search` — Search neighbourhoods and areas. `GET /v2/orgs/{orgId}/host-profile` — Retrieve branding and configuration for a host property. `GET /v2/orgs/{orgId}/host-home` — Bootstrap a host home screen in a single request. `GET /v2/orgs/{orgId}/org-languages` — Retrieve supported languages for an organisation. ## Pagination All search endpoints return paginated results. Use `page` (1-based) and `hitsPerPage` (max 20) to navigate pages. The response always includes `total` (total matching records) and `pages` (total page count). ## Localisation Search and discovery endpoints can return localised fields in two shapes. When `lang` is omitted, localised fields are returned as objects keyed by BCP 47 language code, for example `{ "en-GB": "Top Restaurants" }`. When `lang` is provided, the same fields are returned as resolved values for that language. This applies to guide, listing, location, and host-home responses. Preview endpoints are documented separately under the `Preview` section. # Get v2orgs guide search Source: https://docs.obvlo.com/api-reference/v2-multilingual/get-v2orgs-guide-search /api-reference/openapi-stable.json get /v2/orgs/{orgId}/guide-search Search for guides matching criteria. Returns matching guides with optional nested listings. # Get v2orgs host home Source: https://docs.obvlo.com/api-reference/v2-multilingual/get-v2orgs-host-home /api-reference/openapi-stable.json get /v2/orgs/{orgId}/host-home Single-call bootstrap for a host home screen. Returns the host profile alongside the first page of guides, each with nested listings. Preference filters (userCategory, tripType, userInterests) are applied to both guide and listing ranking. # Get v2orgs host profile Source: https://docs.obvlo.com/api-reference/v2-multilingual/get-v2orgs-host-profile /api-reference/openapi-stable.json get /v2/orgs/{orgId}/host-profile Retrieve a host profile by ID. Returns the public-safe profile fields; internal fields such as API keys and analytics configuration are excluded. # Get v2orgs listing search Source: https://docs.obvlo.com/api-reference/v2-multilingual/get-v2orgs-listing-search /api-reference/openapi-stable.json get /v2/orgs/{orgId}/listing-search Search for listings matching criteria. Returns matching listings with localized content. # Get v2orgs location search Source: https://docs.obvlo.com/api-reference/v2-multilingual/get-v2orgs-location-search /api-reference/openapi-stable.json get /v2/orgs/{orgId}/location-search Search for locations matching criteria. Returns matching locations (neighbourhoods, districts, areas) with localized content. # Get v2orgs org languages Source: https://docs.obvlo.com/api-reference/v2-multilingual/get-v2orgs-org-languages /api-reference/openapi-stable.json get /v2/orgs/{orgId}/org-languages Returns the list of languages supported by the organization. When translationEnabled is false, only the default language is returned. Pass hostId to narrow the list to a single destination when the organization configures target languages per destination; an unknown destination returns the full organization list. # Get v2orgs sites page slug Source: https://docs.obvlo.com/api-reference/v2-site-data/get-v2orgs-sites-page-slug /api-reference/openapi-stable.json get /v2/orgs/{orgId}/sites/{siteId}/page-slug Resolve complete persisted page slugs for a site. Without filters, returns a full tree. With destinationId, guideId, or locationId filters, returns matching page slug entries. # Authentication Source: https://docs.obvlo.com/developer-docs/api/authentication How to authenticate requests to the Obvlo API. ## Authentication Obvlo API requests are authenticated using the `key` query parameter. ## Example ```http theme={null} GET /v2/orgs/{orgId}/guide-search?key=YOUR_API_KEY ``` ## Related pages * [Developers overview](/developer-docs/overview) * [API overview](/developer-docs/api/overview) * [API Reference](/api-reference/introduction) # API overview Source: https://docs.obvlo.com/developer-docs/api/overview When to use the Obvlo API and where to start. ## API overview Use the API when you need direct programmatic access to Obvlo content for custom applications and integrations. ## Overview Set up secure access and token handling. Follow the implementation path from first call to production launch. Review common integration patterns for CMS, PMS, CRM/CDP, and chatbots. Review endpoint details, payloads, and responses. ## Start here * Developers overview: [/developer-docs/overview](/developer-docs/overview) * Authentication: [/developer-docs/api/authentication](/developer-docs/api/authentication) * API onboarding guide: [/onboarding-guides/api-onboarding-guide](/onboarding-guides/api-onboarding-guide) * Partner integrations: [/product/delivery/partner-integrations](/product/delivery/partner-integrations) * Endpoint details: [API Reference](/api-reference/introduction) # Zero-downtime Cloudflare migration Source: https://docs.obvlo.com/developer-docs/cloudflare-migration A professional, industry-standard workflow for migrating a live production domain from any DNS provider to Cloudflare without disrupting traffic, email, or app subdomains. This guide is provided for informational purposes only and is intended for qualified IT professionals with a thorough understanding of DNS, SSL/TLS, and domain management. Obvlo accepts no responsibility for any downtime, data loss, or service disruption that may result from following these steps. Always test in a non-production environment first and ensure you have a verified rollback plan. This document outlines the professional, industry-standard workflow for migrating a live production domain from any DNS provider to Cloudflare. Following this "Staging-to-Proxy" approach ensures that website traffic, email services, and app subdomains remain online throughout the entire transition. ## Prerequisites and verification tools Before starting, ensure you have administrative access to both your **Domain Registrar** (where you pay for the domain) and your **current DNS Host**. | Tool | Purpose | | ---------------------------------------------------------------------- | --------------------------------------------------------------------- | | [DNSViz.net](https://dnsviz.net) | Verifying DNSSEC status | | [WhatsMyDNS.net](https://whatsmydns.net) | Real-time monitoring of global nameserver propagation | | [Google Dig (Admin Toolbox)](https://toolbox.googleapps.com/apps/dig/) | Verifying individual record resolution | | AI Assistant | Sanitizing and reformatting non-standard DNS exports into BIND format | *** ## Phase 1: Pre-migration security (24–48 hours prior) The most common cause of migration failure is **DNSSEC**. If nameservers are changed while the Top-Level Domain (TLD) registry still expects cryptographic signatures from the old provider, the domain will go offline globally. ### Checklist Check your current registrar dashboard for DNSSEC/DS records. Toggle DNSSEC to **OFF** in your registrar's security settings. Enter the domain at [DNSViz.net](https://dnsviz.net). The graph must show an "Insecure" status (blue/gray boxes) with an NSEC3 proof from the registry. Do not proceed if any "Secure" (green) or "Broken" (red) paths remain. Set all critical `A`, `CNAME`, and `MX` records to **300 seconds** (5 minutes). This ensures that if a rollback is required, the change propagates in minutes rather than hours. *** ## Phase 2: The deep data audit (AI-assisted) Cloudflare's automatic scan frequently misses complex TXT records (DKIM, SPF), deeply nested subdomains, or service-specific verifications. A **manual import** is mandatory for production environments. ### Checklist Export your current DNS records as a `.csv`, `.txt`, or `.bind` file from your existing provider. Upload the raw export to an AI Assistant with the following prompt to ensure 100% compatibility: ```text theme={null} Convert these DNS records into a standard BIND zone file for Cloudflare import. Use the format: [Name] [TTL] [Class] [Type] [Content]. Wrap all TXT values in double quotes. For Webflow/Shopify/HubSpot CNAMEs, ensure trailing dots are included (e.g., cdn.webflow.com.). Convert Root A-records to CNAMEs for '@' to enable Cloudflare CNAME Flattening. ``` Manually cross-check that all Google Workspace (or other provider) MX records are present in the sanitized file. In Cloudflare **DNS > Records > Import**, upload the sanitized file. Ensure every record is set to **DNS Only** (Grey Cloud). This allows you to move DNS management without changing the underlying traffic routing or SSL handshake logic yet. *** ## Phase 3: The nameserver handover In this phase, you officially transfer "steering" control to Cloudflare. ### Checklist Navigate to Cloudflare **SSL/TLS > Overview**. Set the mode to **Full (Strict)**. Production origins (like Webflow) already have SSL. "Full (Strict)" ensures an end-to-end encrypted tunnel. In your Registrar (Squarespace/GoDaddy/etc.), replace the current nameservers with the two provided by Cloudflare. Open [WhatsMyDNS.net](https://whatsmydns.net) and check the `NS` record. You should see a majority of global nodes returning the Cloudflare nameservers. In Cloudflare **SSL/TLS > Edge Certificates**, wait until the status for your domain is **Active**. Do not enable proxying until this certificate is issued. *** ## Phase 4: Activation and proxying Now that Cloudflare owns the DNS and has a valid SSL certificate ready, you can activate the Web Application Firewall (WAF) and Workers. For detailed reverse proxy configuration (Cloudflare Workers, NGINX, Apache, IIS, and Caddy), see the [Reverse proxy guide](/microsite/reverse-proxy). ### Checklist Toggle the main `@` and `www` records to **Proxied**. Load the website and inspect the **Network** tab. Response headers must contain `server: cloudflare` and a `cf-ray` ID. Bind your Workers to specific routes (e.g. `example.com/blog/*`). Verify that subdirectories (like `/blog`) are rendering correctly and that canonical tags point to the proxied URL. *** ## Phase 5: Post-migration optimization Perform a **Purge Everything** in Cloudflare to ensure Worker logic is applied to all cached assets. After 24 hours of stability, enable DNSSEC in Cloudflare. Copy the new DS Records from Cloudflare and paste them back into your Registrar's security settings to re-establish the Chain of Trust. *** ## Incident response and rollback procedures ### Tier 1: Record-level rollback If a specific subdomain breaks, toggle that specific record back to **Grey Cloud** (DNS Only). This instantly bypasses Cloudflare for that route. ### Tier 2: The "kill switch" If the entire site behaves unexpectedly, go to the Cloudflare **Overview** page and select **Pause Cloudflare on Site**. This maintains your DNS nameservers but stops all proxying, WAF, and Worker logic. ### Tier 3: Nameserver reversal If DNS resolution fails entirely, revert the nameservers at your registrar back to the original provider's settings. This is a "hard" rollback and may take several hours to propagate. # Overview Source: https://docs.obvlo.com/developer-docs/overview Where technical teams should start when implementing Obvlo. Use this section when your engineering team is implementing Obvlo. It covers API integration, Embeddable Widget setup, Microsite implementation, and language support. ## Where do you want to start? Authentication, onboarding, and endpoint reference for direct integrations. Add the Embeddable Widget to your site and configure the core setup. Serve Microsite content on your domain and configure reverse proxy setup. View supported language codes and locale coverage for implementation. # Configuration reference Source: https://docs.obvlo.com/embed-widget/configuration-reference Complete reference for all Obvlo Embed Widget attributes and configuration options. This page documents every attribute and configuration option available on the `` custom element. For a step-by-step setup guide, see the [Quick Start](/embed-widget/quick-start). Use the [interactive test harness](https://cdn.obvlo.com/widgets/embed-test/index.html) to experiment with these options live — it generates the HTML tag for you in real time. *** ## HTML attributes These are set directly on the `` element. ### `host-id` Required **Type:** `string` The unique identifier for your host profile. Links the widget to your content in the Obvlo system. ```html theme={null} ``` In the Obvlo portal, host profiles are listed as **Destinations**. You can find the value for `host-id` in the Destinations table under the **Destination ID** column. *** ### `config-id` **Type:** `string` Select a specific embedded configuration from your host profile. When omitted, the first configuration in the profile is used. ```html theme={null} ``` *** ### `guide-id` **Type:** `string` Display a specific guide when the widget loads. Users can still navigate to other content. ```html theme={null} ``` *** ### `neighbourhood-id` **Type:** `string` Display a specific neighbourhood/geographic area when the widget loads. Only one of `guide-id` or `neighbourhood-id` should be used at a time. If both are provided, `guide-id` takes precedence. *** ### `guide-type` **Type:** `string` Filter the menu to show only guides of a specific type. | Value | Description | | --------------- | ------------------------- | | `guide` | Standard guides only | | `neighbourhood` | Neighbourhood guides only | | `featured` | Featured guides only | ```html theme={null} ``` *** ### `grid-rows` **Type:** `number` — **Default:** `1` Number of rows to display in grid views on desktop and tablet devices. ### `grid-rows-mobile` **Type:** `number` — **Default:** `3` Number of rows to display in grid views on mobile devices. ```html theme={null} ``` *** ### `lang` **Type:** `string` Sets the display language for the widget. Must be a BCP 47 language tag supported by your host profile. | Code | Language | | ------- | ------------------------ | | `en-GB` | English (UK) — *default* | | `en-US` | English (US) | | `es-ES` | Spanish (Spain) | | `fr-FR` | French (France) | | `de-DE` | German (Germany) | | `it-IT` | Italian (Italy) | | `pt-PT` | Portuguese (Portugal) | | `pt-BR` | Portuguese (Brazil) | | `zh-CN` | Chinese (Simplified) | | `ja-JP` | Japanese | | `ar-SA` | Arabic (Saudi Arabia) | See the [Language reference](/reference/language-reference) for all 63 supported languages. *** ### `uid` **Type:** `string` Custom user identifier for analytics tracking. If not provided, a random UUID is generated and persisted in browser storage. ```html theme={null} ``` *** ### `drawer` **Type:** `boolean` — **Default:** `false` Controls whether the widget appears as an overlay drawer or renders inline. This is a boolean attribute — its presence enables drawer mode. ```html theme={null} ``` ### `drawer-delay-ms` **Type:** `number` — **Default:** `0` Delay in milliseconds before automatically opening the drawer. Only applies when `drawer` is present. ```html theme={null} ``` *** ### `disable-just-for-you` **Type:** `boolean` — **Default:** `false` Hides the "Just For You" personalised guide from the guide menu. Content personalisation remains fully functional — only the menu entry is hidden. ### `hide-travel` **Type:** `boolean` — **Default:** `false` Hides travel-related content categories from the widget. *** ### `geo-location` **Type:** `string` (JSON object) — **Default:** none A JSON object containing `lat` and `lng` properties to set the user's geographic location. Used to scope content and enable proximity-based features. ```html theme={null} ``` Use single quotes around the `geo-location` value since it contains JSON with double quotes. ### `geo-search-radius` **Type:** `number` — **Default:** `10000` Search radius in metres when `geo-location` is set. Controls how far from the provided location the widget will search for content. ```html theme={null} ``` *** ### `config` **Type:** `string` (JSON object) An inline JSON string containing an `EmbeddedConfig` object. Use this attribute to pass advanced configuration options directly on the element without relying on a server-side embedded config. When using `config` in HTML, double quotes inside the JSON must be escaped as `"`. Most frameworks (React, Vue, Angular) handle this automatically when binding to an attribute. ```html theme={null} ``` See the full [EmbeddedConfig object](#embeddedconfig-object) reference below. *** ## EmbeddedConfig object The `EmbeddedConfig` object can be passed via the [`config`](#config) HTML attribute or defined in your host profile's embedded configs (managed through the Obvlo portal). All properties are optional — only include the ones you need. ```json theme={null} { "modules": ["Menu", "Grid"], "kioskMode": false, "requirePreferences": false, "disableAnimations": false, "disableSavedListings": false, "hideGuideHeader": false, "hideLanguageSelector": false, "preferredTravelMode": "walking", "cornerRadius": { "card": { "topLeft": 12, "topRight": 12, "bottomLeft": 12, "bottomRight": 12 }, "button": { "topLeft": 8, "topRight": 8, "bottomLeft": 8, "bottomRight": 8 } }, "containerOptions": { "maxWidth": 1240, "padding": { "top": 16, "bottom": 16, "left": 16, "right": 16 }, "map": { "width": "100%", "height": "500px" } }, "customIconUrl": "https://example.com/icon.svg" } ``` *** ### `modules` **Type:** `string[]` An ordered array of module names that controls which UI sections the widget displays and in what order. When omitted, the widget uses the default module set from your host profile. | Module | Description | | ----------------- | ------------------------------------------ | | `Banner` | Hero banner image at the top of the widget | | `Grid` | Card grid layout for browsing listings | | `List` | Vertical list layout for listings | | `Map` | Interactive map showing listing locations | | `Menu` | Guide/area navigation menu | | `ListMap` | Combined list and map split-view | | `FunctionalPanel` | Functional actions panel (search, filters) | The order of modules in the array determines their display order in the widget. ```json theme={null} { "modules": ["Menu", "Grid", "Map"] } ``` *** ### `kioskMode` **Type:** `boolean` — **Default:** `false` Enables kiosk mode, optimised for unattended displays such as hotel lobby screens or tourist information points. Typically combined with `disableAnimations` and `disableSavedListings`. *** ### `requirePreferences` **Type:** `boolean` — **Default:** `false` When enabled, the widget prompts users to set their personalisation preferences before showing content. Useful for experiences where personalised results are the primary goal. *** ### `disableAnimations` **Type:** `boolean` — **Default:** `false` Disables all transition animations in the widget. Useful for kiosk displays or accessibility requirements. *** ### `disableSavedListings` **Type:** `boolean` — **Default:** `false` Hides the saved listings feature, removing save buttons from listing cards and the saved list from the menu. *** ### `hideGuideHeader` **Type:** `boolean` — **Default:** `false` Hides the guide header section when a guide is displayed. Useful when embedding in a context that already provides its own header or title. *** ### `hideLanguageSelector` **Type:** `boolean` — **Default:** `false` Hides the language selector dropdown from the widget. Use this when the widget language is controlled externally via the [`lang`](#lang) attribute. *** ### `preferredTravelMode` **Type:** `string` — **Default:** platform default Sets the preferred travel mode for directions and distance calculations. | Value | Description | | --------- | --------------------------------- | | `driving` | Car/vehicle travel times | | `walking` | Walking distances and times | | `cycling` | Cycling distances and times | | `transit` | Public transport routes and times | ```json theme={null} { "preferredTravelMode": "walking" } ``` *** ### `cornerRadius` **Type:** `object` Customise the border radius of UI elements to match your brand. Each element type accepts an object with `topLeft`, `topRight`, `bottomLeft`, and `bottomRight` values in pixels. | Property | Description | | -------- | ------------------------------- | | `card` | Border radius for content cards | | `button` | Border radius for buttons | ```json theme={null} { "cornerRadius": { "card": { "topLeft": 16, "topRight": 16, "bottomLeft": 16, "bottomRight": 16 }, "button": { "topLeft": 24, "topRight": 24, "bottomLeft": 24, "bottomRight": 24 } } } ``` For uniform corners, set all four values to the same number. Common presets: `0` (square), `8` (small), `12` (medium), `16` (large), `24` (extra-large). *** ### `containerOptions` **Type:** `object` Controls the widget container's layout and sizing. #### `containerOptions.maxWidth` **Type:** `number` (pixels) Maximum width of the widget container. The widget will not exceed this width even if the parent element is wider. #### `containerOptions.padding` **Type:** `object` Inner padding of the widget container in pixels. | Property | Type | Description | | -------- | -------- | ------------------------ | | `top` | `number` | Top padding in pixels | | `bottom` | `number` | Bottom padding in pixels | | `left` | `number` | Left padding in pixels | | `right` | `number` | Right padding in pixels | #### `containerOptions.map` **Type:** `object` Dimensions for the map module when displayed. | Property | Type | Default | Description | | -------- | -------- | -------- | ------------------------------------------- | | `width` | `string` | `"100%"` | CSS width value (e.g. `"100%"`, `"800px"`) | | `height` | `string` | `"auto"` | CSS height value (e.g. `"500px"`, `"60vh"`) | ```json theme={null} { "containerOptions": { "maxWidth": 1240, "padding": { "top": 20, "bottom": 20, "left": 16, "right": 16 }, "map": { "width": "100%", "height": "500px" } } } ``` *** ### `customIconUrl` **Type:** `string` (URL) URL to a custom icon image (SVG recommended) displayed in the widget. Use this to brand the widget with your own logo or icon. ```json theme={null} { "customIconUrl": "https://example.com/my-icon.svg" } ``` *** ## Configuration priority When the widget initialises, it merges configuration from multiple sources. A value from a higher-priority source always wins. | Priority | Source | Description | | ----------- | --------------------------- | -------------------------------------------------------------------- | | 1 — highest | **HTML attributes** | Values set directly on the element, including the `config` attribute | | 2 | **Named embedded config** | The embedded config matching `config-id`, if provided | | 3 | **Default embedded config** | The first embedded config in your host profile | | 4 — lowest | **Attribute defaults** | Default values documented on this page | *** ## Full examples ### Modular menu with grid ```html theme={null} ``` ### Kiosk display ```html theme={null} ``` ### Geo-scoped with map ```html theme={null} ``` ### Custom-branded with rounded cards ```html theme={null} ``` ### Drawer with delayed auto-open ```html theme={null} ``` *** ## Related pages * [Developers overview](/developer-docs/overview) * [Quick Start](/embed-widget/quick-start) * [Language reference](/reference/language-reference) # Quick Start Source: https://docs.obvlo.com/embed-widget/quick-start Add the Obvlo Embed Widget to your page in minutes. ## Installation Add the widget script to your HTML ``: ```html theme={null} ``` Always load the widget script with `defer` or `async`. Both attributes prevent the script from blocking HTML parsing, keeping your page's initial render fast. Use `defer` (as shown above) if other scripts on the page depend on the widget being initialised first — it guarantees execution order. Use `async` if the widget is fully independent and you want it to execute as soon as it downloads. ## Basic usage Place the custom element anywhere in your page body: ```html theme={null} ``` That's it. The widget loads your content automatically once the script initialises. *** ## Configuration attributes When the widget initialises it resolves its configuration by merging several sources in priority order. A value set by a higher-priority source always wins over a lower-priority one. | Priority | Source | Description | | ----------- | --------------------------- | --------------------------------------------------------------------------------------------------- | | 1 — highest | **HTML attributes** | Values you set directly on the `` element. | | 2 | **Named embedded config** | The embedded config from your host profile whose ID matches the `config-id` attribute, if provided. | | 3 | **Default embedded config** | The first embedded config defined in your host profile, used when no `config-id` is supplied. | | 4 — lowest | **Attribute defaults** | The default values documented for each attribute below. | Your host profile contains the full list of embedded configs in the `embeddedConfigs` field. Use the [Get Host Profile](/api-reference/endpoint/host-profile) endpoint to inspect the configs available for a given `host-id`. *** ### Required #### `host-id` Required **Type:** `string` The unique identifier for your host profile. This links the widget to your content in the Obvlo system. In the Obvlo portal, host profiles are listed as **Destinations**. You can find the value for `host-id` in the Destinations table under the **Destination ID** column. ```html theme={null} ``` *** ### Display mode #### `drawer` **Type:** `boolean` — **Default:** `false` Controls whether the widget appears as an overlay drawer or renders inline. | Value | Behaviour | | ------------------ | -------------------------------------------------------- | | *(omitted)* | Widget displays inline within the page | | `drawer` (present) | Widget opens as an overlay drawer on top of page content | ```html theme={null} ``` #### `drawer-delay-ms` **Type:** `number` — **Default:** `0` Delay in milliseconds before automatically opening the drawer. Only applies when `drawer` is present. ```html theme={null} ``` *** ### Layout #### `config-id` **Type:** `string` Select a specific embedded configuration from your host profile. When omitted, the first configuration in the profile is used. ```html theme={null} ``` *** ### Content filtering #### `guide-id` **Type:** `string` Display a specific guide when the widget loads. Users can still navigate to other content. ```html theme={null} ``` #### `neighbourhood-id` **Type:** `string` Display a specific neighbourhood/geographic area when the widget loads. Only one of `guide-id` or `neighbourhood-id` should be used at a time. If both are provided, `guide-id` takes precedence. #### `guide-type` **Type:** `string` Filter the menu to show only guides of a specific type. | Value | Description | | --------------- | ------------------------- | | `guide` | Standard guides only | | `neighbourhood` | Neighbourhood guides only | | `featured` | Featured guides only | *** ### Grid display #### `grid-rows` **Type:** `number` — **Default:** `1` Number of rows to display in grid views on desktop and tablet devices. #### `grid-rows-mobile` **Type:** `number` — **Default:** `3` Number of rows to display in grid views on mobile devices. *** ### Geolocation #### `geo-location` **Type:** `string` (JSON object) A JSON object with `lat` and `lng` properties to set the user's geographic location. Used to scope content by proximity. ```html theme={null} ``` Use single quotes around the `geo-location` value since it contains JSON with double quotes. #### `geo-search-radius` **Type:** `number` — **Default:** `10000` Search radius in metres when `geo-location` is set. *** ### Feature toggles #### `disable-just-for-you` **Type:** `boolean` — **Default:** `false` Hides the "Just For You" personalised guide from the guide menu. Content personalisation remains fully functional when this attribute is set — only the menu entry is hidden. Useful for kiosk or focused experiences where the personalisation prompt would be distracting. #### `hide-travel` **Type:** `boolean` — **Default:** `false` Hides travel-related content categories from the widget. *** ### Localisation #### `lang` **Type:** `string` Sets the initial display language for the widget. Must be a full language code supported by your host profile. **Format:** `language-DIALECT` — for example `en-GB`, `es-ES`, `fr-FR` ```html theme={null} ``` Common language codes: | Code | Language | | ------- | ------------------------ | | `en-GB` | English (UK) — *default* | | `en-US` | English (US) | | `es-ES` | Spanish (Spain) | | `fr-FR` | French (France) | | `de-DE` | German (Germany) | | `it-IT` | Italian (Italy) | | `pt-PT` | Portuguese (Portugal) | | `pt-BR` | Portuguese (Brazil) | | `zh-CN` | Chinese (Simplified) | | `ja-JP` | Japanese | | `ar-SA` | Arabic (Saudi Arabia) | See the [Language reference](/reference/language-reference) for all 63 supported languages. *** ### Analytics #### `uid` **Type:** `string` Custom user identifier for analytics tracking. If not provided, a random UUID is generated automatically. ```html theme={null} ``` **Usage notes:** * Useful for tracking specific users across sessions * Should be unique per user * Automatically persisted in browser storage * Used for Google Tag Manager events *** ## Advanced configuration For more control over the widget's layout, modules, and styling, pass a JSON `config` attribute containing an `EmbeddedConfig` object: ```html theme={null} ``` The config object supports modules, kiosk mode, corner radius, container sizing, and more. See the [Configuration reference](/embed-widget/configuration-reference) for the full list of options. Use the [interactive test harness](https://cdn.obvlo.com/widgets/embed-test/index.html) to experiment with all attributes and config options — it generates the HTML tag for you in real time. *** ## Analytics events ### Using Google Analytics If your website is already using Google Analytics, the widget will automatically send events to your GA property. No additional setup is required. ### Using a different analytics provider The widget dispatches custom events that you can listen to and send to your preferred analytics provider. This functionality is designed for clients who don't use Google Analytics. To listen for analytics events, add the following JavaScript to your page: ```javascript theme={null} window.addEventListener('ob_analytics_event', (e) => { console.log(e.detail); // Send to your analytics provider }); ``` **Example implementation:** ```javascript theme={null} window.addEventListener('ob_analytics_event', (e) => { const { eventName, params, uid } = e.detail; // Send to your analytics provider yourAnalytics.track(eventName, { ...params, userId: uid }); }); ``` The event detail object contains the following properties: * `eventName` (string): The name of the analytics event (e.g., "widget\_visible", "guide\_viewed"). * `params` (object): Additional event parameters including event type ("click", "view", or "conversion"), host ID, host name, and any custom parameters specific to the event. * `uid` (string): The unique user identifier for tracking user sessions and behavior. ### Available events All events include standard parameters: `type` ('click', 'view', or 'conversion'), `host_id`, `host_name`, and `uid`. * **`widget_visible`** *(view)* — Triggered when the widget first becomes visible in the viewport. Params: `main_view` * `main_view`: widget layout type (e.g., `modular`) * **`widget_loaded`** *(view)* — Triggered when the widget finishes loading. Params: `main_view` * `main_view`: widget layout type (e.g., `modular`) * **`search`** *(click)* — Triggered when a user performs a search with a non-empty string. Params: `term` * `term`: The search query string * **`search_open`** *(click)* — Triggered when a user opens the search input field. * **`search_clear`** *(click)* — Triggered when a user clears the search input. * **`search_close`** *(click)* — Triggered when a user closes the search input field. * **`personalize_start`** *(click)* — Triggered when a user starts personalization for the first time. * **`personalize_update`** *(click)* — Triggered when a user clicks to update their existing personalization preferences. * **`personalization_complete`** *(conversion)* — Triggered when a user completes personalization for the first time. Params: all selected preference values * `tripType`: The user's selected trip type * `userCategory`: The user's selected category * `userInterests`: The user's selected interests * **`personalization_updated`** *(conversion)* — Triggered when a user updates their existing personalization preferences. Params: all selected preference values * `tripType`: The user's selected trip type * `userCategory`: The user's selected category * `userInterests`: The user's selected interests * **`listing_selected`** *(click)* — Triggered when a user selects a listing. Params: `listing_id`, `item_id`, `item_type`, `source` * `listing_id`: The selected listing's ID * `item_id`: The selected listing's ID * `item_type`: `listing` * `source`: `grid`, `list-map`, `map-component`, or `modular-menu` * **`guide_selected`** *(click)* — Triggered when a user selects a guide. Params: `guide_id`, `item_id`, `item_type`, `source` * `guide_id`: The selected guide's ID * `item_id`: The selected guide's ID * `item_type`: `guide` * `source`: Where the selection occurred * **`geoarea_selected`** *(click)* — Triggered when a user selects a geographic area. Params: `geoarea_id`, `item_id`, `item_type`, `source` * `geoarea_id`: The selected area's ID * `item_id`: The selected area's ID * `item_type`: `geoarea` * `source`: Where the selection occurred * **`page_changed`** *(click)* — Triggered when a user navigates to a different page in a paginated list. Params: `page`, `page_size`, `source` * `page`: The new page number * `page_size`: Number of items per page * `source`: Source of the page change (e.g., `explore-view`) * Additional context-specific parameters (e.g., `view`, `term`, `guide_id`/`geoarea_id`) * **`menu_scroll`** *(click)* — Triggered when a user scrolls through the guide/area menu. Params: `action` * `action`: `navigation`, `drag`, or `scroll` * **`filter_option_selected`** *(click)* — Triggered when a user selects a filter option. Params: `option` * `option`: The selected filter option * **`jfy_selected`** *(click)* — Triggered when a user selects "Just for You" (personalized recommendations). * **`drawer_open`** *(click)* — Triggered when a user opens the drawer widget. * **`language_change`** *(click)* — Triggered when a user changes the language selection. Params: `language_code` * `language_code`: BCP 47 language tag (e.g., `en-GB`, `fr-FR`) * **`saved_list_add`** *(click)* — Triggered when a user adds a listing to their saved list. Params: `listing_id`, `id`, `type` * `listing_id`: The listing's ID * `id`: The listing's ID * `type`: `listing` * **`saved_list_remove`** *(click)* — Triggered when a user removes a listing from their saved list. Params: `listing_id`, `id`, `type` * `listing_id`: The listing's ID * `id`: The listing's ID * `type`: `listing` * **`listing_view_on_map`** *(click)* — Triggered when a user clicks to view a listing's location on a map with directions. Params: `listing_id`, `listing_name` * `listing_id`: The listing's ID * `listing_name`: The listing's name * **`listing_book_now`** *(click)* — Triggered when a user clicks to make a table booking at a restaurant. Params: `listing_id`, `listing_name` * `listing_id`: The listing's ID * `listing_name`: The listing's name * **`listing_view_website`** *(click)* — Triggered when a user clicks to visit a listing's website. Params: `listing_id`, `listing_name` * `listing_id`: The listing's ID * `listing_name`: The listing's name * **`listing_see_reviews`** *(click)* — Triggered when a user clicks to view reviews on Google Maps or TripAdvisor. Params: `listing_id`, `listing_name`, `review_type`, `source` * `listing_id`: The listing's ID * `listing_name`: The listing's name * `review_type`: `google` or `tripadvisor` * `source`: `listing-detail` * **`listing_go_back`** *(click)* — Triggered when a user clicks the back button from a listing detail view. Params: `listing_id`, `listing_name` * `listing_id`: The listing's ID * `listing_name`: The listing's name * **`listing_info_click`** *(click)* — Triggered when a user clicks on listing information (map directions, phone, or website). Params: `listing_id`, `listing_name`, `info_type` * `listing_id`: The listing's ID * `listing_name`: The listing's name * `info_type`: `map`, `phone`, or `website` * **`listing_slide_changed`** *(click)* — Triggered when a user changes the image in the listing carousel. Params: `listing_id`, `listing_name`, `action`, `new_index` * `listing_id`: The listing's ID * `listing_name`: The listing's name * `action`: `navigation`, `drag`, or `pagination` * `new_index`: The index of the new slide * **`listing_see_attribution`** *(click)* — Triggered when a user clicks on a photo attribution link. Params: `listing_id`, `listing_name`, `photo_id`, `link` * `listing_id`: The listing's ID * `listing_name`: The listing's name * `photo_id`: The ID of the photo * `link`: The attribution URL * **`map_marker_click`** *(click)* — Triggered when a user clicks on a map marker. Params: `marker_type`; for listing markers: `listing_id`, `listing_name` * `marker_type`: `listing` or `home` * `listing_id`: The listing's ID *(listing markers only)* * `listing_name`: The listing's name *(listing markers only)* * **`map_popup_close`** *(click)* — Triggered when a user closes a map marker popup. Params: `marker_type`; for listing markers: `listing_id`, `listing_name` * `marker_type`: `listing` or `home` * `listing_id`: The listing's ID *(listing markers only)* * `listing_name`: The listing's name *(listing markers only)* * **`map_popup_listing_click`** *(click)* — Triggered when a user clicks a listing button within a map popup. Params: `listing_id` * `listing_id`: The listing's ID * **`map_move`** *(click)* — Triggered when a user pans/drags the map. Params: `center_lat`, `center_lng` * `center_lat`: The latitude of the new map center * `center_lng`: The longitude of the new map center * **`map_zoom`** *(click)* — Triggered when a user zooms the map. Params: `zoom_direction`, `zoom_level` * `zoom_direction`: `in` or `out` * `zoom_level`: The new zoom level * **`book_now`** *(click)* — Triggered when a user clicks a book now button. Params: `url`, `preferences` * `url`: The booking URL * `preferences`: The user's personalization preferences *** ## Examples ### Basic inline widget ```html theme={null} My Travel Site

Explore Our Destination

``` ### Drawer mode with delay ```html theme={null} ``` *** ## Browser support * Chrome / Edge — latest 2 versions * Firefox — latest 2 versions * Safari — latest 2 versions * Mobile: iOS Safari, Chrome for Android ## Responsive behaviour The widget automatically adjusts its layout based on container width: | Breakpoint | Width | | ---------- | -------------- | | X-Large | ≥ 1200px | | Large | 992px – 1199px | | Medium | 768px – 991px | | Small | \< 768px | *** ## Troubleshooting ### Widget not appearing 1. Verify the script tag is loading correctly (check the browser console for errors). 2. Confirm `host-id` is correct and the profile is active. 3. Check that the element is not hidden by CSS. ### Content not loading 1. Verify your host profile is properly configured in the Obvlo admin portal. 2. Check the browser console for API errors. ## Related pages * [Developers overview](/developer-docs/overview) * [Configuration reference](/embed-widget/configuration-reference) * [Language reference](/reference/language-reference) *** ## Getting help * Contact your Obvlo account manager * Email [support@obvlo.com](mailto:support@obvlo.com) # Terminology Source: https://docs.obvlo.com/getting-started/key-terms Key terms teams need before onboarding and implementation. ## Terminology These are the terms most teams need first. | Term | Meaning | | -------------------------------- | ------------------------------------------------------------------------------------------ | | Destination | The location you are managing content for, such as a hotel, resort, city, or region. | | Guide | A collection of listings built around a place or theme. | | Listing | An individual recommendation inside a guide, such as a restaurant, attraction, or event. | | Portal | The management dashboard your team uses to review content, settings, and launch readiness. | | Web App (Digital Concierge) | The hosted guest-facing experience managed by Obvlo. | | Embeddable Widget (Trip Planner) | The embeddable experience placed inside your existing website. | | Microsite (HTML Pages) | Search-focused HTML pages delivered on your domain. | | API | A direct integration for sending Obvlo destination content into your own systems. | For the complete terminology list, see the [Glossary](/support/glossary). # Getting Started Source: https://docs.obvlo.com/getting-started/start-here A 5-step guide from setup to going live with your destination content. This page is for customer teams onboarding with Obvlo. Follow the steps in order, or jump to the section that matches your current stage. If any terms are unfamiliar, read [Terminology](/getting-started/key-terms) first. *** ## Step 1: Defining your content needs This first step gives Obvlo the core inputs needed to begin content creation quickly. Share your brand guidelines, tone of voice, logos, colours, and key editorial rules. Confirm which destinations are in scope for launch. Share any preferred themes or content priorities if you want to guide the initial setup. How you plan to deliver content to guests: through your website, a hosted experience, or a direct integration. This helps Obvlo set up content to match your delivery approach from the start. *** ## Step 2: Creating your content This is where Obvlo sets up your destinations and generates your content set. * Apply available brand and tone inputs * Generate guides, listings, and related content * Deliver content ready for your review and updates *** ## Step 3: Reviewing your content Your team can review and personalise content in the **Portal** before launch. * Review guides, listings, pages, and promos * Edit copy, imagery, and categorisation where needed * Approve launch-ready content when needed * Request updates from Obvlo where required For detailed guidance on reviewing and managing content in the Portal, use the [Portal guide](/onboarding-guides/portal-onboarding-guide). *** ## Step 4: Using your content This is where you activate the implementation path already agreed. * **SEO and discoverability:** [Microsite](/onboarding-guides/microsite-onboarding-guide) — search-friendly HTML pages to improve destination discoverability * **Guest experience:** [Web App (Digital Concierge)](/onboarding-guides/hosted-onboarding-guide) — a full guest-facing experience hosted by Obvlo * **Digital channel enhancement:** [Embeddable Widget (Trip Planner)](/onboarding-guides/widget-onboarding-guide) — embed Obvlo content inside your existing website * **Custom integrations:** [API](/onboarding-guides/api-onboarding-guide) — onboard API delivery, then move into developer implementation pages *** ## Step 5: Going live Make sure launch content has been reviewed, approved, and signed off. Check that your agreed delivery path is configured and connected to the right destinations. Validate branding, content display, links, language behaviour, and responsiveness. Make sure your team knows what is live, where performance is monitored, and who owns escalation. After launch, monitor engagement and feedback, refresh content where needed, and adjust rollout priorities over time. *** Before you start, confirm who on your team owns approvals and day-to-day updates. That person is your point of contact with Obvlo throughout onboarding. ## Common questions | Question | Answer | | --------------------------------------------------- | ----------------------------------------------------------------------------------- | | Where do I start? | You're here. Follow the 5 steps above in order. | | How do I choose the right delivery path? | See Step 4 above or [Help FAQs](/support/faq#help-faqs). | | What do terms like Guide, Listing, and Portal mean? | See [Terminology](/getting-started/key-terms) or the [Glossary](/support/glossary). | | Where do I find product-specific questions? | Use the [FAQ](/support/faq) — it is organised by product area. | # Overview Source: https://docs.obvlo.com/index Everything you need to get destination content live. Obvlo empowers travel businesses to sell their destinations at scale with hyper-relevant local content. This documentation is written for customer teams. Developer implementation pages are included where technical setup is required. ## Where do you want to start? Follow the 5-step journey from setup to going live. API setup, authentication, and endpoint reference. Product guides for Portal, Web App, Embeddable Widget, and Microsite. FAQ, glossary, and support references. ## Next step * If you are onboarding now, start at [Getting Started](/getting-started/start-here). * If your team is implementing code, go to [Developer docs](/developer-docs/api/overview). # Overview Source: https://docs.obvlo.com/microsite/overview On-brand HTML pages served on your domain via reverse proxy. Optimised for SEO and AI discoverability. The Obvlo Microsite delivers on-brand, pre-rendered HTML pages served directly on your domain via reverse proxy. Each page is purpose-built for search engine visibility and AI discoverability, turning your destination content into a durable organic traffic asset. ## Benefits ### SEO performance Every microsite page is a fully rendered HTML document — no client-side rendering, no JavaScript dependency for content. Search engine crawlers receive complete, structured content on the first request, which improves indexing speed and ranking potential. Pages include semantic HTML, canonicals, hreflang tags for multi-language content, and structured data markup where applicable. ### GEO (Generative Engine Optimisation) Generative AI models build responses by drawing from sources they recognise as authoritative and comprehensive. By publishing rich, curated destination content at scale under your own domain, microsite pages strengthen your overall content presence and increase the likelihood your brand is consistently cited across AI-generated travel queries. This builds long-term GEO authority that compounds as your content library grows. ### AEO (Answer Engine Optimisation) AI search engines and large language models favour content that is clear, structured, and authoritative. Microsite pages are written and formatted to appear as high-confidence answers to destination and travel planning queries. This means your content is more likely to be surfaced in AI-generated summaries and conversational search results, giving you visibility in a channel that traditional SEO does not fully address. ### Multi-language support Pages are generated in all languages configured for your site. Each language variant is independently indexed, extending your reach across international search markets without duplicating manual effort. See the [Language reference](/reference/language-reference) for all 63 supported languages. ## Domain authority and the reverse proxy The reverse proxy is one of the most important aspects of the microsite setup. Rather than hosting content on a subdomain (e.g. `microsite.yourdomain.com`) or a third-party domain, the reverse proxy serves microsite pages as part of your main domain (e.g. `yourdomain.com/destination`). This matters for two reasons: 1. **Domain authority** — all SEO signals (backlinks, page authority, engagement signals) accumulate on your main domain rather than being split across a separate hostname. Over time this compounds, making new pages rank faster and more competitively. 2. **Trust signals for AI** — AI crawlers and answer engines weight content more heavily when it appears on a domain with an established trust profile. Pages on your primary domain inherit that trust immediately. Without the reverse proxy in place, the full SEO and AEO benefit of the microsite is not realised. See the setup guide for reverse proxy configuration requirements. Find more information about how to configure reverse proxy here [Reverse Proxy Guide](/microsite/reverse-proxy). ## Sitemap integration Each microsite generates its own `sitemap.xml` covering all destination, guide, and topic pages. Search engines should discover microsite content through two routes: 1. A link from your main site, which is the primary discovery path for crawlers. 2. A reference to the microsite sitemap from your main domain's `sitemap_index.xml`. Use a sitemap index for sitemap references only. Do not mix regular page URLs and sitemap URLs in the same file. For search engines to discover and index these pages as part of your domain, you should reference the microsite sitemap in your main domain's `sitemap_index.xml`: ```xml theme={null} https://yourdomain.com/travel-guides/sitemap-0.xml ``` Full example of an index sitemap: ```xml theme={null} https://yourdomain.com/sitemap.xml https://yourdomain.com/travel-guides/sitemap-0.xml ``` If CMS constraints make it impossible to edit the index sitemap, link microsite pages from your main site where crawlers can discover them easily, and submit the microsite sitemap to Google Search Console. If that link is missing or buried on an unimportant page, discovery becomes slower and less reliable. The sitemap index reference is therefore crucial as a backup discovery path and for maximum crawl efficiency. ## Page types | Page type | Description | Status | | --------------- | --------------------------------------------------------------------------------------------------------- | --------- | | **Destination** | Overview page for a destination, covering key travel information and curated highlights | Available | | **Guide** | In-depth curated content on a specific aspect of a destination — neighbourhoods, experiences, itineraries | Available | | **Topic** | Thematic pages aggregating content across destinations around a common topic or interest | Roadmap | ### Destination pages Destination pages serve as the authoritative landing page for a location. They provide structured overview content and link to all associated guides, giving both users and search engines a clear, navigable content hierarchy. ### Guide pages Guide pages are the primary content unit of the microsite. Each guide covers a focused subject — a neighbourhood, a curated experience, a type of activity — with rich, structured content optimised to answer specific travel queries. Guides are where the majority of organic search traffic lands. ### Topic pages *(coming soon)* Topic pages will aggregate content across multiple destinations around a shared theme or interest category. These pages are designed to capture broader, high-volume queries and funnel traffic into destination and guide content. ## Related pages * [Developers overview](/developer-docs/overview) * [Reverse proxy](/microsite/reverse-proxy) * [Language reference](/reference/language-reference) # Reverse proxy Source: https://docs.obvlo.com/microsite/reverse-proxy Configure a reverse proxy to serve Obvlo Microsite content through your own domain, consolidating SEO authority and providing a seamless user experience. Serving Obvlo content through your primary domain consolidates SEO authority and ensures a seamless user experience. This guide covers configuration for Cloudflare Workers, NGINX, Apache, IIS, and Caddy. A typical Obvlo content URL follows this pattern: ```text theme={null} https://content.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/ ``` ## SEO benefits * **Consolidated authority** — link equity and domain authority accrue to your primary domain rather than being split across subdomains. * **Consistent URLs** — search engine crawlers index a single URL structure, eliminating duplicate content risks. * **Improved engagement signals** — a unified domain reduces bounce rates and increases session duration. * **Simplified analytics** — all traffic flows through one domain, making attribution straightforward. * **Performance control** — edge caching improves page speed, a direct ranking factor. ## General principles These principles apply regardless of which reverse proxy you use. **Path-based proxying** — configure your proxy to forward requests from a specific URL path (e.g. `/travel-guides/`) to your Obvlo content URL. **Host header** — the `Host` header sent to the Obvlo backend must be `content.obvlo.com`. **Forwarded headers** — include the following so the backend can identify the original client: | Header | Value | | ------------------- | ------------------------------------- | | `X-Forwarded-For` | Client IP address | | `X-Forwarded-Proto` | Original protocol (`http` or `https`) | | `X-Forwarded-Host` | The visitor's requested domain | **SSL/TLS** — the connection between your proxy and the Obvlo backend must use HTTPS. *** ## Cloudflare Workers (recommended) Cloudflare Workers run JavaScript at the edge. Cloudflare handles SSL certificate provisioning and renewal automatically — no origin servers, load balancers, or certificate managers required. **Request flow:** Visitor → Cloudflare Edge (SSL + Worker) → Obvlo CDN ### Prerequisites * A Cloudflare account (free tier is sufficient) * Your domain's DNS managed by Cloudflare (nameservers pointed to Cloudflare) * Your Obvlo content URL (provided during onboarding) ### Setup 1. **Add your domain to Cloudflare.** Update your domain registrar's nameservers to the Cloudflare nameservers shown in the dashboard. 2. **Create a DNS A record.** Point your domain to `192.0.2.1` (a dummy address — the Worker intercepts traffic before it reaches any origin). Set proxy status to enabled (orange cloud icon). 3. **Create the Worker.** Go to **Workers & Pages → Create → Create Worker**, select "Start with Hello World!", deploy, then click **Edit Code** and replace the contents with the worker code below. 4. **Configure routes.** Go to your domain → **Workers Routes → Add Route**. Add a route matching your specific path, e.g., `yourdomain.com/local-guides*`, pointing to your worker. 5. **Verify.** Visit your domain — Obvlo content should load with your domain in the browser address bar. ### Worker code Replace `ORIGIN_BASE` with your actual Obvlo content URL and `PROXY_PATH` with the path on your domain where the content should live. ```javascript theme={null} const ORIGIN_BASE = "https://content.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live"; const PROXY_PATH = "/local-guides"; export default { async fetch(request) { const url = new URL(request.url); if (!url.pathname.startsWith(PROXY_PATH)) { return fetch(request); } let targetPath = url.pathname.replace(PROXY_PATH, ""); if (targetPath === "" || targetPath === "/") targetPath = "/"; const originUrl = `${ORIGIN_BASE}${targetPath}${url.search}`; const headers = new Headers(request.headers); headers.set("Host", "content.obvlo.com"); headers.set("X-Forwarded-Host", url.hostname); headers.set("X-Forwarded-Proto", "https"); try { const response = await fetch(originUrl, { method: request.method, headers, redirect: "follow", }); const contentType = response.headers.get("Content-Type") || ""; if (contentType.includes("text/html")) { return new HTMLRewriter() .on("link, script, img, source, a", { element(element) { const attrs = ["href", "src", "srcset"]; attrs.forEach(attr => { if (element.hasAttribute(attr)) { let val = element.getAttribute(attr); // Only prefix relative paths (starting with / but not //) if (val.startsWith("/") && !val.startsWith("//")) { element.setAttribute(attr, `${PROXY_PATH}${val}`); } } }); } }) .transform(response); } const finalResponse = new Response(response.body, response); finalResponse.headers.delete("x-served-by"); finalResponse.headers.delete("x-cache"); return finalResponse; } catch (err) { return new Response(`Proxy error: ${err.message}`, { status: 502 }); } }, }; ``` ### Important Considerations * **Trailing Slashes:** In the Worker code, ensure `ORIGIN_BASE` does not end with a slash. * **Path Stripping:** The code above strips the `PROXY_PATH` before forwarding to Obvlo. This is necessary because the Obvlo backend expects requests relative to its own root, not your domain's sub-folder. ### What this gives you * Automatic SSL certificate management — zero configuration * No servers or infrastructure to maintain * Edge execution with sub-millisecond cold starts globally * 100,000 free requests per day on the free tier * Built-in DDoS protection and CDN caching *** ## Web server configurations Traditional web server setups require you to manage SSL certificates, server infrastructure, and updates yourself. For most use cases, the Cloudflare Workers approach above is simpler to operate. ### NGINX Add the following to your server block. Replace the placeholder paths with your actual Obvlo content URL. ```nginx theme={null} server { listen 443 ssl; server_name customer-domain.com; ssl_certificate /etc/nginx/ssl/customer-domain.com.crt; ssl_certificate_key /etc/nginx/ssl/customer-domain.com.key; location / { proxy_pass https://content.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/; proxy_set_header Host content.obvlo.com; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_redirect off; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` The trailing slash on both `location` and `proxy_pass` strips the location prefix and appends the remainder to the backend URL. The `Host` header must be `content.obvlo.com`. ### Apache HTTP Server Enable `mod_proxy` and `mod_proxy_http`, then configure your virtual host: ```apache theme={null} ServerName customer-domain.com SSLEngine on SSLCertificateFile /path/to/certificate.crt SSLCertificateKeyFile /path/to/private.key ProxyPass "/" "https://content.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/" ProxyPassReverse "/" "https://content.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/" ProxyPreserveHost Off RequestHeader set Host "content.obvlo.com" RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-Host "customer-domain.com" ``` `ProxyPassReverse` rewrites response `Location` headers so redirects use your domain. Set `ProxyPreserveHost Off` and explicitly set `Host` to `content.obvlo.com`. ### IIS (Internet Information Services) IIS requires the Application Request Routing (ARR) module and URL Rewrite module. On Azure App Service, these are pre-installed. **Enable the proxy:** 1. Open IIS Manager and select your server in the Connections pane. 2. Double-click **Application Request Routing Cache**. 3. Click **Server Proxy Settings** and check **Enable proxy**. **web.config** — place this in your site's root directory: ```xml theme={null} ``` ARR automatically adds `X-Forwarded-For`, `X-Forwarded-Proto`, and `X-Forwarded-Host` headers. Proxy timeouts are configured in the ARR server-level settings. ### Caddy Caddy handles SSL automatically via Let's Encrypt: ```caddy theme={null} customer-domain.com { reverse_proxy /* https://content.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/ { header_up Host content.obvlo.com header_up X-Forwarded-Host {host} } } ``` Caddy automatically sets `X-Forwarded-For` and `X-Forwarded-Proto`. *** ## CDN / edge providers If you already use a CDN or edge platform, it can likely serve as your reverse proxy. The key requirements are the same across all providers: correct `Host` header, forwarded headers, and HTTPS to the origin. | Provider | Notes | | ----------------- | ------------------------------------------- | | Cloudflare | Workers (see above) or Page Rules | | Amazon CloudFront | Origin configuration with custom headers | | Google Cloud CDN | URL maps with backend services | | Azure Front Door | Routing rules with backend pools | | Fastly | VCL configuration or Compute\@Edge | | Akamai | Property configuration with origin settings | *** ## Troubleshooting ### Content not loading * Verify the `Host` header is set to `content.obvlo.com` — this is the most common configuration error. * Confirm the full Obvlo content URL is correct, including the trailing path. * Test the origin URL directly: ```bash theme={null} curl -I https://content.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/ ``` ### SSL certificate errors * **Cloudflare Workers** — ensure DNS records are proxied (orange cloud icon). Cloudflare manages certificates automatically. * **Web servers** — verify certificate paths and renewal configuration (e.g. Certbot cron jobs). * **CDN providers** — check origin SSL settings match your backend configuration. ### Cloudflare Worker not executing * Confirm the DNS A record has proxy status enabled (orange cloud, not grey). * Check **Workers Routes** — ensure the correct worker is assigned to your domain pattern. * Purge the Cloudflare cache: **Caching → Configuration → Purge Everything**. * Verify the worker was deployed by checking real-time logs in the Workers dashboard. ## Related pages * [Developers overview](/developer-docs/overview) * [Microsite overview](/microsite/overview) * [Language reference](/reference/language-reference) # API Source: https://docs.obvlo.com/onboarding-guides/api-onboarding-guide Plan API onboarding from first call to production readiness. Use this guide when your team wants to deliver Obvlo content through your own website, app, or internal systems. ## What this product is API delivery gives your developers direct access to destination content so it can be used in your own applications, websites, and workflows. ## Best use case Use API delivery when you need a custom user experience, or when Obvlo content needs to flow into your existing products. ## What to align before setup * Organisation and destination scope for launch * Ownership for API implementation, testing, and monitoring * Environment and release plan for integration rollout * Reporting and monitoring expectations after launch ## Start path for developers Use the Developer pages below as your implementation path: 1. [API overview](/developer-docs/api/overview) 2. [Authentication](/developer-docs/api/authentication) 3. [API Reference](/api-reference/introduction) 4. [Partner Integrations](/product/delivery/partner-integrations) ## Who does what * **Your team** owns implementation, release, and production monitoring. * **Obvlo** provides destination content, API documentation, and onboarding support. ## Related FAQs | Question | Answer | | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Where should developers start with API delivery? | Start with [API overview](/developer-docs/api/overview), then [Authentication](/developer-docs/api/authentication), and [API Reference](/api-reference/introduction). | | When should we choose API instead of hosted channels? | Use API when your team needs full control over frontend behaviour across your own apps or services. | | How should we handle rate limits and failures? | Implement retries, validation checks, and logging as part of your production integration standards. | ## Related pages * [Getting Started](/getting-started/start-here) * [Platform overview](/product/platform-overview) # Web App (Digital Concierge) Source: https://docs.obvlo.com/onboarding-guides/hosted-onboarding-guide Plan rollout of Hosted (Digital Concierge). Use this guide to plan rollout of the hosted Web App, also referred to as Digital Concierge. ## What this product is Web App (Digital Concierge) is Obvlo's hosted guest-facing experience. It is delivered by URL or QR and does not require your team to host frontend infrastructure. ## Best use case Use Hosted (Digital Concierge) when you want a full guest-facing experience hosted and maintained by Obvlo — with no self-hosted infrastructure required. ## What to align before setup * Destinations included in launch scope * Distribution touchpoints (for example, shortlinks, QR codes, pre-arrival emails) * Personalisation expectations and guest journey goals * Internal owner for content approval and post-launch monitoring ## Rollout milestones Content should be reviewed and approved in the Portal before the experience goes live. Agree which channels will distribute the experience and who is accountable for each. Set a target go-live date and schedule a test window before opening to guests. Test the full guest journey and confirm who owns escalation if issues arise post-launch. ## Who does what * **Your team** owns rollout planning, approvals, and channel distribution. * **Obvlo** hosts the product and supports onboarding and readiness checks. ## Related FAQs | Question | Answer | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | How quickly can we launch Web App? | Web App is the fastest path for most teams as Obvlo hosts and maintains it. | | Do we need to host any frontend infrastructure? | No. Web App is hosted by Obvlo and delivered by URL or QR. | | Can we personalise the guest journey? | Yes. Web App can capture traveller context and adjust recommendation ranking. | | When should we choose Web App over Widget? | Choose Web App for a full hosted guest experience. Choose Widget to embed the experience in your existing website. | ## Related pages * [Getting Started](/getting-started/start-here) # Microsite (HTML Pages) Source: https://docs.obvlo.com/onboarding-guides/microsite-onboarding-guide Plan rollout of Microsite pages with clear launch ownership. Use this guide to plan rollout of HTML Pages (Microsite) with clear ownership across content, approvals, and launch. ## What this product is Microsite (HTML Pages) is Obvlo's search-focused page layer. Obvlo hosts the pages and they are served under your domain for destination discovery use cases. ## Best use case Use Microsite when SEO, GEO, and destination discoverability are core launch goals — putting your destination content in front of guests at the search and discovery stage. ## What to align before setup * Priority destinations and page scope for launch * Content readiness and approval ownership * Website and domain coordination with your technical owner * Success metrics across attract, engage, and convert stages ## Rollout milestones Agree which destinations and page types are included in launch scope. Identify who reviews pages and who gives final sign-off before launch. Ensure your domain and DNS coordination is assigned and scheduled. Run a pre-launch check and agree how performance will be tracked post-launch. ## Who does what * **Your team** owns scope, approvals, and launch decisions. * **Obvlo** supports onboarding, implementation guidance, and escalation. ## Technical setup For implementation and configuration detail, use the [Microsite overview](/microsite/overview). ## Related FAQs | Question | Answer | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | What is Microsite best used for? | Microsite is best when SEO, GEO, and destination discoverability are priorities. | | Do we need our own CMS to use Microsite? | No. Obvlo hosts and maintains Microsite pages. Your team manages content through Portal. | | Can Microsite pages sit under our own domain? | Yes. Microsite is served under your domain using reverse proxy. See [Reverse proxy setup](/microsite/reverse-proxy). | | Is the reverse proxy required? | Without it, the full SEO and domain authority benefit is not realised. See [Reverse proxy setup](/microsite/reverse-proxy). | | Does Microsite support multiple languages? | Yes. Pages are generated in all configured languages, each independently indexed. See the [Language reference](/reference/language-reference). | | How do we monitor Microsite performance? | Use Portal Reports and your analytics setup. See [Reporting](/onboarding-guides/portal-onboarding-guide#11-reporting). | ## Related pages * [Getting Started](/getting-started/start-here) # Portal (Dashboard) Source: https://docs.obvlo.com/onboarding-guides/portal-onboarding-guide How your team uses the Portal to review, manage, and publish destination content. Portal is Obvlo's management dashboard. Use it to review and manage content, configure delivery settings, and publish updates. Use this guide alongside [Getting Started](/getting-started/start-here) to understand where Portal actions fit in your overall onboarding. ## 1. Getting access ### Logging in Go to **[portal.obvlo.com](https://portal.obvlo.com/)** to access the Portal. When Obvlo invites you, you will receive a welcome email with your invite link. You can sign in in one of two ways: * **Google Sign-In** if your email is a Google Workspace account. Once invited, you can sign in with Google without setting a password first. * **Email and password** by clicking **Forgot Password**, entering your invited email address, following the reset email, and setting your password before signing in. Check your spam folder if the welcome email does not arrive within a few minutes. Invite links expire after 24 hours. If yours has expired, use **Forgot Password** to set your password and sign in. ### Access roles | Role | What it can do | | --------- | ---------------------------------------------------------------- | | Admin | Full access to content, settings, reporting, and user management | | Editor | Content review, editing, and publishing | | Developer | Technical and integration access where needed | Most users only need **Admin** or **Editor** access. ## 2. Getting oriented in the Portal When you first log in, you may land on the **Dashboard**. Your main workspace is elsewhere. Your primary navigation path is: 1. Go to **Destinations**. 2. Select your destination from the list. 3. Open **Review Content**. Most of your day-to-day work happens here. Other sections you will use depending on your product path: | Portal area | What you use it for | Applies to | | ------------------------- | ------------------------------------------------------------------ | ------------------------------------------------- | | **Review Content** | Browse, edit, and publish guides and listings | All product paths | | **Promos** | Add and update promotional cards on the homepage | Web App (Digital Concierge) | | **Pages** | Build and maintain handbook and guest information content | Web App (Digital Concierge) | | **Hosted & Widget** | Update delivery presentation settings such as cover image and logo | Web App (Digital Concierge) | | **Distribution** | Access tracked links and QR codes for sharing | Web App (Digital Concierge) | | **Reporting** | Review engagement once live | Web App (Digital Concierge) and Embeddable Widget | | **Organization Settings** | Add users and manage brand-level settings | All product paths | ## 3. Reviewing your content Review Content is your main workspace during onboarding. ### How to get there 1. Go to **Destinations**. 2. Select your destination. 3. Open **Review Content**. Inside Review Content you will use: * **Guide Catalogue** to see all curated guides. * **All Listings** to see all listings curated for the destination. Most teams start in **Guide Catalogue**, then move into **All Listings** when they want to refine individual places in more detail. ### What to review first * Guide names and descriptions * Which venues and places are included * Listing copy and practical details * Imagery * Any owned venues or outlets that should be prioritised or featured Most personalisation is already configured by Obvlo. You can still review guide-level settings if needed, but most teams do not need to rebuild personalisation from scratch. ## 4. Editing guides ### Opening the guide catalogue 1. Go to **Destinations** > your destination > **Review Content**. 2. Open **Guide Catalogue**. This shows all guides created for your destination. ### Editing a guide 1. In **Guide Catalogue**, find the guide you want to update. 2. Open the guide editor. 3. Update the **guide name**, **description**, or included listings. 4. Click **Save**. 5. Click **Publish** to make the change live. ### Viewing listings inside a guide From **Guide Catalogue**, open a guide to see the listings included in it. You can also edit individual listings from within the guide view. ### Featuring a guide Apply the **star** to a guide to feature it on the homepage. Featured guides stay visible regardless of a traveller's personalisation settings. Use featured guides sparingly. Feature the guides that matter most, not everything. ## 5. Adding a new guide 1. Go to **Destinations** > your destination > **Review Content**. 2. Click **Add Guide**. 3. Complete the **Guide Wizard** steps. 4. Click **Publish**, then **Save** to make it live. ### Importing a place not in the database Any place you import must exist as a business on Google Maps. The Portal uses that listing to generate copy, find images, and populate the listing automatically. Places without a Google business listing cannot be imported. Once added, the system generates content and imagery automatically. The status changes to **Published** when ready. ## 6. Editing listings Listings are the individual places inside your guides. Editing a listing updates it everywhere it appears. ### Viewing all listings 1. Go to **Destinations** > your destination > **Review Content**. 2. Click **All Listings**. ### Editing a listing 1. Open the listing from **All Listings**. 2. Edit the fields you want to update. 3. Click **Save**. 4. Click **Publish** to make the change live. This is most useful for owned venues or for listings where you want to fine-tune the description, website link, contact details, or other practical fields. ### Regenerating listing text If a listing is noticeably out of date, open it and go to **Advanced Options** > **Regenerate Text Attributes**. Obvlo runs scheduled updates to keep listings current, so this is rarely needed. ### Managing listing images Open a listing inside a guide to manage its images. Hover over an image to access removal options. ## 7. Updating handbook content Applies to: **Web App (Digital Concierge)**. Use **Pages** to manage the handbook and other guest information content that supports the live experience. ### How to get there 1. Go to **Destinations** > your destination. 2. Open **Pages**. ### Managing the handbook structure 1. Open **Manage Information Page**. 2. Go to **Manage Chapters**. This is where you build the structure guests see in the live experience: * **Chapters** are the main content blocks or page parts. * **Sections** sit inside each chapter and act as rows. * **Items** sit inside sections and hold the actual content, such as FAQs or practical guest information. ### Adding or editing content You can: * Start from a blank chapter using **Add Chapter**. * Add or reorder sections within a chapter. * Add individual items within each section. * Use the live preview to check the layout before publishing. Keep handbook content concise and practical so guests can find key information quickly. ## 8. Managing promos Applies to: **Web App (Digital Concierge)**. Promos help you highlight priority messages on the live experience, such as offers, events, or useful next actions. ### How to get there 1. Go to **Destinations** > your destination. 2. Open **Promos**. ### Adding a promo 1. Click **Add Promo**. 2. Complete the promo form. 3. Choose the promo type. 4. Review the content and settings. 5. Save the promo. | Promo type | Typical use | | ------------------ | -------------------------------------------------- | | Open Promo Listing | Show a full promo card with richer content and CTA | | Open Handbook | Send guests to handbook content | | Open URL | Send guests to an external link | You can also set start and end dates, add personalisation, and set time-of-day preferences where relevant. As a practical default, use no more than two promo rows at the same time so guides and destination content remain visible. ## 9. Web App (Digital Concierge) settings Applies to: **Web App (Digital Concierge)**. Use **Hosted & Widget** to manage presentation settings for the live experience. ### What this section is for This is where you update settings such as: * the **cover image** * the **logo treatment** * other visual settings already configured during setup ### What you are most likely to change In practice, the most common update is the hero or cover image shown on the Web App (Digital Concierge) home screen. Most branding is already configured by Obvlo, so teams rarely need to make wider visual changes themselves. ## 10. Distribution links Applies to: **Web App (Digital Concierge)**. Use Distribution to access and manage links for sharing the Web App (Digital Concierge) with guests. ### How to get there 1. Go to **Destinations** > your destination. 2. Open **Distribution**. ### What you will find here This section usually contains the tracked short links agreed during onboarding. These links: * let guests open the Web App (Digital Concierge) directly * support UTM tracking * help you understand where sessions are coming from You can copy these links into pre-arrival email templates, share them directly with guests, or use them to generate QR codes for physical touchpoints. Common channel examples include pre-stay email communications, check-in and reception touchpoints, in-room QR placements, and partner or concierge surfaces. ## 11. Reporting Applies to: **Web App (Digital Concierge)** and **Embeddable Widget**. Microsite reporting is coming soon. Reporting gives you a high-level view of usage once the experience is live. ### How to get there 1. Click **Reporting** in the left sidebar. 2. Open **Reports**. ### What reporting shows Available reports depend on your product path, but they typically include: * sessions and usage over time * language and location mix * time-of-day activity * channel sources * traveller interests or related engagement signals where supported Reporting only starts to populate after launch and real user activity. Empty reports immediately after setup are expected. ## 12. Organisation settings ### Adding team members 1. Go to **Organization Settings**. 2. Open **Users**. 3. Click **Add Permissions**. 4. Enter the new user's details and assign a role. They will receive a welcome email with their invite link. If they use Google Sign-In and have already been invited, they can sign in straight away. Otherwise, they can use **Forgot Password** to set their password. ### Brand settings Go to **Organization Settings** > **Brands** to update organisation-level settings such as colours. Most of this is pre-configured by Obvlo during setup. Update here if your branding changes after launch. ## FAQs **I have not received my login email.** Check your spam folder first. If the invite is still missing, go to [portal.obvlo.com](https://portal.obvlo.com/) and use **Forgot Password** with your invited email address to set your password and sign in. **The original invite link no longer works.** Invite links expire after 24 hours. Use **Forgot Password** to set your password and sign in. If your invited email supports Google Workspace, you can also use Google Sign-In once the invitation has been created. **Where do we manage content day to day?** Go to **Destinations** > your destination > **Review Content**. This is the main area for managing guides and listings. **Can we edit guide and listing content ourselves?** Yes. Use **Guide Catalogue** to edit guides and **All Listings** to edit individual listings. Save your changes, then publish them when ready. **Can we add a place that is not already in the database?** Yes, as long as the place exists as a business on Google Maps. The Portal uses that listing to generate copy, imagery, and listing data automatically. **What if generated listing copy is out of date?** Open the listing and go to **Advanced Options** > **Regenerate Text Attributes**. In most cases, manual editing is enough and regeneration is only needed when a listing is clearly out of date. **How do we update handbook content?** If you are using **Web App (Digital Concierge)**, go to **Destinations** > your destination > **Pages** > **Manage Information Page** > **Manage Chapters**. **How many promos should we run at once?** If you are using **Web App (Digital Concierge)**, keep it focused. As a practical default, use no more than two promo rows at the same time so guides and destination content stay visible. **Where do we change the cover image?** If you are using **Web App (Digital Concierge)**, go to **Destinations** > your destination > **Hosted & Widget**. This is where you manage the cover image and related visual settings. **Why is Reporting empty?** Reporting only starts to populate after launch and real user activity. At the moment, reporting is available for **Web App (Digital Concierge)** and **Embeddable Widget**. Microsite reporting is coming soon. **How do we add new team members?** Go to **Organization Settings** > **Users** > **Add Permissions**, then enter the user's details and assign the right role. **Where can we get help?** Contact your account manager or email **[support@obvlo.com](mailto:support@obvlo.com)**. ## Related FAQs | Question | Answer | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | I have not received my login email. | Check spam first. If still missing, use Forgot Password at [portal.obvlo.com](https://portal.obvlo.com/). | | The original invite link no longer works. | Invite links expire after 24 hours. Use Forgot Password to set your password and sign in. | | Where do we manage content day to day? | Go to Destinations > your destination > Review Content. | | How do I update the handbook? | Go to Destinations > your destination > Pages > Manage Information Page. See [section 7: Handbook](/onboarding-guides/portal-onboarding-guide#7-handbook). | | Why is Reporting empty? | Reporting only starts to populate after launch and real user activity. See [section 11: Reporting](/onboarding-guides/portal-onboarding-guide#11-reporting). | ## Related pages * [Getting Started](/getting-started/start-here) * [Web App (Digital Concierge)](/onboarding-guides/hosted-onboarding-guide) * [Embeddable Widget (Trip Planner)](/onboarding-guides/widget-onboarding-guide) * [Microsite (HTML Pages)](/onboarding-guides/microsite-onboarding-guide) # Embeddable Widget (Trip Planner) Source: https://docs.obvlo.com/onboarding-guides/widget-onboarding-guide Onboard Embeddable Widget rollout from planning through launch readiness. Use this guide to plan Embeddable Widget rollout with clear ownership across onboarding, implementation, and launch. ## Who should use this page * **Customer teams:** use this page for rollout planning, ownership, and launch readiness. * **Developers:** use [Widget quick start](/embed-widget/quick-start) for implementation and configuration. ## What this product is Embeddable Widget (Trip Planner) is the embeddable Obvlo experience inside your existing website. It keeps users on your site while they explore local recommendations. ## Best use case Use the Widget when you want Obvlo content inside your existing website without sending users to a separate experience. ## What to align before setup * Target pages for launch (for example, homepage or destination pages) * Destination scope and content readiness * Brand presentation expectations on the host site * Internal owner for website release and launch testing sign-off ## Rollout milestones Agree which pages will host the Widget and which destinations will be activated first. Content should be reviewed and approved before the Widget goes live. Identify who owns the technical setup and agree a target launch date. Validate the Widget across key devices and browsers before opening to guests. ## Who does what * **Your team** owns page placement, release planning, and sign-off. * **Obvlo** supports onboarding, implementation guidance, and escalation. ## Technical setup For implementation steps and configuration details, use the [Widget quick start](/embed-widget/quick-start). ## Related FAQs | Question | Answer | | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Can I customise the widget to fit my brand? | Yes. You can configure colours, images, logos, and supported fonts. See [Configuration reference](/embed-widget/configuration-reference). | | How do I ensure the widget is functioning correctly on my site? | Run an end-to-end check across key devices and browsers before go-live. | | How do personalised recommendations work? | Recommendations are ranked and filtered by traveller inputs (traveller type, interests, trip type), external factors (weather, time of day, time of year), and editorial priority — Featured guides rank above non-featured guides across all channels. | | Does the widget support analytics tracking? | Yes. Google Analytics is automatic. For other providers, add a JavaScript event listener. See [Analytics events](/embed-widget/quick-start#analytics-events). | | Where do I find my `host-id`? | In the Portal, go to Destinations. The value is listed under the Destination ID column. See [Widget quick start](/embed-widget/quick-start#required). | | Can I display a specific guide when the widget loads? | Yes. Use the `guide-id` or `neighbourhood-id` attribute. See [Configuration reference](/embed-widget/configuration-reference). | ## Related pages * [Getting Started](/getting-started/start-here) * [Widget quick start](/embed-widget/quick-start) # Content Engine Source: https://docs.obvlo.com/product/creation/content-engine Automated destination content creation and quality control. ## Overview Content Engine is the system that collects source data, creates destination content, checks quality, and keeps content up to date. Use case: All stages. Managed in Portal with no frontend build required. ## What customers can and cannot control * **Customers can control:** brand rules, template scope, destination setup, approval workflows, and publishing decisions in Portal. * **Customers cannot directly control:** internal crawl logic, generation engine internals, or AI Judge execution rules at runtime. ## Pipeline stages Collect and map source data into a shared content structure. Create editorial copy, imagery, and structured outputs by content type. Apply AI Judge checks against brand and use-case criteria. Automatically rerun failed content until it meets quality standards. Keep content current through scheduled updates and refresh cycles. ## Core capabilities Apply tone, voice, and visual rules across generated outputs. Define content scope, personalisation mapping, and source requirements. Generate locale-specific content across supported markets. Schedule and monitor creation jobs with queue and retry controls. Assess quality per output type and keep decision logs for review. Deliver guides, listings, itineraries, imagery, and related data outputs. ## Related * [Portal](/product/management/portal) * [Reports](/product/insights/reports) # API Source: https://docs.obvlo.com/product/delivery/api Direct access to destination content through API. ## Overview The API delivers destination content into your own platform and workflows. Integration level: High integration. Your team controls frontend and backend implementation while Obvlo maintains content generation and quality checks. ## What the API gives you Retrieve maintained destination data for guides, listings, locations, and host views. Use account-scoped credentials to securely call endpoints. Manage request flow, retries, and response handling in your own stack. Handle limits and errors with clear response patterns. ## Integration model Use Obvlo content in your own website, app, kiosk, or backend service. Content is maintained and assessed by Obvlo before it reaches API endpoints. Use API delivery when your team needs full control over interface and behaviour. ## Related * [Developers overview](/developer-docs/overview) * [Developer API Overview](/developer-docs/api/overview) * [Partner Integrations](/product/delivery/partner-integrations) * [Analytics](/product/insights/analytics) # Embeddable Widget Source: https://docs.obvlo.com/product/delivery/embeddable-widget Trip planning content embedded into your existing website. ## Overview The Embeddable Widget is a JavaScript component that adds destination content to your existing website so travellers can plan without leaving your domain. Integration level: Low integration. The widget is installed with a script in your page header and a dynamic content snippet where you want it to render. ## Setup flow Obvlo provides a code snippet to load the widget engine. Place the HTML block where the widget should appear on the page. Set card order, styles, filters, and interaction settings. Confirm branding, content quality, and responsive behaviour before launch. ## Key capabilities Configure layout, styling, and behaviour using embed attributes or Portal controls. Build the homepage from Header Banner, Menu, Grid, and List-Map cards. Support listing discovery with free-text search, marker clusters, and co-location handling. Ask preference questions and re-rank recommendations based on traveller responses. Launch as a side button that expands into a full widget overlay. Send interaction events into Google Analytics and related reporting workflows. ## Integration options Use full, nearby, or hidden mode based on your page goals. Hidden mode keeps data flows active without visible UI. Match widget colours, fonts, and card styles to your website. Theme options include light and dark. Track click-through, filters, and sorting behaviour to understand what guests use most. ## Related * [Portal](/product/management/portal) * [Reports](/product/insights/reports) * Other delivery options: [Web App](/product/delivery/web-app), [Microsite](/product/delivery/microsite), [API](/product/delivery/api), [Partner Integrations](/product/delivery/partner-integrations) # Microsite Source: https://docs.obvlo.com/product/delivery/microsite SEO, GEO, and AEO pages served on your domain. ## Overview Microsite, also known as HTML Pages, publishes destination content under your domain using reverse proxy for search-driven discovery. Integration level: Low integration. Obvlo hosts and maintains pages; your team manages content and publishing through Portal. ## Core capabilities Create destination-level pages that target geographic and informational intent. Publish thematic and area pages that support long-tail search coverage. Control branding, CSS, widget visibility, and page behaviour from Portal. Edit page sections manually or regenerate copy with AI-assisted workflows. Serve fully rendered HTML with technical SEO foundations in place. Track engagement and search visibility through reporting integrations. ## Search model Semantic HTML and server-rendered content help search engines crawl and index pages efficiently. Destination authority content supports citation potential in generative search responses. Structured sections and clear answers improve suitability for answer engine retrieval. ## Related * [Portal](/product/management/portal) * [Reports](/product/insights/reports) * Other delivery options: [Web App](/product/delivery/web-app), [Embeddable Widget](/product/delivery/embeddable-widget), [API](/product/delivery/api), [Partner Integrations](/product/delivery/partner-integrations) # Partner Integrations Source: https://docs.obvlo.com/product/delivery/partner-integrations Connect Obvlo with existing business systems. ## Overview Partner Integrations connect Obvlo to existing systems without a custom build for common workflows. Integration level: High integration with pre-built connectors and guided setup. ## Supported integration categories Use booking and stay data to support timed communications and guest journeys. Feed Obvlo content into your existing website and publishing workflows. Pass enriched traveller interaction data into customer profile systems. Let chatbot platforms query Obvlo content for destination-aware responses. ## Integration outcomes Reuse current systems while adding Obvlo content and signals where they are needed. Use connector-led setup to reduce implementation effort compared with custom point integrations. Delivery teams keep system control while Obvlo handles content quality and lifecycle. ## Related * [Portal](/product/management/portal) * [Reports](/product/insights/reports) * Other delivery options: [Web App](/product/delivery/web-app), [Embeddable Widget](/product/delivery/embeddable-widget), [Microsite](/product/delivery/microsite), [API](/product/delivery/api) # Web App Source: https://docs.obvlo.com/product/delivery/web-app Hosted Digital Concierge for fast launch. ## Overview Web App, also known as Digital Concierge, is Obvlo's hosted guest experience for pre-arrival and in-stay journeys. Integration level: Zero integration. Obvlo hosts and maintains the experience. ## Core capabilities Launch using tracked URLs and QR codes across email, print, and on-site touchpoints. Capture traveller preferences and rank content based on travel type, purpose, and interests. Show recommendations around the destination with search, clustering, and quick planning actions. Let guests save recommendations on-device without requiring account sign-up. Deliver practical destination and service information in a live format instead of static PDFs. Measure engagement, behaviour, and satisfaction through platform analytics. ## Journey coverage Share links before arrival so guests can plan around your destination content early. Support guest decisions during their stay with local recommendations, promos, and practical guidance. Use engagement data to refine content priorities, guide coverage, and distribution channels. ## Related * [Portal](/product/management/portal) * [Reports](/product/insights/reports) * Other delivery options: [Embeddable Widget](/product/delivery/embeddable-widget), [Microsite](/product/delivery/microsite), [API](/product/delivery/api), [Partner Integrations](/product/delivery/partner-integrations) # AI Visibility Health Check methodology Source: https://docs.obvlo.com/product/insights/ai-visibility-health-check-methodology How Obvlo measures AI visibility readiness, how the 0-100 score works, and how Agentic Browsing parity is tracked. The **AI Visibility Health Check** measures how ready a site is to be discovered, understood, cited, and used by search engines and AI assistants. It combines technical crawlability, structured machine-readable signals, content quality, and agent-readiness checks into a practical view that teams can act on. The methodology is designed around two separate outputs: | Output | What it means | | ----------------------------- | ------------------------------------------------------------------------- | | **Obvlo AI Visibility Score** | A weighted 0-100 readiness score focused on commercial AI visibility. | | **Agentic Browsing parity** | A separate pass-ratio style signal for applicable agent-readiness checks. | ## Overview The health check audits your domain and one representative page on it across **3 categories** and **23 individual check results**. Some checks are informational and do not affect the 0-100 score. | Category | Checks | Raw max | Displayed weight | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------: | ---------------: | | Infrastructure | SSL, Sitemap, Robots.txt, Indexability, AI Crawler Accessibility, PageSpeed Performance, Layout Stability, PageSpeed Accessibility, PageSpeed Best Practices, PageSpeed SEO | 45 | 25 | | AI readiness | Structured Data, FAQ Markup, WebMCP Schema Validity, llms.txt\*, Registered WebMCP Tools\*, Forms Missing Declarative WebMCP\* | 22 | 25 | | Page analysis | Semantic HTML, Content Structure, Content Depth, Content Freshness, Citation Readiness, Internal Linking, Accessibility for Agents | 68 | 50 | | **Total** | **23 check results** | **135** | **100** | \*These checks are informational or non-scoring. They can still appear in Agentic Browsing parity when the implementation marks the signal as applicable. ## How the score works Each scored check produces a raw score. Raw scores are summed within their category, then normalized to that category's displayed weight. This keeps the headline score on a stable 0-100 scale even as individual check weights change. The category weights are: | Category | Score weight | | -------------- | -----------: | | Infrastructure | 25 points | | AI readiness | 25 points | | Page analysis | 50 points | For example, an Infrastructure raw score of 36 out of 45 becomes `round((36 / 45) * 25) = 20` of the 25 Infrastructure points. The three normalized category scores are then added together and capped at 100. Individual check statuses usually follow these thresholds: | Status | Threshold | | ------ | ----------------------------------------- | | Pass | At least 90% of the check's max score | | Warn | At least 50%, but below pass | | Fail | Below 50% | | Info | Informational signal with no score impact | Some checks have explicit outcome-based statuses where the implementation needs to distinguish blocked, unavailable, or not-applicable results. ## How we choose the page to analyse You enter a domain, not a page URL. Infrastructure and AI readiness checks look at your domain, but page analysis needs one specific page — and your homepage is usually a poor subject for content depth, freshness, and citation readiness. To pick a page, Obvlo: 1. Looks for your sitemap at `/sitemap.xml`, `/sitemap_index.xml`, `/wp-sitemap.xml`, and any `Sitemap:` entry in your `robots.txt`. 2. Follows up to three child sitemaps when the sitemap is an index, preferring ones that look like post or article sitemaps. 3. Scores the URLs it finds and picks the strongest editorial page. Blog and article paths, `/YYYY/MM/` date segments, and descriptive slugs score higher. Listing pages, paginated archives, tag and author pages, and utility pages such as `/about` or `/pricing` are excluded. 4. Falls back to your homepage when no sitemap exists or the sitemap contains no article-like pages. When this happens the widget tells you, so a low content score is never read against a page you did not intend to test. The page Obvlo picked is always shown in the results header. Only pages on the domain you submitted are eligible. A sitemap that lists URLs on another host is ignored. ### The same page, however you start the check Selection works identically whether the check starts from the public AI readiness checker, from a domain you add in the Obvlo portal, or from a scan an admin runs on your behalf. Give any of them a bare domain and they pick the same page and report the same scores. Naming a specific page is the one thing that changes this: when you supply a URL with a path, Obvlo analyses that page and skips selection entirely. ## How we fetch pages Most checks need the analysed page's HTML. Obvlo uses a two-layer fetch model: 1. **Direct HTTPS fetch** with realistic browser headers. This mirrors the kind of request many search and AI crawlers send. 2. **Residential proxy fallback** when direct fetches are blocked by WAF or bot-protection systems such as Cloudflare or Sucuri. **Access problems are reported once.** If we had to use the fallback to reach your page, access is graded once. **A HEAD request is never used to decide you are blocked.** We may send a quick `HEAD` first as a hint, but a refusal there is only ever confirmed by attempting the real `GET`. Content delivery networks routinely refuse or mishandle `HEAD` while serving `GET` perfectly, and treating that as a block reported healthy pages as firewall-blocked. If both the direct request and the fallback fail, that is reported as an access problem, with a confidence level attached rather than as a flat certainty — and named for what actually refused us: bot protection, a rate limit, a certificate fault, or a geographic restriction are four different problems. Infrastructure checks can be cached briefly at domain level, while page-level analysis uses the selected entry URL so the result reflects the specific page being graded. ## Infrastructure Infrastructure checks verify whether crawlers and agents can reach, trust, render, and revisit the page reliably. ### 1. SSL certificate **Max score: 3** This check establishes a TLS connection to the domain on port 443 and reads the certificate expiry and issuer chain. | Outcome | Score | Status | | -------------------------------------------------------------------------------- | ----: | ------ | | Valid certificate, chain verifies, more than 7 days until expiry | 3 | Pass | | Valid certificate, fewer than 7 days until expiry | 2 | Warn | | Certificate valid but the chain does not verify (usually a missing intermediate) | 1 | Warn | | Certificate expired | 0 | Fail | | Connected over TLS but no certificate could be read | 0 | Fail | | No TLS handshake at all — the site is not served over HTTPS | 0 | Fail | **An unverifiable chain is not a missing certificate.** If your server does not send its intermediate certificate, browsers quietly fetch the missing piece themselves, so visitors see nothing wrong — but strict clients, including many crawlers, cannot verify the connection and fail. That is reported as a chain problem, naming the intermediate as the likely cause. The expiry warning fires inside **7 days**, not 14. Automated issuers renew around 30 days out, so a longer window flagged healthy auto-renewing setups. HTTPS is a prerequisite for modern search and AI crawler access. A missing, expired, or incomplete certificate can make a site unreachable to automated clients even when it appears to load in a human browser. Incomplete certificate chains matter because browsers often cache or fetch missing intermediate certificates automatically. Standard crawler clients often do not. The fix is to configure the server or CDN to present the full certificate chain. ### 2. Sitemap **Max score: 4** This check probes common sitemap locations, then falls back to sitemap directives inside `robots.txt`: * `/sitemap.xml` * `/sitemap_index.xml` * `/wp-sitemap.xml` * `Sitemap:` entries in `robots.txt` | Outcome | Score | Status | | -------------------------------- | ----: | ------ | | Valid XML sitemap found | 4 | Pass | | Blocked by WAF or bot protection | 0 | Warn | | No sitemap found | 0 | Fail | A sitemap gives crawlers a machine-readable index of the pages a site wants discovered. Without one, crawlers must rely on links alone, which can miss new, deep, or orphaned pages. ### 3. Robots.txt **Max score: 4** This check fetches `/robots.txt` and evaluates validity, sitemap references, blanket blocking, and AI crawler directives. | Signal | Score impact | | ------------------------------------------------------- | --------------: | | Valid robots.txt with a sitemap reference | +2 | | AI directives present and no search-mode bots blocked | +2 | | AI directives present but some search-mode bots blocked | +1 | | No AI-specific directives | +0 | | Blanket `Disallow: /` | Fails the check | Obvlo distinguishes search-mode bots from training-mode bots: | Category | Examples | Blocking impact | | ------------------ | ------------------------------------------------ | -------------------------------------------------------- | | Search-mode bots | OAI-SearchBot, PerplexityBot, Bingbot | Penalized because content may lose AI answer visibility. | | Training-mode bots | GPTBot, ChatGPT-User, Google-Extended, ClaudeBot | Treated as a legitimate content-protection choice. | Robots.txt is the gatekeeper for automated access. A forgotten staging rule or overbroad AI-blocking policy can quietly remove a site from AI discovery. ### 4. Indexability and snippet eligibility **Max score: 4** This check scans the entry page for directives that prevent indexing or prevent the page from being shown with snippets. It checks: * `` * `` * `X-Robots-Tag` response headers * `data-nosnippet` attributes in page content | Outcome | Score | Status | | ------------------------------------------------- | ----: | ------ | | Indexable, no snippet restrictions | 4 | Pass | | `data-nosnippet` present on some content | 3 | Warn | | Restrictive `max-snippet` from 1 to 49 characters | 2 | Warn | | `nosnippet` or `max-snippet:0` | 0 | Fail | | `noindex` or `none` | 0 | Fail | | WAF or SSL issue prevents verification | 0 | Warn | Google's AI features require pages to be indexed and eligible for snippets. These directives are easy to leave behind from staging builds, CMS defaults, or privacy settings, and they can cap visibility regardless of content quality. ### 5. AI crawler accessibility **Max score: 5** This check asks for your page three times, as three different kinds of client, and compares the answers: | Identity | What it tells us | | ------------------------------ | ------------------------------------------------------------------- | | An ordinary browser | Whether the page works for people at all | | A declared AI crawler (GPTBot) | Whether AI crawlers are being allowed or refused | | A plain script (`curl`) | Whether a refusal is aimed at AI specifically, or at all automation | Asking as only one client cannot tell those apart, and the distinction changes the answer completely. A site can refuse a generic scraper while welcoming GPTBot — because verified crawlers arrive from published IP ranges that many firewalls allowlist by default. A site can also serve browsers perfectly while refusing GPTBot **by name**, which is the case that actually costs you AI visibility. | Outcome | Score | Status | | --------------------------------------------------------------------- | ----: | ------ | | Served to browsers and to AI crawlers | 5 | Pass | | Our generic scraper refused, but the AI crawler served in full | 5 | Pass | | Automation refused, but search indexing shows crawlers do get through | 4 | Warn | | A JavaScript challenge stands between crawlers and the content | 3 | Warn | | All non-browser clients refused, AI crawlers included | 2 | Warn | | Access could not be established either way | 1 | Warn | | AI crawlers refused by name while other clients are served | 0 | Fail | | Nothing could retrieve the page | 0 | Fail | | Rate-limited during the scan | 3 | Info | **What this check can and cannot prove.** We hold GPTBot's user agent but not OpenAI's IP ranges. So a **successful** AI-crawler fetch is proof that your page is being served; a **refused** one is a strong signal, not a certainty, because the real crawler may still be allowlisted by address. Every result carries a confidence level — `confirmed`, `likely`, or `unverified` — and we never state that AI assistants cannot cite you, because assistants also cite pages from search indexes without fetching them directly. **Rate limiting is not a block.** If your server replies with HTTP 429 or 503 while we are scanning, that is about request throughput, not bot policy. It is reported as informational and does not count against you. **We check for a second opinion.** When a page looks blocked, we also check whether a major search engine already has it indexed. If it does, some crawler has plainly fetched it, so the block is not absolute and we cap the severity accordingly rather than overstating it. **We compare your stated policy with what actually happens.** If your `robots.txt` welcomes GPTBot but your firewall turns it away, the two disagree and you almost certainly did not intend that — so we say so explicitly, rather than telling you to go and review your firewall settings. ### 6. PageSpeed and layout stability **Max score: 25 total, split across 5 sub-checks** Obvlo runs the analysed page through the Google PageSpeed Insights API in mobile mode. Four Lighthouse categories are scored, and the Performance response also provides the Cumulative Layout Shift signal used for Layout Stability. | Sub-check | Source | Max score | | ------------------------ | ---------------------------------------- | --------: | | PageSpeed Performance | Lighthouse Performance score | 5 | | Layout Stability | Lighthouse Cumulative Layout Shift audit | 5 | | PageSpeed Accessibility | Lighthouse Accessibility score | 5 | | PageSpeed Best Practices | Lighthouse Best Practices score | 5 | | PageSpeed SEO | Lighthouse SEO score | 5 | For the four Lighthouse category scores, percentages are mapped to 0-5 points: | Lighthouse score | Mapped score | Status | | ---------------- | -----------: | ------ | | 90-100 | 5 | Pass | | 50-89 | 3-4 | Warn | | 20-49 | 1-2 | Fail | | 0-19 | 0 | Fail | If a PageSpeed category request fails, the check returns a warning result worth 2 out of 5 rather than failing the whole health check. Layout Stability maps the Lighthouse Cumulative Layout Shift audit score to 0-5 points. A stable layout helps agents click and type into intended elements reliably, because buttons and fields are less likely to move between detection and interaction. ## AI readiness AI readiness checks evaluate whether the page provides structured signals that help AI systems understand content, extract answers, and identify agent-usable actions. ### 7. Structured data **Max score: 12** This check extracts JSON-LD structured data from the homepage and the selected entry page. Schema types are grouped into site-level and page-level signals. | Group | Schema types scored | Purpose | | ---------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------- | | Site-level | Organization, LocalBusiness, Hotel, LodgingBusiness, WebSite, WebPage | Establishes entity identity. | | Page-level | Article, BlogPosting, NewsArticle, HowTo, FAQPage, BreadcrumbList, TouristDestination, Event, Product, Review | Describes the page content type. | | Condition | Score | | -------------------------- | ----: | | 2 or more site-level types | 6 | | 1 site-level type | 4 | | 2 or more page-level types | 6 | | 1 page-level type | 4 | | No structured data | 0 | Structured data is not a special prerequisite for Google's AI features, but it does help with rich results and entity understanding. It is especially useful for non-Google AI engines and travel or hospitality pages where business, location, property, event, review, and article entities need to be unambiguous. ### 8. FAQ markup **Max score: 6** This check looks for machine-readable FAQ schema and visible FAQ-style HTML. | Outcome | Score | Status | | ----------------------------------- | ----: | ------ | | `FAQPage` JSON-LD found | 6 | Pass | | FAQ-style HTML found without schema | 3 | Warn | | No FAQ content or schema | 0 | Fail | FAQ-formatted content gives AI assistants liftable question-and-answer units. Google has deprecated FAQ rich results for most sites, so this check has a moderate weight. The broader value is in clear, question-led content that can be extracted and cited. ### 9. llms.txt **Max score: 0** This check requests `/llms.txt` and, when present, also checks for `/llms-full.txt`. | Outcome | Score | Status | | ------------------------------------ | ----: | -------------------- | | `llms.txt` found | 0 | Info | | `llms.txt` and `llms-full.txt` found | 0 | Info | | `llms.txt` missing | 0 | Info, not applicable | | Retrieval blocked or server error | 0 | Warn | The file is treated as optional because evidence of broad crawler adoption is still mixed and Google does not require special AI files. Missing `llms.txt` does not reduce the AI Visibility Score. Retrieval failures can still produce a warning because they may indicate that machine-readable resources are not consistently reachable. ### 10. Registered WebMCP tools **Max score: 0** This informational check looks for WebMCP-style tool registration signals: * Declarative elements with a `toolname` attribute * Imperative calls to `document.modelContext.registerTool(...)` Registered tools help capable agents understand which site actions exist, but the signal is experimental and currently does not affect the 0-100 score. ### 11. Forms missing declarative WebMCP **Max score: 0** This informational check counts page forms and whether each form includes both: * `toolname` * `tooldescription` If the page has no forms, the signal is not applicable. If forms exist but lack complete declarative WebMCP metadata, the result explains how many are missing it. This does not affect the AI Visibility Score, but it helps teams identify where agent-readable form metadata could improve future interaction readiness. ### 12. WebMCP schema validity **Max score: 4** This scored check validates whether WebMCP-style form metadata is complete enough for agents to understand. | Outcome | Score | Status | | --------------------------------------------- | ----: | ------ | | Valid WebMCP tool forms found | 4 | Pass | | No forms found | 4 | Pass | | Plain forms found but no WebMCP tool metadata | 2 | Warn | | Metadata is usable but has warnings | 2 | Warn | | Blocking metadata issues found | 0 | Fail | | Page cannot be fetched for validation | 0 | Warn | Blocking issues include a tool form with `toolname` but no `tooldescription`, a tool form with `tooldescription` but no `toolname`, or user-supplied fields in a tool form without a `name` attribute. Warnings include named fields that lack a label or `toolparamdescription`. This check is scored because incomplete action metadata can make agent-operated forms unreliable. ## Page analysis Page analysis checks evaluate the actual content and HTML structure that determine whether AI systems can select, summarize, cite, and operate the page. Where possible, these checks focus on the primary content area rather than site chrome such as headers, navigation, and footers. ### 13. Semantic HTML and meta **Max score: 10** This check evaluates page structure and metadata signals. | Aspect | Max | What is checked | | ---------------------------- | --: | ---------------------------------------------------------------------------------------------------------------- | | Semantic elements | 3 | Presence of elements such as `
`, `
`, `
`, `