Members APIDiscover
Get Featured Content
Get personalized featured content for the discover screen
GET
/members/me/features/discover/featuredOverview
Returns personalized featured content for the discover screen, including perks, upcoming/current stays, featured properties, and offers. Content is customized based on membership tier, preferences, and booking history.
Authentication
Requires a valid Clerk JWT token in the Authorization header.
Query Parameters
propertiesLimitquerynumberMaximum number of featured properties to include
propertiesOffsetquerynumberOffset for featured properties pagination
Request
curl -X GET "http://localhost:3000/v1/members/me/features/discover/featured" \
-H "Authorization: Bearer YOUR_CLERK_JWT"Response
Returns an array of featured content items, each with a specific type and data structure.
itemsarrayrequiredArray of featured content items
Content Types
itemTypestringrequiredType of featured content: "perk", "offer", "stay-past", "stay-current", "stay-upcoming", or "property"
itemDataobjectrequiredContent data (structure varies by itemType)
Response
[
{
"itemType": "perk",
"itemData": {
"id": 123,
"documentId": "perk_luxury_airport_lounge",
"name": "Priority Airport Lounge Access",
"description": "Access to premium airport lounges worldwide",
"headline": "Relax in style before your flight",
"membershipTierName": "Premium",
"image": {
"url": "https://cdn.example.com/perks/lounge-access.jpg",
"alt": "Airport lounge interior"
},
"icon": {
"url": "https://cdn.example.com/icons/lounge.svg",
"alt": "Lounge icon"
}
}
},
{
"itemType": "stay-upcoming",
"itemData": {
"property": {
"id": 456,
"documentId": "prop_luxury_resort_bali",
"type": "Resort",
"name": "Luxury Resort Bali",
"brandName": "Journey Select",
"maxGuests": 4,
"nightlyCost": 350,
"address": {
"city": "Ubud",
"state": "Bali",
"country": "Indonesia",
"latitude": -8.5069,
"longitude": 115.2625
},
"tagIds": [1, 5, 12],
"heroImage": {
"url": "https://cdn.example.com/properties/bali-resort-hero.jpg",
"alt": "Luxury resort pool view"
}
},
"booking": {
"id": 789,
"documentId": "booking_456_789",
"bookingStatus": "CONFIRMED",
"checkInDate": "2025-02-15",
"checkOutDate": "2025-02-20",
"guestCount": 2,
"totalPrice": 1750
}
}
},
{
"itemType": "offer",
"itemData": {
"offerId": "offer_spa_package_2025",
"name": "Rejuvenating Spa Package",
"shortDescription": "3-hour luxury spa treatment",
"description": "Complete wellness experience including massage, facial, and access to spa facilities",
"pointsRequired": 15000,
"retailValue": 450,
"redemptionType": "spa_treatment",
"heroImageUrl": "https://cdn.example.com/offers/spa-package.jpg",
"howToRedeem": "Book directly through Journey app or website",
"rules": "Valid at participating spa locations. Subject to availability.",
"isGlobal": true,
"partnerLabel": "Luxury Spa Partners"
}
},
{
"itemType": "property",
"itemData": {
"id": 321,
"documentId": "prop_mountain_lodge_aspen",
"name": "Mountain Lodge Aspen",
"brandName": "Journey Collection",
"type": "Lodge",
"baseRoomCategory": "Mountain View Suite",
"nightlyCost": 480,
"guestRating": 4.8,
"maxGuests": 6,
"amenities": ["Ski-in/Ski-out", "Spa", "Fine Dining"],
"address": {
"city": "Aspen",
"state": "Colorado",
"country": "United States",
"latitude": 39.1911,
"longitude": -106.8175
},
"heroImage": {
"url": "https://cdn.example.com/properties/aspen-lodge-hero.jpg",
"alt": "Mountain lodge exterior with snow"
},
"gallery": [
{
"url": "https://cdn.example.com/properties/aspen-lodge-1.jpg",
"alt": "Lodge interior"
},
{
"url": "https://cdn.example.com/properties/aspen-lodge-2.jpg",
"alt": "Mountain view from room"
}
]
}
}
]Content Types
Perk Items (itemType: "perk")
- Purpose: Display membership tier benefits and perks
- Personalization: Based on current membership tier
- Examples: Lounge access, room upgrades, late checkout
Offer Items (itemType: "offer")
- Purpose: Show available point redemption offers
- Personalization: Based on point balance and preferences
- Examples: Spa treatments, dining credits, experience packages
Stay Items (itemType: "stay-past" | "stay-current" | "stay-upcoming")
- Purpose: Highlight past, current, or upcoming reservations
- stay-past: Completed stays with rebooking options
- stay-current: Active reservations with check-in details
- stay-upcoming: Future reservations with trip planning
Property Items (itemType: "property")
- Purpose: Featured properties for discovery
- Personalization: Based on search history and preferences
- Examples: New properties, trending destinations, seasonal recommendations
Personalization Factors
Membership Tier
- Benefits: Tier-specific perks highlighted
- Offers: Tier-exclusive redemption options
- Properties: Access to tier-restricted properties
Booking History
- Past Stays: Similar properties recommended
- Preferences: Property types and locations
- Seasonal Patterns: Timing-based recommendations
Current Context
- Active Bookings: Trip-related content prioritized
- Point Balance: Affordable offers highlighted
- Location: Regional properties and offers
Use Cases
Hero Carousel
const FeaturedHeroCarousel = () => {
const [featuredContent, setFeaturedContent] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadFeaturedContent = async () => {
try {
const response = await fetch('/api/members/me/features/discover/featured?propertiesLimit=5');
const content = await response.json();
setFeaturedContent(content);
} catch (error) {
console.error('Failed to load featured content:', error);
} finally {
setLoading(false);
}
};
loadFeaturedContent();
}, []);
if (loading) return <FeaturedContentSkeleton />;
return (
<Carousel>
{featuredContent.map((item, index) => (
<FeaturedCard key={index} item={item} />
))}
</Carousel>
);
};Content Type Handling
const FeaturedCard = ({ item }) => {
switch (item.itemType) {
case 'perk':
return <PerkCard perk={item.itemData} />;
case 'offer':
return <OfferCard offer={item.itemData} />;
case 'stay-upcoming':
return <UpcomingStayCard stay={item.itemData} />;
case 'stay-current':
return <CurrentStayCard stay={item.itemData} />;
case 'stay-past':
return <PastStayCard stay={item.itemData} />;
case 'property':
return <PropertyCard property={item.itemData} />;
default:
return null;
}
};Personalized Loading
const loadPersonalizedFeatured = async (preferences = {}) => {
const params = new URLSearchParams();
// Limit properties based on screen size
const isMobile = window.innerWidth < 768;
params.set('propertiesLimit', isMobile ? '3' : '8');
// Offset for pagination
if (preferences.offset) {
params.set('propertiesOffset', preferences.offset.toString());
}
const response = await fetch(`/api/members/me/features/discover/featured?${params}`);
return response.json();
};Content Filtering
Client-side Filtering
const filterFeaturedContent = (content, filters) => {
return content.filter(item => {
// Filter by content type
if (filters.contentTypes && !filters.contentTypes.includes(item.itemType)) {
return false;
}
// Filter offers by point requirements
if (item.itemType === 'offer' && filters.maxPoints) {
if (item.itemData.pointsRequired > filters.maxPoints) {
return false;
}
}
// Filter properties by price range
if (item.itemType === 'property' && filters.priceRange) {
const price = item.itemData.nightlyCost;
if (price < filters.priceRange.min || price > filters.priceRange.max) {
return false;
}
}
return true;
});
};Content Prioritization
const prioritizeFeaturedContent = (content, userContext) => {
return content.sort((a, b) => {
// Prioritize upcoming stays
if (a.itemType.startsWith('stay-') && !b.itemType.startsWith('stay-')) {
return -1;
}
// Prioritize affordable offers
if (a.itemType === 'offer' && b.itemType === 'offer') {
const aAffordable = a.itemData.pointsRequired <= userContext.pointBalance;
const bAffordable = b.itemData.pointsRequired <= userContext.pointBalance;
if (aAffordable && !bAffordable) return -1;
if (!aAffordable && bAffordable) return 1;
}
return 0;
});
};Performance Optimization
Caching Strategy
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
const featuredContentCache = new Map();
const getCachedFeaturedContent = async (userId, params = {}) => {
const cacheKey = `featured_${userId}_${JSON.stringify(params)}`;
const cached = featuredContentCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
const fresh = await loadPersonalizedFeatured(params);
featuredContentCache.set(cacheKey, {
data: fresh,
timestamp: Date.now()
});
return fresh;
};Progressive Loading
const useProgressiveFeaturedContent = () => {
const [content, setContent] = useState([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const loadInitial = useCallback(async () => {
setLoading(true);
try {
const initial = await fetch('/api/members/me/features/discover/featured?propertiesLimit=3');
const data = await initial.json();
setContent(data);
} finally {
setLoading(false);
}
}, []);
const loadMore = useCallback(async () => {
setLoadingMore(true);
try {
const more = await fetch(`/api/members/me/features/discover/featured?propertiesLimit=5&propertiesOffset=${content.length}`);
const data = await more.json();
setContent(prev => [...prev, ...data]);
} finally {
setLoadingMore(false);
}
}, [content.length]);
return { content, loading, loadingMore, loadInitial, loadMore };
};Related Endpoints
- Get Signature Stays - Premium curated properties
- Get New to Journey - Recently added properties
- Get Inspiring Stays - Grouped collections with themes
- Get Earn More Gallery - Properties with bonus campaigns