# 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-09-10 — API updates
* Updated API schema metadata and documentation details.
***
## 2026-08-14 — Destination-scoped languages
* Added optional query parameter `hostId` to `GET /v2/orgs/{orgId}/org-languages`, narrowing the response to one destination's target languages.
* Omitting `hostId` is unchanged behaviour: callers receive the organisation-wide language list. An unknown or out-of-scope `hostId` also returns the organisation-wide list rather than an empty one.
* Documented per-destination translation scope in [Translations](/product/management/translations).
***
## 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. |
### Optional parameters
| Parameter | Type | Description |
| --------- | ------ | ---------------------------------------------------------------------------- |
| `hostId` | string | Destination ID. Narrows the response to that destination's target languages. |
## Destination-scoped languages
An organisation can configure target languages **per destination** rather than one list for the whole organisation — see [Translations](/product/management/translations). Pass `hostId` when your integration operates in the context of a single destination, so a language switcher offers only the languages that destination is actually translated into.
| Request | Response |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| No `hostId` | The organisation-wide language list — the union of every in-scope destination's languages, plus the default language. |
| `hostId` of a destination with its own target languages | That destination's languages, plus the default language. |
| `hostId` of a destination with no per-destination languages configured | The organisation-wide list. |
| `hostId` that is unknown or outside the organisation's translation scope | The organisation-wide list. |
An unrecognised `hostId` falls back to the organisation-wide list rather than returning an empty one. An empty list is indistinguishable from a mistyped parameter, and would render a language switcher with nothing in it.
Omitting `hostId` is always safe: existing integrations keep receiving the organisation-wide list exactly as before.
## 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"
```
### Retrieve languages for one destination
```bash Retrieve languages for one destination theme={null}
curl "https://api.obvlo.com/v2/orgs/YOUR_ORG_ID/org-languages?hostId=YOUR_DESTINATION_ID&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. Narrowed to one destination's target languages when `hostId` is supplied. 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}
```
***
### `drawer-label`
**Type:** `string` — **Default:** translated "Plan your trip" label
Overrides the translated label shown on the drawer trigger button. Only applies when `drawer` is present. When omitted, the trigger falls back to the localized `drawer.plan_trip_label` translation.
```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. Values are uppercase — any other value fails config validation and the setting is ignored.
| Value | Description |
| ----------- | --------------------------------- |
| `DRIVING` | Car/vehicle travel times |
| `WALKING` | Walking distances and times |
| `BICYCLING` | 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}
```
#### `drawer-label`
**Type:** `string` — **Default:** translated "Plan your trip" label
Overrides the translated label shown on the drawer trigger button. 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).
# Deployment modes
Source: https://docs.obvlo.com/microsite/deployment-modes
The four ways Obvlo content reaches your domain, what each costs you to run, and why we recommend the reverse proxy to sites.obvlo.com.
Obvlo content always lives on **your** domain. What differs between modes is how it gets there, how quickly a change you publish becomes visible, and how much you have to operate yourself.
Every mode is set per domain, and every mode is reversible: switching does not migrate data, change your URLs, or invalidate anything you have already published.
**Start with the reverse proxy to `sites.obvlo.com`.** It is the mode we recommend for new and
existing sites, and the one we support best. Pages render on request behind a cache, so publishing is
live within seconds, new pages appear without a rebuild, and there is nothing to schedule or wait
for. If you run WordPress, the [WordPress plugin](/microsite/wordpress-plugin) is the same thing with
the configuration already done for you.
## The options
**Recommended.** Whole pages from `sites.obvlo.com`, through a proxy on your own server or CDN.
Publishing is live immediately.
The same reverse proxy, packaged as a plugin and pre-configured for your site. Nothing to
configure after activation.
One guide at a time, as an HTML fragment inside a page you already render yourself.
Whole pages built ahead of time and served from `content.obvlo.com`. The original mode, still
fully supported. [Switch to dynamic serving →](/microsite/ssr-proxy)
## Comparison
| | Reverse proxy | WordPress plugin | Embed | Prerendered |
| ---------------------------------- | ---------------------------------- | ------------------- | ------------------------------ | --------------------------------------- |
| **Recommended** | **Yes — default** | Yes, on WordPress | For one guide in your own page | Existing sites only |
| Origin host | `sites.obvlo.com` | `sites.obvlo.com` | `sites.obvlo.com` | `content.obvlo.com` |
| What Obvlo serves | Whole pages | Whole pages | A content fragment | Whole pages |
| Who renders the surrounding page | Obvlo | Obvlo | You | Obvlo |
| A published change is live | Within seconds | Within seconds | Within seconds | After a rebuild — 20–30 minutes or more |
| New pages appear | Automatically | Automatically | You add each placement | Only after the next rebuild |
| What you operate | A proxy rule on your server or CDN | A plugin | A snippet in your template | A proxy rule on your server or CDN |
| Setup effort | One config file | Upload and activate | Paste one snippet | One config file |
| Needs a rebuild to go live | No | No | No | Yes |
| Works without a build ever running | Yes | Yes | Yes | No |
## Pros and cons
Whole Obvlo pages served through a proxy rule you add to your own web server or CDN, fetching from
`sites.obvlo.com`.
**Pros**
* Publishing is live within seconds. There is no build to wait for and no window where the site and
the portal disagree.
* New pages are reachable as soon as they are published — you never have to remember to rebuild.
* All SEO authority accrues to your primary domain, and Obvlo renders the whole page, so structured
data, canonicals and hreflang are handled for you.
* Works on any stack: NGINX, Apache, IIS, Caddy, Cloudflare Workers, or most CDNs.
* Fully reversible. Repointing at `content.obvlo.com` is a two-line change.
**Cons**
* You need access to a web server or CDN configuration. If you cannot add a proxy rule, this mode
is not available to you.
* The `Host` header must match the origin. It is the most common setup error, which is why you
should paste the generated configuration rather than adapt an example.
* If you run your own CDN in front of Obvlo, its cache is yours to purge — a publish clears Obvlo's
cache, not yours.
[Set up a reverse proxy →](/microsite/reverse-proxy)
The same reverse proxy, delivered as a WordPress plugin with your site's origin, ids and mount path
already filled in.
**Pros**
* No server configuration at all. Upload, activate, done.
* Everything the reverse proxy gives you, including immediate publishing and new pages appearing on
their own.
* The download is generated for your site specifically, so there is nothing to substitute by hand
and nothing to get wrong.
* Deactivating the plugin hands the path straight back to WordPress.
**Cons**
* WordPress only. For any other stack, use the reverse proxy.
* Requests pass through PHP rather than your web server's proxy module, so it is marginally slower
than an NGINX or CDN rule on the same hardware.
* If you change the mount path in the portal, download the plugin again — the archive carries the
path it was built with.
[Install the WordPress plugin →](/microsite/wordpress-plugin)
A single guide returned as an HTML fragment and placed inside a page you build and render yourself.
**Pros**
* You keep complete control of the page: your header, footer, layout, navigation and analytics.
* No proxy and no server configuration — one snippet in your template.
* The server-side include variant puts the content in the HTML you send, so crawlers see it on the
first request.
**Cons**
* One guide per placement. You add each one, so it does not scale to a large or growing set of
pages the way the proxy modes do.
* You own the page, so canonicals, structured data and hreflang for it are yours to get right.
* Resolves against your published production content only.
[Use the Embed API →](/microsite/embed-api)
Whole pages built ahead of time and served as finished files from `content.obvlo.com`, through the
same kind of proxy rule.
**Pros**
* Serves static files, so response times are extremely consistent and there is no render in the
request path.
* Nothing renders on demand, so a traffic spike costs nothing extra.
* Well proven — this is how Obvlo microsites were originally delivered, and it remains fully
supported.
**Cons**
* A published change is not visible until the site is rebuilt and redeployed, which takes 20–30
minutes and longer for a large site.
* New pages do not exist until the next build, so publishing and going live are separate events you
have to track.
* The same proxy configuration burden as the reverse proxy, without the immediacy.
**On this mode the deploy is the publish, so the portal shows you no publish controls.** There is
no Publish button, no Preview or Production column and no publish action in the page menu,
because none of them could change what a visitor receives — only a rebuild can. Deploy the site
to make your saved changes live. Every other mode shows the publish controls, because on those
publishing is what puts a change in front of a visitor.
[Switch to dynamic serving →](/microsite/ssr-proxy)
## Where it mounts
The mode decides *how* pages are produced. A second, independent choice decides *where* they appear on
your domain — and it applies to the reverse proxy and prerendered modes alike.
| | Path on your main domain | Dedicated subdomain |
| ---------------------------------------- | ------------------------------ | ------------------------------ |
| Example | `yourdomain.com/travel-guides` | `guides.yourdomain.com` |
| **Recommended** | **Yes — default** | For pilots and trials |
| SEO authority | Accrues to your primary domain | Accrues to the subdomain |
| Touches your main site's configuration | Yes — one proxy rule | No |
| Needs its own DNS record and certificate | No | Yes |
| Your own pages on that hostname | Untouched | None — Obvlo serves everything |
| WordPress plugin available | Yes | No |
Obvlo serves one path, and every other route on the hostname stays exactly as it is.
**Pros**
* Link equity and page authority accrue to the domain you actually want to rank, which is the
single biggest reason to run a microsite at all.
* One URL structure for crawlers, one domain for analytics and attribution.
* Works with every mode, including the WordPress plugin.
**Cons**
* You need to add a rule to the server or CDN that serves your main site. In some organisations
that is the slowest part of the whole setup.
A hostname of its own — `guides.yourdomain.com` — where every address is an Obvlo page.
**Pros**
* Nothing on your main domain changes, and no existing route moves. Nothing you already serve can
break.
* The fastest way to get a microsite in front of real traffic: a DNS record, a certificate, and a
proxy rule on a host with nothing else on it.
* Ideal for a pilot, or where you cannot get a change made to the main site's configuration.
**Cons**
* Search engines treat a subdomain as related to but distinct from your primary domain, so
authority builds there instead of where you want it. This is the trade-off, and it is the reason
the path mount is the default recommendation.
* The WordPress plugin cannot do it — it proxies a path inside a WordPress site, and an install
with every route proxied has no home page left of its own.
* Moving to a path mount later means redirects, so it is cheaper to start on a path if you already
know the pilot will become permanent.
[Set up a subdomain mount →](/microsite/reverse-proxy#mounting-on-a-subdomain)
## Choosing
Three rules of thumb:
* **If Obvlo owns the whole page, use the reverse proxy to `sites.obvlo.com`** — or the WordPress
plugin, which is the same thing pre-configured. Choose prerendered only if you have a specific reason
to serve static files and can accept a rebuild before every change goes live.
* **If you own the page and want one guide inside it, use the Embed API** — and use its server-side
include rather than the client-side script if the guide is the reason the page should rank.
* **Mount on a path of your main domain unless you are piloting.** A dedicated subdomain is faster to
stand up and changes nothing you already serve, but the SEO authority accrues to the subdomain rather
than to your primary domain.
## What every mode shares
* **Your domain, your URLs.** Content is always served from a hostname you control, never from an Obvlo
one. SEO authority accrues to your primary domain in every mode — provided you mount on a path of it
rather than on a subdomain. See [Microsite overview](/microsite/overview) for why this matters.
* **The same content.** Modes change delivery, never what is published.
* **Multi-language support.** All configured languages are available in every mode.
* **Reversibility.** Switching is a configuration change on Obvlo's side plus, for the proxy modes, two
lines in your proxy configuration.
## Switching
Ask your account contact to change the mode for your domain. Then:
* **To the reverse proxy from prerendered** — repoint your proxy at `sites.obvlo.com` and update the
`Host` header to match. [Dynamic serving](/microsite/ssr-proxy) covers the exact change and how to
verify it.
* **To the WordPress plugin** — download it from the portal's **Integration** tab and activate it. You
can remove your old proxy rule afterwards.
* **To embed** — place the snippet from the **Integration** tab where you want the guide to appear. You
can leave your existing proxy in place while you try it.
The portal's **Integration** tab always generates configuration for the mode your domain is currently
in, so the copy you take from it is the copy that will work.
## Related pages
* [Microsite overview](/microsite/overview)
* [Reverse proxy](/microsite/reverse-proxy)
* [WordPress plugin](/microsite/wordpress-plugin)
* [Dynamic serving](/microsite/ssr-proxy)
* [Embed API](/microsite/embed-api)
# Embed API
Source: https://docs.obvlo.com/microsite/embed-api
Drop a single Obvlo guide into a page you already control, as an HTML fragment served from the Obvlo edge.
The Embed API returns one Obvlo page as an **HTML fragment** — no ``, no header, no footer — so you can place it inside a page your own site already renders. Use it when the surrounding page is yours and only the guide content comes from Obvlo.
If you want Obvlo to serve whole pages under your domain instead, use [Reverse proxy](/microsite/reverse-proxy) or [Dynamic serving](/microsite/ssr-proxy).
Embedding is enabled per domain by Obvlo. Ask your account contact to switch your domain to embed
mode — the portal's **Integration** tab then generates these snippets pre-filled with your own
organisation and site ids.
## Choose client-side or server-side
This is an SEO decision, not a preference.
| | Client-side script | Server-side include |
| ------------------- | ------------------------------------------------------ | ----------------------------------------------------------------- |
| How it arrives | The browser fetches the fragment after the page loads | Your server fetches the fragment and inlines it before responding |
| What a crawler sees | An empty container, unless the crawler runs JavaScript | The full guide, in the first response |
| Setup | Paste two tags into a template | A few lines in your server or application code |
| Use it when | The guide is supplementary content | **The guide is the reason the page should rank** |
Anything you want indexed should use the server-side include.
## Client-side script
Paste the container and the loader into your page template. The loader reads its configuration from the container's `data-obvlo-*` attributes.
```html theme={null}
```
| Attribute | Meaning |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `data-obvlo-org` | Your Obvlo organisation id. |
| `data-obvlo-site` | The Obvlo site the page belongs to. |
| `data-obvlo-path` | The page's path **within the Obvlo site** — the page hierarchy, not the URL on your domain. |
| `data-obvlo-base` | Optional. A path prefix on **your** domain. Set it and links inside the fragment stay on your site; omit it and they point back at Obvlo. |
| `data-obvlo-locale` | Optional. A configured language code. Defaults to the site's default language. |
Anything you put inside the container is a fallback: it stays on screen until the guide loads, and stays permanently if the fetch fails. The loader only ever writes on success, so a bad network never blanks the section.
## Server-side include
Your server fetches the fragment while rendering, so the guide is in the HTML the crawler receives.
```php theme={null}
['timeout' => 5, 'ignore_errors' => true],
]);
$obvlo_html = @file_get_contents($obvlo_url, false, $obvlo_ctx);
$obvlo_ok = isset($http_response_header[0])
&& strpos($http_response_header[0], ' 200 ') !== false;
echo ($obvlo_ok && $obvlo_html !== false) ? $obvlo_html : '';
?>
```
Three properties matter more than the language you write it in, and every version Obvlo generates has all three:
* **A timeout.** Obvlo must never hold up your own response.
* **A status check.** A 404 body is an error page, not content — never inline it.
* **An empty-string fallback.** The page renders without the guide rather than erroring.
The portal also generates NGINX `ssi` and Node versions of the same include. Open the **Integration** tab for your site and pick the flavour you need.
### The endpoint
```text theme={null}
https://sites.obvlo.com/api/embed/content?org=…&site=…&path=…&base=…
```
| Parameter | Required | Meaning |
| --------- | -------- | ------------------------------------------------------------------------ |
| `org` | Yes | Your Obvlo organisation id. |
| `site` | Yes | The Obvlo site id. |
| `path` | Yes | URL-encoded page path within the site, e.g. `%2Fparis`. |
| `base` | No | URL-encoded path prefix on your domain, so internal links stay with you. |
| `locale` | No | A configured language code. |
The response is an HTML fragment with a `text/html` content type. It carries no ``, ``, `` or `` — those stay yours.
The embed endpoint serves the **published** version of a page only. A page that has not been
published yet returns a 404, which your include should treat as "render without the guide".
## Styling
The fragment ships with the styles it needs, scoped so they do not leak into your page. Your own stylesheet still applies to the surrounding layout, so give the container the width and spacing you want the guide to occupy and let the fragment fill it.
## Troubleshooting
### The container stays empty
* Check the browser's network tab for the `/api/embed/content` request. A 404 means the `org`, `site` or `path` does not match a published page.
* Confirm `data-obvlo-path` is the path **inside the Obvlo site**, not the URL on your domain.
* Confirm the page is published, not just built.
### Links inside the guide leave my site
Set `data-obvlo-base` (or the `base` query parameter) to the path prefix where the guides live on your domain.
### The guide is not appearing in search results
You are almost certainly on the client-side script. Move to the server-side include — a crawler that does not execute JavaScript sees only your fallback.
### Check the endpoint directly
```bash theme={null}
curl -sS "https://sites.obvlo.com/api/embed/content?org={orgId}&site={siteId}&path=%2Fparis" | head
```
## Related pages
* [Deployment modes](/microsite/deployment-modes)
* [Reverse proxy](/microsite/reverse-proxy)
* [Dynamic serving](/microsite/ssr-proxy)
* [Microsite overview](/microsite/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.
### If a subdomain is the only option
A subdomain mount is supported, and it is the right call in one situation: getting a pilot in front of real traffic without waiting for a change to the server that runs your main site. Nothing on your primary domain is touched, and no existing route moves.
Be clear about what it costs. Authority accrues to `guides.yourdomain.com` rather than to `yourdomain.com`, so the compounding benefit described above does not reach the domain you want to rank. Treat it as a way to prove the content works, and plan to move to a path mount — which means redirects — if it becomes permanent.
See [Mounting on a subdomain](/microsite/reverse-proxy#mounting-on-a-subdomain) for the setup, and [Deployment modes](/microsite/deployment-modes#where-it-mounts) for the full comparison.
**The reverse proxy to `sites.obvlo.com` is the recommended setup.** See the
[Reverse proxy guide](/microsite/reverse-proxy) for configuration, or the
[WordPress plugin](/microsite/wordpress-plugin) if you run WordPress — it is the same proxy with
nothing to configure. [Deployment modes](/microsite/deployment-modes) compares every option and its
trade-offs.
## 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)
* [Deployment modes](/microsite/deployment-modes)
* [Reverse proxy](/microsite/reverse-proxy)
* [WordPress plugin](/microsite/wordpress-plugin)
* [Dynamic serving](/microsite/ssr-proxy)
* [Embed API](/microsite/embed-api)
* [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.
**This is the recommended way to run an Obvlo microsite.** The examples below point at
`sites.obvlo.com`, which renders each page on request, so anything you publish is live within
seconds and new pages need no rebuild.
Running WordPress? The [WordPress plugin](/microsite/wordpress-plugin) is this same proxy, packaged
and pre-configured, and needs none of the configuration on this page. If you only want a single
guide inside a page you already render, see the [Embed API](/microsite/embed-api).
[Deployment modes](/microsite/deployment-modes) compares every option.
A typical Obvlo content URL follows this pattern:
```text theme={null}
https://sites.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/
```
**If your domain is still on prerendered delivery**, the origin host is `content.obvlo.com` instead
— everything else on this page is identical, including the path after the host. Substitute the host
in both places each example names it: the URL it fetches, and the `Host` header it sends.
[Dynamic serving](/microsite/ssr-proxy) covers the switch and how to verify it.
Take your configuration from the portal's **Integration** tab rather than adapting an example by
hand. It generates against the origin your domain is actually on, so the copy you paste is the copy
that works.
## 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. `/local-guides/`) to your Obvlo content URL. You can also point a whole subdomain at Obvlo instead — see [Mounting on a subdomain](#mounting-on-a-subdomain).
**Host header** — the `Host` header sent to the Obvlo backend must be `sites.obvlo.com`. It always names the origin you fetch from, so on prerendered delivery both are `content.obvlo.com` instead — see the note above.
**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.
## Mounting on a subdomain
Every example on this page mounts Obvlo on a **path** of a domain that also serves your own content, which is the arrangement we recommend. The alternative is to give Obvlo a **whole subdomain** — say `guides.yourdomain.com` — where every address is an Obvlo page and there is nothing of yours to preserve.
This is the quickest way to get a microsite in front of real traffic, because it needs no change to the server that runs your main site. It is what we suggest for a pilot.
| | Path on your main domain | Dedicated subdomain |
| ---------------------------------------- | ------------------------------ | ------------------------------------------------ |
| Example | `yourdomain.com/travel-guides` | `guides.yourdomain.com` |
| SEO authority | Accrues to your primary domain | Accrues to the subdomain |
| Touches your main site's configuration | Yes — one proxy rule | No |
| Needs its own DNS record and certificate | No | Yes |
| WordPress plugin available | Yes | No — use a server proxy configuration |
| Recommended for | Production | Pilots, trials, and sites you cannot reconfigure |
**A subdomain does not consolidate SEO authority the way a path does.** Search engines treat a
subdomain as related to but distinct from your primary domain, so link equity and page authority
build up there rather than on the domain you actually care about. That is the whole reason the path
mount is the recommendation — see [SEO benefits](#seo-benefits) above.
Choose a subdomain when speed of setup matters more than compounding authority, and plan to move to a
path mount if the pilot becomes permanent. Moving later means redirects, so it costs more than
starting there.
### Setting it up
1. Create a DNS record for the subdomain pointing at the server or CDN that will run the proxy, and issue a certificate covering it.
2. In the portal's **Integration** tab, enter the subdomain on its own — `https://guides.yourdomain.com`, with no path after it.
3. Tick **This is a dedicated subdomain**. The portal will confirm that every address on that hostname will serve Obvlo content, including its home page.
4. Copy the generated configuration for your platform and apply it to that subdomain's virtual host.
Everything else on this page applies unchanged: the same origin, the same `Host` header, the same forwarded headers, the same path after the origin host. The only difference is what the proxy matches on — the whole host rather than one path beneath it — and the portal writes that for you, which is why there is no separate set of examples here to copy.
The [WordPress plugin](/microsite/wordpress-plugin) cannot serve a whole subdomain. It proxies one
path within a WordPress site, and a WordPress install with every route proxied has no home page of
its own left to serve. Use one of the server configurations below instead.
***
## 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 one route pointing at your Worker, matching your mount path with a trailing wildcard — for example `yourdomain.com/local-guides*`. Copy the exact pattern for your site from the portal's **Integration** tab rather than typing it.
**The trailing `*` is required.** Cloudflare matches a route pattern against the entire URL, query string included, so `yourdomain.com/local-guides` without the wildcard matches that one exact URL and nothing else. Every page beneath the mount — and the mount itself as soon as a visitor arrives with a `?utm_source=…` on the end — would never reach the Worker, and would be served by your own origin instead. One route with the wildcard covers the whole mount; a pair like `yourdomain.com/local-guides` plus `yourdomain.com/local-guides/*` does not, because the first half still carries no wildcard.
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://sites.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live";
const PROXY_PATH = "/local-guides";
/**
* Whether this request belongs to the Obvlo mount rather than to the rest of the site.
*
* The route this Worker is bound to has to end in `*`, because Cloudflare matches a route pattern
* against the whole URL including the query string — without the wildcard, `/local-guides?utm_source=x`
* never reaches the Worker at all. That same wildcard also matches `/local-guides-elsewhere`, which is
* not the mount, so the boundary is checked here where it can be exact rather than left to a route
* pattern that cannot express it. An empty PROXY_PATH is a whole-host mount, where every path is ours.
*/
function isMounted(pathname) {
if (PROXY_PATH === "") return true;
return pathname === PROXY_PATH || pathname.startsWith(PROXY_PATH + "/");
}
/** Move a root-relative URL inside the mount, so an absolute asset path still resolves. */
function mounted(value) {
return value.startsWith("/") && !value.startsWith("//") ? PROXY_PATH + value : value;
}
export default {
async fetch(request) {
const url = new URL(request.url);
if (!isMounted(url.pathname)) {
return fetch(request);
}
// Sliced rather than replaced: the prefix is known to be at the front, and a `replace` would also
// rewrite a later occurrence of the same string inside a page slug.
const targetPath = url.pathname.slice(PROXY_PATH.length) || "/";
const originUrl = `${ORIGIN_BASE}${targetPath}${url.search}`;
const headers = new Headers(request.headers);
headers.set("Host", "sites.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,
body: request.body,
redirect: "follow",
});
// Re-instantiated so the headers are mutable, before either branch below returns it.
const proxied = new Response(response.body, response);
proxied.headers.delete("x-served-by");
proxied.headers.delete("x-cache");
proxied.headers.delete("x-cache-hit");
proxied.headers.delete("x-cloud-trace-context");
const contentType = proxied.headers.get("Content-Type") || "";
if (!contentType.includes("text/html")) {
return proxied;
}
return new HTMLRewriter()
.on("link, script, img, source, a", {
element(element) {
for (const attribute of ["href", "src"]) {
const value = element.getAttribute(attribute);
if (value !== null) element.setAttribute(attribute, mounted(value));
}
// `srcset` is a comma-separated candidate list, so each URL in it is moved on its own.
// Prefixing the attribute whole rewrites the first candidate and corrupts every other.
const srcset = element.getAttribute("srcset");
if (srcset !== null) {
const candidates = srcset.split(",").map((candidate) => {
const parts = candidate.trim().split(/\s+/);
parts[0] = mounted(parts[0]);
return parts.join(" ");
});
element.setAttribute("srcset", candidates.join(", "));
}
},
})
.transform(proxied);
} 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.
Mount Obvlo content at a **path**, never at your domain root. Every example below uses
`/local-guides` — substitute your own path. A root mount (`location /`, `ProxyPass "/"`) sends your
home page and every existing route to Obvlo, and the Obvlo portal will not generate a configuration
for it.
### NGINX
Add the following to your server block. Replace `/local-guides` with the path on your domain where the content should live, and the placeholder ids 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 /local-guides/ {
proxy_pass https://sites.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/;
proxy_set_header Host sites.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 `/local-guides` prefix and appends the remainder to the backend URL. The `Host` header must be `sites.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 "/local-guides/" "https://sites.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/"
ProxyPassReverse "/local-guides/" "https://sites.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/"
ProxyPreserveHost Off
RequestHeader set Host "sites.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 `sites.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 /local-guides/* https://sites.obvlo.com/live/orgs/{orgId}/sites/{siteId}/live/ {
header_up Host sites.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 `sites.obvlo.com` — this is the most common configuration error. It must match the origin the same rule fetches from, so use `content.obvlo.com` in both places if you are on prerendered delivery.
* Confirm the full Obvlo content URL is correct, including the trailing path.
* Test the origin URL directly, substituting your own origin host:
```bash theme={null}
curl -I https://sites.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.
### The mount loads but every page under it returns your own 404
A route pattern with no trailing wildcard. It looks nothing like a routing fault from the outside, because the mount root itself works.
* Check **Workers Routes** for your zone. A pattern such as `yourdomain.com/local-guides` matches that one exact URL; `yourdomain.com/local-guides*` matches the mount and everything beneath it. Add the `*`.
* The give-away is that the mount root works while `/local-guides/any-page/` does not — and that appending `?x=1` to the working URL breaks that too, because Cloudflare matches the query string as part of the URL.
* The 404 you see is your own origin's. An unmatched route is not an error: Cloudflare simply does not route to the Worker, so your origin answers instead, and nothing is logged on either side.
### 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)
* [Deployment modes](/microsite/deployment-modes)
* [Dynamic serving](/microsite/ssr-proxy)
* [Embed API](/microsite/embed-api)
* [Microsite overview](/microsite/overview)
* [Language reference](/reference/language-reference)
# Dynamic serving
Source: https://docs.obvlo.com/microsite/ssr-proxy
Serve Obvlo pages through your reverse proxy from a dynamic origin, so a publish is live immediately with no site rebuild.
Dynamic serving is what the recommended reverse proxy fetches from. If your domain is still on prerendered delivery, this page is the switch: it keeps the reverse proxy you already run and changes only where it fetches from. Instead of prerendered files in Obvlo's content CDN, pages are rendered on request behind a cache — so publishing a page makes it live straight away, with no site rebuild.
Everything else stays the same: the same URL structure on your domain, the same proxy configuration shape, the same rollback path.
**This is the mode we recommend for every site.** If you are setting up for the first time, go
straight to [Reverse proxy](/microsite/reverse-proxy) — its examples already point at
`sites.obvlo.com`, and on WordPress the [plugin](/microsite/wordpress-plugin) does it for you. Read on
if you are already serving prerendered files and want to move.
Dynamic serving is enabled per domain by Obvlo. Ask your account contact to switch your domain
over — the portal's **Integration** tab then generates your proxy configuration against the right
origin automatically, and it is the copy you should paste.
## What changes
| | Prerendered | Dynamic (recommended) |
| -------------------------- | ---------------------------------------- | -------------------------------------------- |
| Origin host | `content.obvlo.com` | `sites.obvlo.com` |
| A published change appears | After the site is rebuilt and redeployed | Immediately |
| New pages | Need a rebuild | Available as soon as they are published |
| Your proxy configuration | Two lines name the origin | The same two lines, pointing at the new host |
| A build must have run | Yes — nothing serves until one has | No |
Only the origin host and the `Host` header change. The path after the host is identical, so a switch is two edits in one file and reverses the same way.
The `Host` header must match the origin you are fetching from. On dynamic serving that is
`sites.obvlo.com`, **not** `content.obvlo.com`. Sending the old value is the most common
configuration error when switching, and it presents as every page 404ing.
## Getting your configuration
Take the generated snippet rather than editing your existing one by hand:
1. Open your site in the Obvlo portal and go to the **Integration** tab.
2. Confirm the URL on your domain where the guides live, e.g. `https://yourdomain.com/local-guides`.
3. Copy the snippet for your platform — Cloudflare Workers, NGINX, Apache, IIS or Caddy.
4. Replace your existing proxy configuration with it and reload your proxy.
The generated snippet already names the correct origin, the correct `Host` header and the correct deployment path for your site. The [Reverse proxy](/microsite/reverse-proxy) guide explains what each directive in it does and covers prerequisites, SSL and troubleshooting — all of which apply unchanged here.
## Caching and freshness
Pages are served from a cache in front of the renderer, so a repeat visitor gets a cached response rather than a fresh render.
* **Publishing a page clears that page from the cache.** Siblings keep their cached copies, so a small edit does not cost a full re-render of your site.
* **A stale copy may be served briefly while a fresh one renders in the background.** This is deliberate — it keeps a slow first render off a visitor's request.
* **If you run your own CDN in front of Obvlo** (for example Cloudflare), that cache is yours to purge. Obvlo cannot clear it, so a publish may take up to an hour to appear through it unless you purge as well.
## Verifying a switch
Check the pages, not the home page — and check more than one.
1. Load a guide URL on your domain and confirm the content is current.
2. Publish a small change to that page in the portal and reload. It should appear within seconds.
3. Load a second guide that you did **not** change and confirm it still works.
If pages 404 after switching, re-check the `Host` header first.
## Rolling back
Point the same two lines back at `content.obvlo.com` and reload your proxy. Prerendered files are still in place, so there is no data migration and no waiting — this is why the switch is safe to try.
## Related pages
* [Deployment modes](/microsite/deployment-modes)
* [Reverse proxy](/microsite/reverse-proxy)
* [Embed API](/microsite/embed-api)
* [Microsite overview](/microsite/overview)
# WordPress plugin
Source: https://docs.obvlo.com/microsite/wordpress-plugin
Serve Obvlo guides under your own domain on WordPress. Download the plugin from the portal, upload it, activate it — no web server configuration, provided your server allows .htaccess rewrites.
The Obvlo Microsite plugin serves your guides under your own domain — `yourhotel.com/local-guides/paris` — so the SEO value accrues to your site. It is the same reverse proxy described in [Reverse proxy](/microsite/reverse-proxy), packaged as a plugin. On any server that allows `.htaccess` rewrites — which is most of them, and all the common managed WordPress hosts — there is **no server config to edit, no shell access and no restart**: the plugin sets up its own path and then checks that it really works. Where a server does not allow them, no plugin can serve these URLs, and the [one requirement](#before-you-start-one-server-requirement) below says how to tell.
If you run WordPress, this is the mode we recommend. You get everything the reverse proxy gives you —
publishing live within seconds, new pages appearing on their own — without touching a server config
file. [Deployment modes](/microsite/deployment-modes) compares it against the alternatives.
## Before you start: one server requirement
The plugin needs your server to let `.htaccess` files set up rewrites. On Apache that means two things:
`mod_rewrite` enabled, and `AllowOverride` set to allow it — normally `AllowOverride All`.
**If `AllowOverride` is `None`, the plugin cannot work.** Apache then never reads `.htaccess` at all,
so the rule the plugin writes is ignored and your guides return a 404. This is not something the
plugin, or any WordPress plugin, can work around from inside WordPress — the request is refused before
PHP ever runs.
**You almost certainly already meet this requirement.** It is the same thing that makes WordPress's
pretty permalinks work, so there is a one-second check: if your posts have URLs like
`yoursite.com/my-post/`, your server allows `.htaccess` rewrites and the plugin will work. If your posts
look like `yoursite.com/?p=123` or `yoursite.com/index.php/my-post/`, they do not — fix that first, and
see [When guide URLs stay unreachable](#when-guide-urls-stay-unreachable).
## Install
Open your site in the Obvlo portal, go to the **Integration** tab, choose **WordPress plugin** and
download the archive.
It arrives **already configured for your site**. There is no org id, site id or origin to type in,
and nothing to substitute by hand.
In WordPress, go to **Plugins → Add New → Upload Plugin**, choose the `.zip`, install, then
activate.
Go to **Settings → Obvlo Microsite**. **Guide URLs reachable** should say *Yes* — the plugin checks
this by requesting one of your guide URLs, so it is reporting what a visitor would actually get.
That is the whole installation. You do not need shell access, and nothing outside your own site
folder is touched.
Open the path the portal generated the plugin for, e.g. `https://yourhotel.com/local-guides/`. Your
Obvlo content loads on your own domain.
**If your guides return a 404, the settings screen will tell you why.** The row **Guide URLs
reachable** is checked by actually requesting one, not by guessing — so it reflects what a visitor
would get. If it says no, use the **Add the rule and re-check** button on that screen.
## How the guide path reaches the plugin
Your web server has to hand `/local-guides/paris` to WordPress before the plugin can answer it.
Otherwise the server looks for a file or folder of that name, does not find one, and returns its own 404
without ever loading WordPress.
On most sites WordPress's own rule already does this — it is the same rule that makes your post URLs
work. Where it does not, the plugin adds one line of its own to the `.htaccess` file in your site
folder, claiming **only** your guide path:
```apache theme={null}
# BEGIN Obvlo Microsite
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^local\-guides(/.*)?$ /index.php [L]
# END Obvlo Microsite
```
Nothing else on your site is affected: your post URLs, your permalink setting and any other rules in
that file are left exactly as they were, and the block is removed again if you deactivate the plugin.
A guide that does not exist gets **your own theme's 404 page**, on your own domain.
## When guide URLs stay unreachable
If **Guide URLs reachable** still says no after the plugin has written its rule, the rule is being
ignored. The plugin has done everything it can from inside WordPress; what is left is a server setting.
**How to confirm it.** Your guide path returns a plain Apache 404 — a small unstyled "Not Found" page —
while `yoursite.com/index.php/local-guides/` reaches WordPress. That combination means PHP and the
plugin are both healthy and only the rewrite is failing.
**On Apache**, ask your host (or run, if you have shell access):
```bash theme={null}
apache2ctl -M | grep rewrite
grep -rn "AllowOverride" /etc/apache2/apache2.conf /etc/apache2/sites-enabled/
```
The fix is `AllowOverride All` on the directory that holds your site, then a reload:
```bash theme={null}
apache2ctl configtest && systemctl reload apache2
```
**Check the HTTPS site specifically.** `AllowOverride` is set per virtual host, and a common setup has
it correct on the plain-HTTP site while the HTTPS one — added later by an SSL certificate tool — never
received it. Because HTTP redirects straight to HTTPS, every real visit uses the site that does *not*
allow overrides, and the plugin's rule is ignored even though `AllowOverride All` appears in your
config. Make sure it applies to the `:443` virtual host, not only `:80`.
On **NGINX or IIS** the `.htaccess` file does nothing at all, so the plugin does not write one; your
host needs to route the guide path to WordPress.
If your host will not change any of this, [Reverse proxy](/microsite/reverse-proxy) and
[Embed API](/microsite/embed-api) do not depend on `.htaccess`.
## What your visitors get if Obvlo is unavailable
The plugin serves a cached copy rather than an error, and never lets a slow response affect the rest of
your site.
| Situation | What a visitor gets |
| -------------------------------------------- | ---------------------------------------------------------- |
| Page cached and fresh (under 5 minutes) | The cached page. No request leaves your server. |
| Cache stale, Obvlo healthy | A freshly fetched page, refreshed during that request. |
| Obvlo slow (over 4 seconds) or unreachable | The last known-good copy of that page, kept for 24 hours. |
| Obvlo failing repeatedly | The same, and Obvlo is not called again for a minute. |
| Never-visited page **and** Obvlo unreachable | A plain "temporarily unavailable" page. Never a PHP error. |
Two of those numbers are deliberate. The request to Obvlo is abandoned after **4 seconds**, so one slow
page cannot occupy a PHP worker long enough to starve the rest of your site. After **3 consecutive
failures** Obvlo is not called again for a minute, so a sustained outage costs microseconds per request
rather than four seconds.
## Diagnosing a page
Every response carries two headers:
```bash theme={null}
curl -sI https://yourhotel.com/local-guides/ | grep -i x-obvlo
```
* `X-Obvlo-Cache` is one of `hit`, `miss`, `stale`, `breaker-open`, `error` or `disabled`.
* `X-Obvlo-Ref` is a short id for the page. Quote it to Obvlo support.
## Changing the mount path
The path is baked into the archive when you download it. If you change the URL on your domain in the
portal, **download the plugin again** and upload the new version — the old archive still carries the old
path.
Alternatively, correct it on the settings screen. Those edits are stored as overrides in WordPress
options and survive a re-upload, so a locally corrected path is not clobbered by a fresh download.
## Updates
Obvlo distributes this plugin, not wordpress.org, so WordPress will never offer you an update for it.
The plugin checks Obvlo once a day and shows an admin notice when a newer version exists, or a warning
when your copy is too old for the current Obvlo service. **Downloading a fresh archive from the portal
is the update.** Your settings survive it.
## Compatibility
| | Supported |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| WordPress | 6.0 and later |
| PHP | 7.4 and later, including 8.0–8.4 |
| Multisite | Yes, per site — activate on the site whose domain serves the guides |
| Permalinks | Any setting except *Plain* |
| Outbound HTTP | cURL preferred; PHP streams used if cURL is disabled |
| Object cache | Redis or Memcached preferred; transients in the database otherwise |
| Page caches | WP Rocket, LiteSpeed, Cloudflare APO and similar are compatible. Obvlo pages carry their own `Cache-Control`, and a stale or error response is sent `no-store` so an outage is never cached. |
## Removing it
Deactivating changes nothing else on your site — it simply stops serving the guides, and the path
returns to WordPress. Deleting the plugin also removes its settings and its cached pages.
## Not on WordPress?
The same archive is available as a generic PHP proxy for a single-server PHP stack, and every other
platform is covered by a configuration file you paste in. See [Reverse proxy](/microsite/reverse-proxy).
## Related pages
* [Deployment modes](/microsite/deployment-modes)
* [Reverse proxy](/microsite/reverse-proxy)
* [Dynamic serving](/microsite/ssr-proxy)
* [Embed API](/microsite/embed-api)
* [Microsite overview](/microsite/overview)
# 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**. For Microsite performance, use the [Measure dashboard](/product/insights/ai-search-visibility-measure) in AI Visibility.
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.
### Managing pending invitations
The **Users** list shows people you have invited who have not signed in yet, alongside your active users, so you can see at a glance who is still outstanding.
For any pending invitation you can:
* **Resend** the invitation email.
* **Copy the registration link** and send it yourself — useful when the invite email is being filtered or the original link has expired.
* **Revoke** the invitation if it was sent in error or the person no longer needs access.
Once someone completes registration they move from pending to active in the same list.
### 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. An Admin can also resend the invitation, or copy the registration link and send it to you directly, from **Organization Settings** > **Users**.
**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. Reporting covers **Web App (Digital Concierge)** and **Embeddable Widget**. Microsite performance is tracked separately in the [Measure dashboard](/product/insights/ai-search-visibility-measure).
**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. Invitations that have not been accepted yet appear in the same list, where you can resend, copy the registration link, or revoke them.
**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.
## Draft and publish workflow
Changes you save in the Portal page editor stay in the draft preview. They do not change the public page.
To make saved changes live, open **Publish** in the page editor and choose **Publish draft live**. The
Deployment settings show the available publishing actions for your site and environment.
On [prerendered delivery](/microsite/deployment-modes) there are no publish controls at all — the deploy
is the publish, so you rebuild the site instead. Each channel reports its own freshness, so publishing to
Preview says nothing about what Production is serving.
## 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)
# Measure — AI Search Visibility
Source: https://docs.obvlo.com/product/insights/ai-search-visibility-measure
The Measure dashboard: a weekly health tracker for your AI search visibility across Visibility, Discoverability, Engagement, and Monetisation.
The **Measure** dashboard is the post-deploy view of your AI Search Visibility
workspace. It replaces the standalone Microsite Health Tracker spreadsheet with a
live, weekly-updated metric tree so you can see how your optimised pages are
performing without maintaining a spreadsheet by hand.
Measure is part of the AI Visibility workspace, available to organisations with
AI Visibility enabled. Open it from **Organization** > **AI Visibility** > your
domain > **Track & Measure**.
## How Measure is organised
Measure has three tabs:
| Tab | What it shows |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Overview** | The weekly headline, the four area scores, key signals, and data-source health. Before your first data arrives this tab is labelled **Get started**. |
| **Area tracker** | The full metric tree for one area at a time — Visibility, Discoverability, Engagement, or Monetisation. |
| **Details** | Page-level and query-level detail behind the numbers. |
## The four areas
Every metric belongs to one of four areas, each rolling up from raw metrics (L2)
into weighted composite scores (L1) and a top-level page count (L0):
| Area | What it tells you | Powered by |
| ------------------- | ------------------------------------------------------------------ | --------------------------------------------------------- |
| **Visibility** | How much of your site is indexed and ranking, and how fresh it is | Google Search Console, ranking checks, page publish dates |
| **Discoverability** | Search clicks, impressions, position, and AI-answer citation rates | Google Search Console, AI citation checks |
| **Engagement** | Sessions, engagement time, bounce rate, and audience reach | Google Analytics 4 |
| **Monetisation** | Revenue, purchasers, and purchases attributed to your pages | Google Analytics 4 |
A **Headline** metric — *Microsite Ready Pages* — sits above the four areas as
your at-a-glance progress number.
## Refresh your data on demand
Measure normally updates on a schedule — each source pulls fresh data once a day,
and the weekly metric tree is rebuilt every Monday. If you don't want to wait for
the next cycle, admins can pull and recompute right away:
* **Sync now** (per source) — open a source from its status pill and click
**Sync now** to pull the latest Search Console or Analytics data immediately.
* **Refresh metrics** — the **Refresh metrics** button in the Measure header
re-runs the weekly aggregation for the current week using whatever data has been
captured, so the tiles move without waiting for Monday.
A **Sync now** also recomputes the current week automatically, so the numbers
update in one step. To avoid hammering the underlying APIs, an on-demand sync is
limited to **once per source every 15 minutes**, and **Refresh metrics** to once
a minute.
**Sync now** and **Refresh metrics** are available to **Admins** and above.
## Weekly dashboard summary
On the **Overview** tab you'll see:
* The **Headline** value and its week-over-week trend.
* The **four area page counts** (how many pages are visible, discoverable,
engaged, and monetised).
* Up to two **key signals** — the biggest movers of the week, good or bad.
* An overall **data-quality** indicator and a *“Data current to …”* note so you
always know how fresh the numbers are.
## Metric detail
On the **Area tracker** tab, open any area to expand its metrics. Each metric row
shows:
* The current weekly value and its unit.
* **Target vs. actual** and the variance, when you've set a target.
* A **trend** chip. Trends are polarity-aware — for metrics where lower is better
(bounce rate, average position, page age) a decrease is shown as an
improvement.
* A **weekly sparkline** of the recent trend.
* A **data-quality badge** — *Good*, *Approximate*, or *Pending* — and a marker
showing whether the value was measured automatically or entered manually.
Metrics with no data for the week show an explicit *no data* state — never a
misleading zero.
## Page and query detail
The **Details** tab breaks the weekly numbers down to individual pages and
search queries. It has up to two sub-tabs, shown once the underlying data exists:
### Existing vs microsite
Compares your Obvlo-built pages against your own existing pages, so you can see
what the optimised pages are actually adding. It shows, per cohort:
* **Winning targets** and **cited targets** — how many of your strategy targets
are ranking, and how many are being cited in AI answers.
* **Median best rank** and **citation rate**.
* A **microsite pages** table listing each deployed page with its score, target
count, and deployment date.
### Search Console
Available once Google Search Console is connected. It shows:
* **Microsite cohort** — which URL paths count as "the microsite" for Analytics
and Search Console attribution.
* **Indexed pages** — a weekly count of indexed microsite pages.
* **Page performance** — impressions, clicks, CTR, and position per page.
* **Post-deploy position trend** — how rankings have moved since deployment.
* **Untargeted queries** — queries driving impressions to your deployed pages
that aren't in your strategy yet. Each one can be added to your strategy
directly from this table.
Search Console rows appear after the first daily sync, so this sub-tab is empty
immediately after connecting.
## Connecting your data sources
Measure draws on **Google Search Console** (Visibility and Discoverability) and
**Google Analytics 4** (Engagement and Monetisation). Both are connected from
your domain's settings, and both are **read-only** — Obvlo never requests write
access.
To connect Google Analytics on a **Pro** plan, click **Connect Google
Analytics**:
1. Grant Obvlo read-only access to your Analytics data.
2. Pick the GA4 property you want to track.
3. From the next sync onward, the Engagement and Monetisation areas populate
automatically.
Until GA4 is connected, those two areas show a **Connect Google Analytics**
prompt rather than empty numbers. Disconnecting revokes Obvlo's access at Google
and stops future syncs; the history already collected is retained.
### You only connect each website once
A Search Console or Analytics connection is stored against your **website**, not
against an individual set of pages. If you run several Obvlo page groups on the
same website, they all inherit that one connection — there's nothing to connect
a second time, and Obvlo won't pull the same data twice.
Where a particular set of pages genuinely needs its own Analytics tag or Search
Console property, it can be pointed at one; that override replaces the shared
connection for those pages only.
## Site-wide vs Microsite
Your Analytics property usually covers your whole website, not just the pages
Obvlo builds. So Measure splits every Engagement and Monetisation metric into two
paired values:
* **Site-wide** — the whole Analytics property (every page on your site).
* **Microsite** — only the pages under your microsite's path (the pages Obvlo
optimised and deployed).
Seeing them side by side tells you how much of your engagement and revenue is
coming from the Obvlo-built pages versus the rest of your site.
The two cohorts are scoped slightly differently, to match how each metric
actually behaves:
* **Engagement** (sessions, engaged sessions, engagement time, and so on) is
counted **per page** — a metric belongs to the Microsite cohort when the page
itself sits under your microsite path.
* **Revenue and purchases** are counted **per session's landing page** — a
purchase is attributed to the Microsite cohort when the visitor *arrived* on a
microsite page in that session. This is because the purchase event fires on your
checkout page, not on the content page, so counting it by the checkout page's
path would under-report the microsite's contribution.
If your microsite lives at the root of your site (its path is `/`), the whole
property *is* the microsite, so the Site-wide and Microsite values are the same.
Search Console's microsite view uses the same microsite path, so its "microsite"
figures line up with Analytics.
### How rows are assigned
Because one website can host more than one set of Obvlo pages, every Analytics
and Search Console row is assigned to the set of pages that owns it, matched on
the **longest matching URL path**. A row that doesn't sit under any Obvlo path
falls into a *rest of the site* bucket rather than being dropped or
double-counted.
The same page classification is used everywhere — monitoring, effectiveness, and
Measure all agree on which pages belong to your microsite, so your own existing
pages are never counted as Obvlo-generated ones.
## Targets, manual entries, and the change log
Team members with **super-admin** access can:
* **Set targets** for any metric (per week, or as a rolling default).
* **Enter a value manually** for metrics without an automated source — for
example the *Microsite Ready Pages* headline or a manual `site:` index count —
recording a data-quality status and an optional note on where the value came
from.
* **Override the data-quality status** of a metric.
Every manual entry, target change, and quality override is written to an
immutable **change log** (who changed what, when, and the before/after values),
so the numbers are always auditable.
## Data-source health
A **data-source health matrix** shows the connection state of each source
(Google Search Console, Google Analytics 4, and AI citation data), including the
last successful sync, with quick links to reconnect anything that needs
attention.
### Is your data actually flowing?
A source can be *connected* and still not be *capturing* anything — for example
if the Analytics tag isn't firing on your pages yet. Rather than showing a
misleading green tick, each source in the Measure header shows one of three
states:
| State | What it means | What to do |
| ----------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| **Capturing data** | The source is connected and the last sync pulled rows. The pill shows the row count. | Nothing — you're all set. |
| **Connected, but no rows captured** | The source is connected but the last sync returned **0 rows**. | Check the likely cause (below) and click the source for details. |
| **Not connected** | The source hasn't been connected yet. | Connect it to start capturing data. |
When Analytics is connected but showing 0 rows, the most common cause is that the
**GA4 tag isn't firing on your site yet**, or the property simply has no data for
the window. Search Console with 0 rows usually means the property has no
impressions in the window yet.
### Inspecting captured data
Click any source pill to open its **capture detail drawer**. The drawer shows:
* The current capture state and, for a 0-rows source, the likely cause.
* The sync **status**, **last sync** time, and the **row count** and **window**
of the most recent sync.
* **Recent captured rows** — the last few days of raw Search Console / Analytics
rows exactly as they came in, *before* the weekly aggregation runs. This lets
you confirm data is arriving even on a day when the weekly tiles haven't updated
yet.
* A **Sync now** button to pull the latest data for that source right away.
Measure is a read-only view of data already collected by your monitoring cycles —
opening it never triggers new crawls or ranking checks, so it uses no credits.
**Sync now** and **Refresh metrics** pull from Search Console and Analytics
(read-only) and recompute existing metrics; they don't run new crawls either.
## Related
* [Reports](/product/insights/reports)
* [AI Visibility Health Check methodology](/product/insights/ai-visibility-health-check-methodology)
* [Microsite](/product/delivery/microsite)
# 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 ``, ``, ``, ``, ``, ``, and ``. |
| Title and meta description | 2 | Title exists and is no more than 70 characters; meta description is 50-160 characters. |
| Canonical URL | 1 | ` ` is present. |
| Open Graph and Twitter cards | 2 | At least three of `og:title`, `og:description`, `og:image`, and `twitter:card`. |
| Language declaration | 1 | `lang` attribute on the `` element. |
| ARIA landmarks | 1 | Explicit landmark roles or implicit landmarks such as ``. |
Semantic markup helps AI systems identify primary content, navigation,
supporting sections, and page summaries instead of treating the page as one
undifferentiated text block.
### 14. Content structure
**Max score: 12**
This check evaluates how well the content is organized for fragment extraction.
| Aspect | Max | Criteria |
| ----------------------- | --: | ---------------------------------------------------------------------------- |
| Heading hierarchy | 3 | Single H1, no skipped heading levels, descriptive heading text. |
| Q\&A format | 2 | Headings phrased as questions. |
| Snippable content | 3 | Lists, tables, `` elements, and inline-list paragraphs. |
| Front-loaded answers | 2 | Section-opening paragraphs are concise and answer-bearing. |
| Self-contained sections | 2 | Sections make sense without relying on phrases such as "as mentioned above". |
AI systems extract fragments rather than whole pages. Clear headings, lists,
tables, and concise section openings make content easier to match to user
queries and cite in answers.
### 15. Content depth
**Max score: 10**
This check evaluates whether the page has enough substance to serve as a
credible citation source.
| Aspect | Max | Criteria |
| ------------------ | --: | -------------------------------------------------------------------------------- |
| Word count | 3 | At least 800 words = 3, at least 500 = 2, at least 300 = 1. |
| Topic coverage | 3 | Unique H2/H3 sub-topics: at least 5 = 3, at least 3 = 2, at least 1 = 1. |
| Factual density | 2 | Statistics, percentages, dates, measurements, or currencies. |
| Content visibility | 2 | Penalizes substantive content hidden in accordions, tabs, or collapsed elements. |
If a page returns fewer than 300 words and shows client-side application
fingerprints such as empty root containers, `__NEXT_DATA__`, Nuxt markers, React
root markers, or Angular `ng-version`, Obvlo flags a high-impact recommendation
to add server-side rendering or pre-rendering.
### 16. Content freshness
**Max score: 8**
This check evaluates whether the page shows current and recently reviewed
signals.
| Aspect | Max | Criteria |
| ------------------------- | --: | ------------------------------------------------------------------------------------- |
| JSON-LD date metadata | 3 | Recent `dateModified` earns the highest score; older or publish-only dates earn less. |
| Last-Modified HTTP header | 2 | Recent server-level modification headers earn more. |
| Visible dates | 1 | Publication or update dates visible to users. |
| Time-sensitive signals | 2 | Current year, "updated" language, or seasonal context. |
Freshness helps retrieval systems choose content that is likely to be current.
The `dateModified` signal is especially useful because it indicates active
review rather than one-time publication.
### 17. Citation readiness
**Max score: 10**
This check evaluates whether an AI system can confidently cite the page with
attribution.
| Aspect | Max | Criteria |
| ---------------------------- | --: | ----------------------------------------------------------------------- |
| External references | 3 | Outbound links to authoritative external domains from primary content. |
| Statistics and data | 2 | Percentages, measurements, study references, or "according to" phrases. |
| Quotations | 2 | Blockquotes, `` elements, or meaningful inline quotes. |
| Author and expertise signals | 2 | Author schema, byline elements, or `rel="author"` links. |
| Marketing fluff penalty | -1 | Penalizes excessive vague superlatives. |
Social media, CDN, font, and self-referencing links are excluded from the
external reference count. The goal is to measure verifiable supporting sources,
not navigation or branding links.
### 18. Internal linking
**Max score: 10**
This check evaluates whether the page connects to other useful pages on the same
site.
| Aspect | Max | Criteria |
| --------------------- | --: | ----------------------------------------------------------------------- |
| Internal link count | 3 | Links from primary content to other pages on the same site. |
| Link text quality | 2 | Descriptive anchor text instead of generic labels such as "click here". |
| Breadcrumb navigation | 2 | Breadcrumb schema and visible breadcrumbs. |
| Linked page quality | 3 | Up to five linked pages are fetched to verify they are reachable. |
Internal links help crawlers and AI systems discover related pages and understand
how topics connect. Descriptive anchor text helps them decide which links are
worth following.
### 19. Accessibility for agents
**Max score: 8**
This check evaluates a focused accessibility subset that matters for agents using
the accessibility tree to identify controls and operate pages.
| Aspect | Max | Criteria |
| ---------------------------- | --: | ------------------------------------------------------------------------------------------ |
| Named interactive elements | 3 | Links, buttons, inputs, selects, textareas, and role-based controls have accessible names. |
| Labelled form fields | 2 | User-supplied form fields have labels or equivalent accessible names. |
| Visible interactive elements | 2 | Interactive elements are not hidden from the accessibility tree. |
| Valid role relationships | 1 | For example, `menuitem` roles appear inside `menu` or `menubar` containers. |
| Score | Status |
| ----: | ------ |
| 7-8 | Pass |
| 4-6 | Warn |
| 0-3 | Fail |
This is separate from the Lighthouse Accessibility score. Lighthouse measures a
broader accessibility baseline; this check focuses on whether agents can name,
find, and operate key controls.
## Agentic Browsing parity
Google's Lighthouse Agentic Browsing category is experimental. Obvlo does not
fold it directly into the 0-100 AI Visibility Score.
Instead, Obvlo reports **Agentic Browsing parity** separately. The parity result
counts applicable agent-readiness checks and reports:
| Field | Meaning |
| ---------- | ----------------------------------------------------- |
| Passed | Applicable checks with `pass` or `info` status. |
| Warnings | Applicable checks with `warn` status. |
| Failed | Applicable checks with `fail` status. |
| Applicable | Total checks included in the parity calculation. |
| Ratio | `passed / applicable`, rounded to two decimal places. |
| Status | `pass`, `warn`, `fail`, or `not-applicable`. |
The current parity calculation considers these check IDs when they are marked
applicable by the health-check implementation:
| Check | Category | Score impact |
| -------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------- |
| `llms.txt` | AI readiness | No 0-100 score impact. Missing files are not applicable; found files and retrieval warnings are applicable. |
| Registered WebMCP Tools | AI readiness | Informational. Currently surfaced as a discovery signal rather than a scored requirement. |
| Forms Missing Declarative WebMCP | AI readiness | Informational. Applicable when forms exist. |
| WebMCP Schema Validity | AI readiness | Scored, max 4. |
| Layout Stability | Infrastructure | Scored, max 5. |
| Accessibility for Agents | Page analysis | Scored, max 8. |
Parity status is intentionally stricter than the 0-100 score summary:
| Condition | Parity status |
| -------------------------------------------------- | -------------- |
| No applicable checks | Not applicable |
| Any applicable check failed | Fail |
| No failures, but at least one warning | Warn |
| All applicable checks passed or were informational | Pass |
Agentic Browsing parity is a methodology signal. It is not a promise that any
specific AI assistant will use a site in a specific way.
## Score interpretation guide
The overall score is normalized to a 0-100 scale.
| Overall score | Rating | Interpretation |
| ------------- | ---------- | -------------------------------------------------------------------------------------------------- |
| 90-100 | Excellent | The site is highly ready for AI visibility. Content should be competitive in AI-generated answers. |
| 75-89 | Good | Strong foundation with specific areas to improve. |
| 50-74 | Needs work | Significant gaps remain. Competitors with stronger foundations may be cited more often. |
| 25-49 | Poor | Major crawlability, structure, or content issues are limiting AI visibility. |
| 0-24 | Critical | The site may be effectively invisible or unusable for many AI systems. |
## What changed in the latest methodology update
The latest implementation update added Agentic Browsing parity while keeping the
AI Visibility Score normalized to 100.
**Added**
* **AI Crawler Accessibility** as a scored Infrastructure check worth 5 raw
points.
* **Layout Stability** as a scored Infrastructure sub-check worth 5 raw points,
derived from Lighthouse Cumulative Layout Shift.
* **Registered WebMCP Tools** as an informational AI readiness signal.
* **Forms Missing Declarative WebMCP** as an informational AI readiness signal.
* **WebMCP Schema Validity** as a scored AI readiness check worth 4 raw points.
* **Accessibility for Agents** as a scored Page Analysis check worth 8 raw
points.
* **Agentic Browsing parity** as a separate stored and streamed result with
passed, applicable, warning, failed, ratio, and status fields.
**Changed**
* Infrastructure raw max increased to 45.
* AI Readiness raw max increased to 22.
* Page Analysis raw max increased to 68.
* `llms.txt` remains non-scoring and optional, but blocked or server-error
retrieval can now count as an applicable warning for parity.
* The 0-100 score remains category-normalized at 25 / 25 / 50, so new raw checks
do not make the overall score exceed 100.
## Why scores may change over time
Scores can change when:
* A site changes templates, content, redirects, firewall rules, or analytics
setup.
* Search engines and AI assistants change how they fetch and interpret pages.
* Obvlo updates the methodology to reflect stronger evidence or new public
guidance.
* Optional emerging checks move from informational to warning-capable or scored
once the underlying standard becomes more stable.
When methodology changes affect customer interpretation, Obvlo documents the
change and keeps the 0-100 AI Visibility Score separate from experimental parity
signals.
## How to improve your score
The most reliable improvements come from practical fundamentals:
* Keep pages crawlable without blocking legitimate search and AI crawlers.
* Publish clean sitemaps and avoid accidental `noindex` or `nosnippet` rules.
* Use HTTPS with a complete certificate chain.
* Improve mobile performance and reduce layout shift.
* Add structured data for key page types and business entities.
* Write useful, specific content with clear headings and citation-ready facts.
* Use semantic HTML, descriptive links, and accessible labels for controls and
forms.
* Keep content fresh and internally linked.
* Treat WebMCP and `llms.txt` as emerging enhancements, especially for important
forms and workflows.
The health check recommendations show which actions are most relevant for the
specific page being tested.
# Analytics
Source: https://docs.obvlo.com/product/insights/analytics
Structured data output for reporting pipelines.
## Overview
Analytics sends event data from Obvlo delivery channels into your analytics setup.
Use case: Internal teams. Use this when you need Obvlo events in your existing analytics workflows.
## Feed coverage
Capture guest interactions across supported delivery products.
Feed events into your own analytics environment and reporting models.
Configure tracking identifiers and channel tags where required.
Support downstream analysis and scheduled reporting workflows.
## Implementation fit
Feed event data directly into Google Analytics with customer-specific tag setup.
Route event streams into customer-selected analytics platforms when needed.
Use feeds for engagement monitoring, audience segmentation, and improvement analysis.
## Related
* [Reports](/product/insights/reports)
* [API](/product/delivery/api)
# Reports
Source: https://docs.obvlo.com/product/insights/reports
In-product dashboards for performance tracking.
## Overview
Reports provide destination-level performance views inside Portal.
Use case: Internal teams. Use reports for engagement tracking, search monitoring, and ranking movement.
## What reports cover
Review sessions, interaction depth, and activity trends.
Monitor impressions, clicks, and click-through indicators.
Track keyword and GEO movement for monitored query sets.
Download report outputs for offline review and stakeholder sharing.
## Reporting workflow
Check dashboard signals to spot movement in engagement and search behaviour.
Use filters and trend slices to identify channels or pages causing change.
Feed findings back into content, delivery, and rollout priorities.
## Related
* [Portal](/product/management/portal)
* [Analytics](/product/insights/analytics)
* [Measure — AI Search Visibility](/product/insights/ai-search-visibility-measure) — performance tracking for Microsite pages
* [Microsite](/product/delivery/microsite)
# Portal
Source: https://docs.obvlo.com/product/management/portal
Central admin for setup, edits, and publishing.
## Overview
Portal is the dashboard where teams set up destinations, review and publish content, manage delivery settings, and control user access.
Use case: Internal operations. Teams use Portal to review content, configure delivery, and publish updates.
## What teams use Portal for
Manage destination details, ranking behaviour, search radius, and related configuration. A guided wizard walks you through creating a new destination, including AI Visibility onboarding.
Review and update guides, listings, promos, and destination pages from one workspace.
Configure Web App, Embeddable Widget, and Microsite delivery settings.
Connect and maintain external platform integrations.
Manage roles and organisation access, including MFA requirements.
Preview and publish changes with clear ownership before go-live.
Plan AI search coverage across three levels — pillars, clusters, and targets — so you can see how individual topics ladder up.
Manage design tokens and component styles, with per-breakpoint overrides so a component can look different on each screen size.
## Role model
| Role | Typical scope |
| --------- | -------------------------------------------------------------------- |
| Admin | Full settings, user management, publishing, and integration control. |
| Editor | Content and delivery updates with publishing responsibilities. |
| Developer | Integration and API implementation support where needed. |
## Related
* [Content Engine](/product/creation/content-engine)
* [Web App](/product/delivery/web-app)
* [Embeddable Widget](/product/delivery/embeddable-widget)
* [Microsite](/product/delivery/microsite)
* [Reports](/product/insights/reports)
* [Measure — AI Search Visibility](/product/insights/ai-search-visibility-measure)
# Translations
Source: https://docs.obvlo.com/product/management/translations
How Obvlo decides which content is translated, into which languages, and how to check translation coverage.
Obvlo translates your published content into the languages configured for your organisation. Two settings decide what gets translated: your **translation scope** — which destinations are included — and the **target languages** attached to each destination in that scope.
Translation scope and target languages are configured by Obvlo. Ask your account manager to change them. You can see the result at any time in **Organization** > **Dashboard** > **Translation Health**.
## Translation scope
| Scope | What is translated |
| -------------------------------- | --------------------------------------------------------------------------------------------------- |
| **All destinations** *(default)* | Every destination's content is translated into every language on your organisation's language list. |
| **Selected destinations** | Only the destinations named in your scope are translated, each into its own target languages. |
Every organisation starts on **All destinations**. Nothing changes for you unless you ask for a narrower scope.
Under **Selected destinations**, content that belongs only to a destination outside the scope is not translated, creates no translation work, and uses no credits.
## Target languages per destination
When your organisation uses **Selected destinations**, each destination carries its own list of target languages. A destination is translated into its own languages and no others — there is no fallback to the organisation-wide list.
| Destination | Target languages |
| ----------- | ---------------- |
| Barcelona | `es-ES`, `de-DE` |
| Lyon | `fr-FR` |
With that configuration:
* A guide that belongs only to Barcelona is translated into Spanish and German, and **not** into French.
* A guide that belongs only to Lyon is translated into French, and **not** into Spanish or German.
* A listing that belongs to **both** Barcelona and Lyon is translated into **all three** — shared content receives the union of its destinations' languages, never the smaller set.
Your organisation's default language is the source language and is never a translation target.
### A destination with no target languages
A destination can be in scope with no target languages selected. That destination is not translated at all — no translation work is created and no credits are used.
This is a valid configuration, but it is indistinguishable from a forgotten step, so it is flagged when the configuration is saved and **Translation Health** reports the destination as configured-but-untranslated rather than as thousands of individual gaps.
### Your organisation-wide language list
Whatever the per-destination configuration, your organisation's language list is maintained as the union of every in-scope destination's languages plus your default language. That list is what the [Organisation Languages](/api-reference/endpoint/org-languages) endpoint, the Embeddable Widget, and your Microsite read, so integrations continue to work against one authoritative list.
To get the languages for a single destination rather than the union, pass `hostId` to the [Organisation Languages](/api-reference/endpoint/org-languages) endpoint.
## What happens when the configuration changes
| Change | Effect |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Adding a language** to a destination | Existing content for that destination is translated into the new language on the next scheduled run, even if the content has not been edited. This uses credits. |
| **Removing a language** from a destination | Nothing is deleted and existing translations continue to be served. No credits are used. The language simply stops being expected. |
| **Adding a destination** to the scope | That destination's content is translated. Content it shares with a destination already in scope is also widened to the new destination's languages, which uses credits. |
| **Removing a destination** from the scope | No new translation work is created for it. Existing translations are kept and continue to be served. |
Because adding a language or a destination can translate content that was never translated before, these changes are billable, and that is stated at the point the change is saved.
## Excluding site pages
Site pages and sections can be excluded from translation while every other content type stays in scope — guides, listings, locations, host profiles, host information, and promos are all unaffected. This suits organisations that publish their site in one language but need translated content everywhere else.
Turning the exclusion **on** creates no translation work, deletes nothing, and continues to serve translations that already exist. Turning it back **off** triggers a catch-up run so the pages that were skipped are translated, rather than leaving them permanently untranslated.
## Translation Health
**Translation Health** is a weekly report on your translation coverage, at **Organization** > **Dashboard** > **Translation Health**. It is scoped to your translation configuration — a destination limited to French is assessed against French only, so a deliberately narrow scope does not show up as missing translations.
Every portal role can read the report. Re-running it on demand and re-syncing the search index are Obvlo-side actions — ask support if you need either.
The report groups findings by what they mean for what visitors see:
| Finding | What it means |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **Not translated** | No translation has been attempted for this record in this language. |
| **Translated but not searchable** | The translation exists but the search index does not have it, so visitors see the original language. A re-sync fixes this and uses no credits. |
| **Search index out of date** | The search index holds an older translation than the record does. A re-sync fixes this and uses no credits. |
| **Source changed since translating** | The original text changed after it was translated, so the translation is out of date. Re-translating uses credits. |
| **Translation failed** | The translation was attempted and errored. It is not retried automatically — ask support to re-run it. |
| **Translation finished but not applied** | The translation completed but never reached the record, so nothing is shown in that language. |
| **Language not listed on the record** | Translated content is present but the record does not list the language, so it may not be served. |
| **Language list missing** | The record has translated fields but no list of languages. |
| **Translation for a removed language** | A translation exists in a language your organisation no longer uses. Harmless, but it can be cleaned up. |
| **Translated before change tracking** | The content itself is fine. It predates change tracking, so we cannot tell whether the source has since changed. |
## Related
* [Language Reference](/reference/language-reference) — every supported language code
* [Organisation Languages endpoint](/api-reference/endpoint/org-languages) — read the configured languages, optionally per destination
* [Portal](/product/management/portal)
* [Content Engine](/product/creation/content-engine)
# Overview
Source: https://docs.obvlo.com/product/platform-overview
How Obvlo is structured across Creation, Management, Delivery, and Insights.
Obvlo is structured into four connected platform areas that move destination content from creation to performance monitoring: **Creation**, **Management**, **Delivery**, and **Insights**.
Access to products on the Obvlo Platform depends on your plan. Contact the Commercial Team for product availability and packaging details.
## Platform areas
Generate, assess, and maintain destination content through Content Engine.
Use Portal as the dashboard for setup, review, and publishing control.
Distribute content through Web App, Widget, Microsite, API, and Partner Integrations.
Track performance and engagement using Analytics and Reports.
# Language Reference
Source: https://docs.obvlo.com/reference/language-reference
Complete list of all 63 languages supported across Obvlo products.
Use any of these language codes with the [Embed Widget](/embed-widget/quick-start#lang), the [Microsite](/microsite/overview), or the [Organisation Languages](/api-reference/endpoint/org-languages) API endpoint.
Languages must be enabled on your host profile before they are available. Use the [Organisation Languages](/api-reference/endpoint/org-languages) endpoint to retrieve the exact set of languages enabled for your organisation. Contact your Obvlo account manager to enable additional languages.
Some organisations configure target languages **per destination**, so one destination may be translated into a smaller set than the organisation as a whole. Pass `hostId` to the [Organisation Languages](/api-reference/endpoint/org-languages) endpoint to get the languages for a single destination, and see [Translations](/product/management/translations) for how the scope is decided.
| Code | Language | Currency | Measurement | RTL |
| --------- | -------------------------------- | ---------- | ----------- | ------- |
| `en-GB` | English (UK) — *default* | GBP (£) | Imperial | No |
| `sq-AL` | Albanian (Albania) | ALL (L) | Metric | No |
| `am-ET` | Amharic (Ethiopia) | ETB (Br) | Metric | No |
| `ar-SA` | Arabic (Saudi Arabia) | SAR (﷼) | Metric | **Yes** |
| `hy-AM` | Armenian (Armenia) | AMD (֏) | Metric | No |
| `bn-BD` | Bengali (Bangladesh) | BDT (৳) | Metric | No |
| `bs-BA` | Bosnian (Bosnia and Herzegovina) | BAM (KM) | Metric | No |
| `bg-BG` | Bulgarian (Bulgaria) | BGN (лв) | Metric | No |
| `my-MM` | Burmese (Myanmar) | MMK (K) | Metric | No |
| `ca-ES` | Catalan (Spain) | EUR (€) | Metric | No |
| `zh-Hans` | Chinese (Simplified) | CNY (¥) | Metric | No |
| `zh-Hant` | Chinese (Traditional) | TWD (NT\$) | Metric | No |
| `hr-HR` | Croatian (Croatia) | HRK (kn) | Metric | No |
| `cs-CZ` | Czech (Czech Republic) | CZK (Kč) | Metric | No |
| `da-DK` | Danish (Denmark) | DKK (kr) | Metric | No |
| `nl-NL` | Dutch (Netherlands) | EUR (€) | Metric | No |
| `en-US` | English (US) | USD (\$) | Imperial | No |
| `et-EE` | Estonian (Estonia) | EUR (€) | Metric | No |
| `fi-FI` | Finnish (Finland) | EUR (€) | Metric | No |
| `fr-FR` | French (France) | EUR (€) | Metric | No |
| `ka-GE` | Georgian (Georgia) | GEL (₾) | Metric | No |
| `de-DE` | German (Germany) | EUR (€) | Metric | No |
| `el-GR` | Greek (Greece) | EUR (€) | Metric | No |
| `gu-IN` | Gujarati (India) | INR (₹) | Metric | No |
| `hi-IN` | Hindi (India) | INR (₹) | Metric | No |
| `hu-HU` | Hungarian (Hungary) | HUF (Ft) | Metric | No |
| `is-IS` | Icelandic (Iceland) | ISK (kr) | Metric | No |
| `id-ID` | Indonesian (Indonesia) | IDR (Rp) | Metric | No |
| `it-IT` | Italian (Italy) | EUR (€) | Metric | No |
| `ja-JP` | Japanese (Japan) | JPY (¥) | Metric | No |
| `kn-IN` | Kannada (India) | INR (₹) | Metric | No |
| `kk-KZ` | Kazakh (Kazakhstan) | KZT (₸) | Metric | No |
| `ko-KR` | Korean (South Korea) | KRW (₩) | Metric | No |
| `lv-LV` | Latvian (Latvia) | EUR (€) | Metric | No |
| `lt-LT` | Lithuanian (Lithuania) | EUR (€) | Metric | No |
| `mk-MK` | Macedonian (North Macedonia) | MKD (ден) | Metric | No |
| `ms-MY` | Malay (Malaysia) | MYR (RM) | Metric | No |
| `ml-IN` | Malayalam (India) | INR (₹) | Metric | No |
| `mr-IN` | Marathi (India) | INR (₹) | Metric | No |
| `mn-MN` | Mongolian (Mongolia) | MNT (₮) | Metric | No |
| `nb-NO` | Norwegian (Norway) | NOK (kr) | Metric | No |
| `fa-IR` | Persian (Iran) | IRR (﷼) | Metric | **Yes** |
| `pl-PL` | Polish (Poland) | PLN (zł) | Metric | No |
| `pt-BR` | Portuguese (Brazil) | BRL (R\$) | Metric | No |
| `pt-PT` | Portuguese (Portugal) | EUR (€) | Metric | No |
| `pa-IN` | Punjabi (India) | INR (₹) | Metric | No |
| `ro-RO` | Romanian (Romania) | RON (lei) | Metric | No |
| `ru-RU` | Russian (Russia) | RUB (₽) | Metric | No |
| `sr-RS` | Serbian (Serbia) | RSD (дин) | Metric | No |
| `sk-SK` | Slovak (Slovakia) | EUR (€) | Metric | No |
| `sl-SI` | Slovenian (Slovenia) | EUR (€) | Metric | No |
| `so-SO` | Somali (Somalia) | SOS (Sh) | Metric | No |
| `es-ES` | Spanish (Spain) | EUR (€) | Metric | No |
| `sw-KE` | Swahili (Kenya) | KES (KSh) | Metric | No |
| `sv-SE` | Swedish (Sweden) | SEK (kr) | Metric | No |
| `tl-PH` | Tagalog (Philippines) | PHP (₱) | Metric | No |
| `ta-IN` | Tamil (India) | INR (₹) | Metric | No |
| `te-IN` | Telugu (India) | INR (₹) | Metric | No |
| `th-TH` | Thai (Thailand) | THB (฿) | Metric | No |
| `tr-TR` | Turkish (Turkey) | TRY (₺) | Metric | No |
| `uk-UA` | Ukrainian (Ukraine) | UAH (₴) | Metric | No |
| `ur-PK` | Urdu (Pakistan) | PKR (₨) | Metric | **Yes** |
| `vi-VN` | Vietnamese (Vietnam) | VND (₫) | Metric | No |
## Related pages
* [Developers overview](/developer-docs/overview)
* [Widget Quick Start](/embed-widget/quick-start)
* [Microsite overview](/microsite/overview)
# Change log
Source: https://docs.obvlo.com/support/change-log
Track notable documentation and product update communication.
## Release 22 — 27 August 2026
This release is corrections. Nothing here is a new capability, and nothing you have configured needs
to change.
### Microsite — delivery and publishing
* **The Cloudflare route that binds the proxy Worker is now generated for you**, wildcard included.
A route pattern without a trailing `*` matches one exact URL, so every page beneath the mount — and
the mount itself once a visitor arrives with a tracking parameter — was served by your own origin
instead. Nothing logged an error, because an unmatched route is not a fault. Copy the pattern from
the portal's **Integration** tab. See [Reverse proxy](/microsite/reverse-proxy).
* **Four fixes to the Worker template on that page**: the mount path is now stripped by position
rather than by first match, each `srcset` candidate is moved on its own rather than the attribute
being prefixed whole, the request body is forwarded so a non-GET is no longer emptied, and the
origin's cache and trace headers are removed from every response rather than only from non-HTML
ones. If you copied an earlier version, take the current one.
* **Publishing a page to one channel no longer misreports the other.** Publishing to Preview could
flip Production to "Serving" while production carried on serving the snapshot it already had, and
removed the control that would have corrected it. Freshness is now resolved per channel.
* **Publish controls only appear where publishing changes anything.** On prerendered delivery the
deploy is the publish, so the Publish button, the Preview and Production columns and the publish
menu items are no longer shown there — none of them could change what a visitor receives. See
[Deployment modes](/microsite/deployment-modes).
* **There is one place to set where your pages are served.** Two editors wrote the same value under
two names, and one of them disappeared when the delivery route changed. A single **Served at**
readout in Site Settings now shows the domain and path, and states what depends on them: the
reverse-proxy rule, the sitemap root, and which pages are measured.
* **The page editor preview shows a listing's current content immediately.** An edited listing could
keep rendering its previous copy in the preview for up to five minutes.
* **Accented characters survive sitemap slug generation.** Any character outside `a-z0-9` became a
hyphen, so `Santarém` lost its accented letter as punctuation instead of folding to `santarem`.
### Translations
* **Listing translations are being scheduled again.** From 18 to 25 August no listing translation was
created for any organisation: one large account consumed each run's entire time allowance and every
account behind it was never reached. Jobs now resume from where the previous run stopped, so no
account can hold up another's translations.
* **No credit warnings for a re-scan you did not ask for**, and a run that fails part way is retried
rather than recording itself as complete. Credits are still charged per translation, exactly as
before.
* **On-demand localisation audits complete again.** Audits for organisations with no translation
scope set could not finish, so requests sat unserviced.
### Embeddable widget
* **`preferredTravelMode` values are uppercase** — `DRIVING`, `WALKING`, `BICYCLING`, `TRANSIT`. The
configuration reference previously showed them in lower case, and listed `cycling`, which is not a
value. Any other value fails validation and the setting is ignored. See the
[Configuration reference](/embed-widget/configuration-reference#preferredtravelmode).
### Documentation
* **The NGINX, Apache, IIS and Caddy reverse-proxy examples now name `sites.obvlo.com` and mount at a
path.** They still named the prerendered origin and mounted at your domain root, which would route
your home page and every existing route to Obvlo. Only the Cloudflare example had been corrected.
If you copied one of the four, take the current version.
* **Five pages were unreachable for nine days** after a mistaken revert on 18 August — deployment
modes, the WordPress plugin, dynamic serving, the Embed API, and translations. All are restored.
## Release 21 — 18 August 2026
This release adds three new ways to put Obvlo content on your own domain, and
changes which one we recommend.
### Microsite — how content reaches your domain
* **The reverse proxy to `sites.obvlo.com` is now the recommended way to run a
microsite.** Pages render on request, so publishing a change makes it live within
seconds and new pages appear without a rebuild. The
[Reverse proxy](/microsite/reverse-proxy) examples now point at this origin.
* **Dynamic serving.** The origin behind that recommendation. Your reverse proxy
configuration keeps the same shape — only the origin it fetches from changes, so
switching is two lines and reverses the same way.
See [Dynamic serving](/microsite/ssr-proxy).
* **WordPress plugin.** The same reverse proxy, packaged and pre-configured for your
site. Upload it, activate it, and there is no web server configuration at all. It
serves a cached copy if Obvlo is ever unreachable, and gives the path back to
WordPress when you deactivate it.
See [WordPress plugin](/microsite/wordpress-plugin).
* **Embed API.** Place a single guide inside a page you already render, as an HTML
fragment. There is a client-side script for supplementary content and a
server-side include that puts the guide in the HTML a crawler receives — use the
second one if the guide is the reason the page should rank.
See [Embed API](/microsite/embed-api).
* **Prerendered serving is unchanged and still fully supported.** Nothing you have
configured needs to change, and there is no deadline to move. It is no longer the
default recommendation for a new site, because a published change there is not
visible until the next rebuild.
* **A microsite can now be mounted on a whole subdomain**, such as
`guides.yourdomain.com`, instead of on a path of your main domain. Nothing on
the main domain is touched and no existing route moves, which makes it the
quickest way to put a pilot in front of real traffic. The trade-off is real:
SEO authority accrues to the subdomain rather than to your primary domain, so
a path mount remains the recommendation for production.
See [Mounting on a subdomain](/microsite/reverse-proxy#mounting-on-a-subdomain).
* **A comparison of all four**, with the trade-offs of each and how to switch
between them, at [Deployment modes](/microsite/deployment-modes).
### Microsite — performance
* **Pages load faster on the dynamic path** — responses are compressed and cached
at the edge, and page CSS is inlined rather than fetched as a separate
render-blocking file.
* **Hero images appear immediately.** The image at the top of a page previously
waited for you to move the mouse, press a key or scroll — otherwise eight
seconds — before it was requested at all.
### Documentation
* **The NGINX and Apache reverse-proxy examples now mount at a path**, matching
the Cloudflare example. The previous examples mounted at your domain root, which
would route your home page and every existing route to Obvlo. If you copied one
of those, see [Reverse proxy](/microsite/reverse-proxy) for the corrected
version.
* **Every reverse-proxy example now names `sites.obvlo.com`.** If your domain is
still on prerendered delivery, substitute `content.obvlo.com` in the two places
each example names the origin — the URL it fetches and the `Host` header it sends.
Everything else, including the path after the host, is identical.
* **A new [WordPress plugin](/microsite/wordpress-plugin) page** covers install,
the permalink requirement, what visitors get during an outage, and how updates
work.
## Release 20 — 27 July 2026
This release makes AI Visibility measurement more accurate and adds a significant
Portal platform update.
### AI Visibility — Measurement and attribution
* **Connect Google Analytics and Search Console once per website.** A connection
is now shared across everything on the same website, so there's nothing to
connect twice and no duplicate data pull. A specific set of pages can still use
its own tag or property where it needs one.
See [Connecting your data sources](/product/insights/ai-search-visibility-measure#connecting-your-data-sources).
* **Analytics and search data land on the right pages.** Every row is attributed
to the set of pages that owns it, matched on the longest matching URL path, with
anything else rolled into a *rest of the site* bucket.
* **One definition of "which pages belong to a microsite".** Monitoring,
effectiveness, and Measure now agree, so your own existing pages are no longer
counted as Obvlo-generated ones.
* **Fixed:** Measure could read a stale Search Console connection after
connecting; it now reads the current shared connection.
### Portal
* **Clearer strategy model** — content strategy is now presented in three columns
(pillars, clusters, and targets), making it easier to see how topics ladder up.
* **New destination wizard** — a guided setup flow with AI Visibility onboarding
built in.
* **Per-breakpoint theming** — the theme editor can now override component styles
per screen size.
* **Self-registration and invitation management** — pending invitations are now
listed alongside active users, and can be resent, copied as a link, or revoked.
See [Adding team members](/onboarding-guides/portal-onboarding-guide#12-organisation-settings).
* **Angular 22 upgrade** for performance and long-term maintainability.
### Microsite
* **Component-level theming** — page sections now consume theme variables through
a faster styling pipeline, so your site's look is applied more consistently.
* **Theme-accurate previews** in the page editor.
* **New building blocks** — a blog card and a Markdown block section.
* **Fixed:** `tel:` links now render correctly.
### Platform reliability
* Domains interrupted mid-scan now recover instead of getting stuck.
* Support for compressed (gzip) sitemaps, detection of JavaScript-only pages, and
a guard against URLs that don't really exist.
* **Fixed:** scanning a specific path is no longer overridden by the site's root,
so discovery stays where you pointed it.
The API reference is unchanged in this release. See the
[API changelog](/api-reference/changelog) for API-specific history.
## Related
* [Getting Started](/getting-started/start-here)
* [FAQ](/support/faq)
* [Glossary](/support/glossary)
# FAQ
Source: https://docs.obvlo.com/support/faq
Frequently asked questions by product and workflow area.
Use the sections below to find answers by category.
## Portal FAQs
| Question | Answer |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| I have not received my login email. What should I do? | Check spam first. If you still cannot find it, go to [portal.obvlo.com](https://portal.obvlo.com/) and use Forgot Password with your invited email. |
| My welcome link no longer works. | This is expected after 24 hours. Use Forgot Password with your invited email, or use Google Sign-In if your account has already been invited. An Admin can also resend your invitation or copy the registration link from Organization Settings > Users. |
| Where do I manage content in Portal? | Go to Destinations, select your destination, then open Review Content. |
| Can I change copy on a listing or guide? | Yes. You can edit names, descriptions, and listing fields directly in Portal and publish updates. |
| Can I add a place that is not already in the database? | Yes, as long as the place exists as a business listing in Google Maps. |
| How do I update the handbook? | Go to Destinations, choose your destination, then open Pages and Manage Information Page. |
| Can multiple team members review content at the same time? | Yes. Teams can collaborate in Portal, but it helps to assign one owner for final approval so launch readiness is clear. |
## Web App FAQs
| Question | Answer |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| How quickly can we launch Web App? | Web App is the fastest path for most teams because Obvlo hosts and maintains it. Your main work is content approval and distribution planning. |
| 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 to improve relevance. |
| When should we choose Web App over Widget? | Choose Web App when you want a full hosted guest experience. Choose Widget when you want the experience embedded inside your existing website. |
## Embeddable Widget FAQs
| Question | Answer |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| How does the widget's data capture feature benefit us? | Data Capture collects traveller preferences in a simple way, so you can show more relevant recommendations without manual data collection. |
| How do personalised recommendations work? | Recommendations are ranked and filtered using three layers. Traveller inputs — traveller type, interests, and trip type — adjust which guides and listings are most relevant to each user. External factors — weather, time of day, and time of year — further shape what is surfaced in context. Editorial priority gives your team control: guides marked as Featured are ranked above non-featured guides across all delivery channels, making them useful for highlighting partner venues or owned outlets. |
| How does responsive design improve user experience? | The widget adapts to screen size automatically, so guests get a consistent experience across desktop, tablet, and mobile. |
| Can I customise the widget to fit my brand? | Yes. You can configure colours, images, logos, and supported fonts. See [Widget configuration reference](/embed-widget/configuration-reference) for available options. |
| How do I ensure the widget is functioning correctly on my site? | Run an end-to-end check across key devices and browsers. Confirm branding, content accuracy, and responsive behaviour before go-live. |
| Does the widget support analytics tracking? | Yes. If your site uses Google Analytics, events are sent automatically with no setup. For other analytics providers, add a JavaScript event listener on the page. See [Analytics events](/embed-widget/quick-start#analytics-events) for implementation detail. |
| Where do I find my Destination ID (host-id)? | In the Portal, go to Destinations. The value is listed in the Destination ID column. This is the `host-id` used in widget setup. See [Widget quick start](/embed-widget/quick-start#required). |
| Should I use `defer` or `async` when loading the widget script? | Use `defer` 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. See [Widget quick start](/embed-widget/quick-start#installation). |
| Can I display a specific guide or area when the widget loads? | Yes. Use the `guide-id` attribute to open a specific guide, or `neighbourhood-id` to open a specific area. See [Configuration reference](/embed-widget/configuration-reference) for usage. |
| How do I set the widget display language? | Use the `lang` attribute with a language-dialect code, for example `en-GB` or `fr-FR`. See [Configuration reference](/embed-widget/configuration-reference) for supported codes. |
| Why does the widget map fail in development but work in production? | This usually comes from Google Places API key permissions. In production, the host key is typically configured correctly. In development or staging, restricted or mismatched key settings can block map rendering. See [Widget quick start](/embed-widget/quick-start) for configuration guidance. |
## Microsite FAQs
| Question | Answer |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| What is Microsite best used for? | Microsite is best when search visibility and destination discoverability are priorities, especially across SEO, GEO, and AEO journeys. |
| Do we need our own CMS to use Microsite? | No. Obvlo hosts and maintains Microsite pages. Your team manages content and publishing 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) for configuration. |
| Is the reverse proxy required? | Without the reverse proxy, the full SEO and domain authority benefit of Microsite is not realised. Pages will still work, but authority accrues to the Obvlo domain rather than yours. See [Reverse proxy setup](/microsite/reverse-proxy). |
| Does Microsite support multiple languages? | Yes. Pages are generated in all languages configured for your site, each independently indexed. See the [Language reference](/reference/language-reference) for all 63 supported languages. |
| Do I need to add the Microsite sitemap to my domain's sitemap\_index.xml? | Yes. Without the reference, search engine crawlers may not discover Microsite pages during their scheduled crawl of your domain. See [Microsite overview](/microsite/overview#sitemap-integration) for the correct format. |
| How do we monitor Microsite performance? | Use the [Measure dashboard](/product/insights/ai-search-visibility-measure) in AI Visibility for indexing, search, engagement, and revenue performance on your Obvlo-built pages. |
## API FAQs
| Question | Answer |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Where should teams start with API delivery? | Start with [API overview](/developer-docs/api/overview), then review [Authentication](/developer-docs/api/authentication) and [API Reference](/api-reference/introduction). |
| Where is endpoint-level detail? | Use [API Reference](/api-reference/introduction) for endpoint definitions, payloads, and response details. |
| 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. |
| Do preview endpoints behave differently from stable endpoints? | Yes. Stable endpoints are for predictable production use. Preview endpoints can evolve faster and should be validated before release. |
| How should we handle rate limits and failures? | Implement retries, validation checks, and logging in your integration layer. |
## Reporting and analytics FAQs
| Question | Answer |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Why is Reporting empty after launch prep? | Reporting starts populating after the live experience is active and guests begin interacting with content. |
| What is the difference between Analytics and Reports? | Analytics is the event data feed for your analytics tools. See [Analytics events](/embed-widget/quick-start#analytics-events). Reports are in-Portal dashboards for day-to-day monitoring. See [Reporting](/onboarding-guides/portal-onboarding-guide#11-reporting). |
| Can we export data for stakeholder updates? | Yes. Reports supports export workflows so teams can share offline updates and reviews. |
## Help FAQs
| Question | Answer |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Where should our team start? | Start with [Getting Started](/getting-started/start-here), then review the relevant [onboarding guide](/onboarding-guides/overview) for your delivery path. |
| How do we choose the right implementation path? | Use [Step 4: Using your content](/getting-started/step-4-using-your-content). It maps goals to Web App, Embeddable Widget, Microsite, or API. |
| Where can I track planned work and product updates? | Use the [Change log](/support/change-log) for product updates. |
| How do we get support? | Contact your account manager or email [support@obvlo.com](mailto:support@obvlo.com). |
## Related
* [Getting Started](/getting-started/start-here)
* [Key terms](/getting-started/key-terms)
* [Onboarding guides](/onboarding-guides/overview)
# Glossary
Source: https://docs.obvlo.com/support/glossary
Canonical terminology used across Obvlo documentation.
## Platform and workflow terms
| Term | Meaning |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Content Engine | The creation system that crawls, curates, generates, assesses, and reprocesses destination content. |
| Portal | The management dashboard used to manage destinations, content, delivery settings, and users. |
| Management | The platform area that covers setup, review, publishing, and access management in Portal. |
| Destination Setup | The process of setting destination details, rules, and delivery settings in Portal. |
| Approval Workflow | The review and sign-off process a team follows in Portal before content is published live. |
| Launch Scope | The launch scope agreed by the team, including destinations, delivery path, and content priorities. |
| Use Case | The practical business objective a team wants a product path to support, such as SEO visibility, engagement, or guest experience. |
## Content model terms
| Term | Meaning |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Destination | A location-based entity in Obvlo with its own content and delivery settings. |
| Guide | A collection of listings built around a place or theme. |
| Thematic Guide | A guide focused on an interest or theme, such as food, culture, or wellness. |
| Area Guide | A guide focused on a specific location or neighbourhood within a destination. |
| Listing | An individual recommendation item used across guides and delivery channels. |
| Itinerary | A destination plan created by Obvlo. |
| Content Theme | A strategic topic focus for content planning, such as family, food, wellness, culture, events, or seasonal travel. |
| Content Output | Generated content type such as guides, listings, itineraries, imagery, or related destination data. |
| Template Configuration | Rules defining what content is created, for which audience, and under which criteria. |
| Brand Configuration | Rules used by Obvlo to keep generated content aligned with brand tone, voice, and visuals. |
| Promos | Promotional content units that can be targeted and scheduled by audience and time window. |
## Delivery and integration terms
| Term | Meaning |
| -------------------- | ---------------------------------------------------------------------------------------------------- |
| Web App | Obvlo's hosted Digital Concierge experience used across pre-arrival and in-stay journeys. |
| Digital Concierge | The customer-facing name for the hosted Obvlo Web App. |
| Embeddable Widget | The product name for Obvlo's on-site Trip Planner component that is embedded in an existing website. |
| Trip Planner | The customer-facing name often used for the Embeddable Widget experience. |
| Microsite | The customer-facing name for Obvlo HTML Pages. |
| HTML Pages | Technical product name for Microsite pages served under your domain. |
| API | The API used to send destination content into a customer's own system. |
| Partner Integrations | Pre-built connectors linking Obvlo with systems such as PMS, CMS, CRM/CDP, and AI chatbot platforms. |
| Reverse Proxy | The technical setup used to serve Obvlo-managed Microsite pages under a customer's own domain. |
## Performance and optimisation terms
| Term | Meaning |
| ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| Guest Journey | The sequence of traveller touchpoints across pre-arrival, in-stay, and post-launch optimisation. |
| Attract Stage | The early guest-journey stage focused on search visibility and destination discovery before booking. |
| Engage Stage | The guest-journey stage focused on on-site interaction depth and conversion support. |
| Journey Coverage | The way a product supports pre-arrival planning, in-stay usage, and post-launch optimisation. |
| Personalisation | The process of adapting ranking and recommendations based on traveller context, intent, or preference signals. |
| Analytics Feed | Event data from Obvlo delivery channels that can be sent to your analytics tools. |
| Reports | Portal dashboards used to review engagement, search, and performance trends. |
| Search Model | The framework used on Microsite pages to support SEO, GEO, and AEO discovery patterns. |
| SEO | Search Engine Optimisation: practices that improve visibility in traditional search results. |
| GEO | Generative Engine Optimisation: content practices aimed at improving visibility in AI-generated search responses. |
| AEO | Answer Engine Optimisation: structuring content so direct-answer systems can retrieve and present it clearly. |
| Change Log | The support page that tracks notable product updates and release communication. |
## Related
* [Getting Started](/getting-started/start-here)
* [Key terms](/getting-started/key-terms)
* [FAQ](/support/faq)