Get Property Details
Get comprehensive property information without authentication
/public/properties/{propertyId}Overview
Returns detailed property information including basic details, amenities, location, and optionally hydrated related data such as suggested places, key features, and restaurants. Available to all users without authentication.
Authentication
No authentication required. This is a public endpoint.
Path Parameters
propertyIdpathstringrequiredProperty ID (internal numeric ID or document ID)
Query Parameters
hydratequerystring[]Additional data to include in response
Options: suggestedPlaces, keyFeatures, restaurants
Request
curl -X GET "https://api.journey.com/v1/public/properties/abc123def456ghi789jkl012"Response
idnumberrequiredInternal property ID
documentIdstringrequiredStrapi document ID
namestringrequiredProperty name
slugstringrequiredURL-friendly property identifier
descriptionstringProperty description
shortDescriptionstringBrief property description
propertyTypestringrequiredType of property (hotel, resort, villa, etc.)
starRatingnumberStar rating (1-5)
guestRatingnumberGuest review rating
reviewCountnumberNumber of reviews
addressobjectrequiredProperty location information
address object
streetstringStreet address
citystringrequiredCity name
statestringrequiredState or province
countrystringrequiredCountry name
postalCodestringPostal/ZIP code
latitudenumberrequiredLatitude coordinate
longitudenumberrequiredLongitude coordinate
brandobjectBrand information
brand object
idnumberrequiredBrand ID
namestringrequiredBrand name
logostringBrand logo URL
descriptionstringBrand description
heroImageobjectMain property image
hero image
urlstringrequiredFull resolution image URL
thumbnailUrlstringThumbnail image URL
altstringAlt text for accessibility
galleryarrayProperty image gallery
gallery items
urlstringrequiredImage URL
thumbnailUrlstringThumbnail URL
altstringAlt text
typestringImage type (exterior, interior, amenity, etc.)
amenitiesstring[]requiredList of property amenities
policiesobjectProperty policies
policies object
checkInstringCheck-in time
checkOutstringCheck-out time
petPolicystringPet policy details
smokingPolicystringSmoking policy
childPolicystringChildren policy
contactInfoobjectProperty contact information
contact info
phonestringPrimary phone number
emailstringContact email
websitestringProperty website URL
suggestedPlacesarrayNearby places of interest (when hydrated)
suggested places
namestringrequiredPlace name
categorystringPlace category (restaurant, attraction, etc.)
distancenumberDistance in kilometers
ratingnumberPlace rating
descriptionstringPlace description
keyFeaturesarrayProperty key features (when hydrated)
key features
titlestringrequiredFeature title
descriptionstringFeature description
iconstringFeature icon identifier
restaurantsarrayOn-property restaurants (when hydrated)
restaurants
namestringrequiredRestaurant name
cuisinestringCuisine type
descriptionstringRestaurant description
hoursobjectOperating hours
reservationsRequiredbooleanWhether reservations are required
Response
{
"id": 12345,
"documentId": "abc123def456ghi789jkl012",
"name": "Journey Beach Resort & Spa",
"slug": "journey-beach-resort-spa",
"description": "An oceanfront paradise offering luxury accommodations, world-class dining, and rejuvenating spa experiences. Perfect for romantic getaways and family vacations.",
"shortDescription": "Oceanfront luxury resort with spa and dining",
"propertyType": "Resort",
"starRating": 5,
"guestRating": 4.7,
"reviewCount": 1247,
"address": {
"street": "123 Ocean Drive",
"city": "Cancun",
"state": "Quintana Roo",
"country": "Mexico",
"postalCode": "77500",
"latitude": 21.1619,
"longitude": -86.8515
},
"brand": {
"id": 1,
"name": "Journey Resorts",
"logo": "https://cdn.journey.com/brands/journey-resorts-logo.svg",
"description": "Luxury resorts in breathtaking destinations"
},
"heroImage": {
"url": "https://cdn.journey.com/properties/beach-resort-hero.jpg",
"thumbnailUrl": "https://cdn.journey.com/properties/beach-resort-hero-thumb.jpg",
"alt": "Oceanfront resort with infinity pool at sunset"
},
"gallery": [
{
"url": "https://cdn.journey.com/properties/beach-resort-pool.jpg",
"thumbnailUrl": "https://cdn.journey.com/properties/beach-resort-pool-thumb.jpg",
"alt": "Infinity pool overlooking the ocean",
"type": "amenity"
},
{
"url": "https://cdn.journey.com/properties/beach-resort-suite.jpg",
"thumbnailUrl": "https://cdn.journey.com/properties/beach-resort-suite-thumb.jpg",
"alt": "Ocean view suite interior",
"type": "interior"
}
],
"amenities": [
"Private Beach",
"Infinity Pool",
"Full-Service Spa",
"Fine Dining Restaurant",
"Fitness Center",
"Business Center",
"Concierge Service",
"Room Service",
"WiFi",
"Parking",
"Airport Shuttle"
],
"policies": {
"checkIn": "3:00 PM",
"checkOut": "11:00 AM",
"petPolicy": "Pets not allowed",
"smokingPolicy": "Non-smoking property",
"childPolicy": "Children of all ages welcome"
},
"contactInfo": {
"phone": "+52 998 123 4567",
"email": "reservations@journeybeachresort.com",
"website": "https://www.journeybeachresort.com"
}
}Alternative Endpoint: By Slug
You can also retrieve property details using a URL-friendly slug:
slugpathstringrequiredProperty slug (URL-friendly identifier)
Request
curl -X GET "https://api.journey.com/v1/public/properties/by-slug/journey-beach-resort-spa?hydrate=keyFeatures"Use Cases
Property Detail Page
const PropertyDetailPage = ({ propertyId }) => {
const [property, setProperty] = useState(null);
const [loading, setLoading] = useState(true);
const [selectedImage, setSelectedImage] = useState(0);
useEffect(() => {
const fetchProperty = async () => {
try {
const data = await getPropertyDetails(propertyId, [
'suggestedPlaces',
'keyFeatures',
'restaurants'
]);
setProperty(data);
} catch (error) {
console.error('Failed to fetch property:', error);
} finally {
setLoading(false);
}
};
fetchProperty();
}, [propertyId]);
if (loading) return <div className="loading">Loading property details...</div>;
if (!property) return <div className="error">Property not found</div>;
return (
<div className="property-detail">
{/* Hero Section */}
<div className="hero-section">
<div className="image-gallery">
<img
src={property.heroImage.url}
alt={property.heroImage.alt}
className="hero-image"
/>
<div className="gallery-thumbnails">
{property.gallery?.map((image, index) => (
<img
key={index}
src={image.thumbnailUrl}
alt={image.alt}
onClick={() => setSelectedImage(index)}
className={selectedImage === index ? 'selected' : ''}
/>
))}
</div>
</div>
<div className="property-header">
<div className="brand-info">
{property.brand?.logo && (
<img src={property.brand.logo} alt={property.brand.name} />
)}
<span>{property.brand?.name}</span>
</div>
<h1>{property.name}</h1>
<p className="short-description">{property.shortDescription}</p>
<div className="rating-info">
<div className="star-rating">
{'★'.repeat(property.starRating || 0)}
{'☆'.repeat(5 - (property.starRating || 0))}
</div>
{property.guestRating && (
<div className="guest-rating">
{property.guestRating}/5 ({property.reviewCount} reviews)
</div>
)}
</div>
<div className="location">
📍 {property.address.city}, {property.address.country}
</div>
</div>
</div>
{/* Key Features */}
{property.keyFeatures && (
<section className="key-features">
<h2>Why Stay Here</h2>
<div className="features-grid">
{property.keyFeatures.map((feature, index) => (
<div key={index} className="feature-card">
<div className={`feature-icon icon-${feature.icon}`}></div>
<h3>{feature.title}</h3>
<p>{feature.description}</p>
</div>
))}
</div>
</section>
)}
{/* Description & Amenities */}
<section className="details">
<div className="description">
<h2>About</h2>
<p>{property.description}</p>
</div>
<div className="amenities">
<h2>Amenities</h2>
<div className="amenities-grid">
{property.amenities.map((amenity, index) => (
<div key={index} className="amenity-item">
{amenity}
</div>
))}
</div>
</div>
</section>
{/* Restaurants */}
{property.restaurants && property.restaurants.length > 0 && (
<section className="restaurants">
<h2>Dining</h2>
<div className="restaurants-list">
{property.restaurants.map((restaurant, index) => (
<div key={index} className="restaurant-card">
<h3>{restaurant.name}</h3>
<div className="cuisine-type">{restaurant.cuisine}</div>
<p>{restaurant.description}</p>
<div className="restaurant-details">
{restaurant.reservationsRequired && (
<span className="reservations-required">
Reservations Required
</span>
)}
{restaurant.hours && (
<div className="hours">
{Object.entries(restaurant.hours).map(([period, time]) => (
<div key={period}>
<strong>{period}:</strong> {time}
</div>
))}
</div>
)}
</div>
</div>
))}
</div>
</section>
)}
{/* Suggested Places */}
{property.suggestedPlaces && property.suggestedPlaces.length > 0 && (
<section className="nearby">
<h2>Explore Nearby</h2>
<div className="places-list">
{property.suggestedPlaces.map((place, index) => (
<div key={index} className="place-card">
<h3>{place.name}</h3>
<div className="place-meta">
<span className="category">{place.category}</span>
<span className="distance">{place.distance}km away</span>
{place.rating && (
<span className="rating">⭐ {place.rating}</span>
)}
</div>
<p>{place.description}</p>
</div>
))}
</div>
</section>
)}
{/* Policies & Contact */}
<section className="policies-contact">
<div className="policies">
<h3>Policies</h3>
<div className="policy-item">
<strong>Check-in:</strong> {property.policies?.checkIn}
</div>
<div className="policy-item">
<strong>Check-out:</strong> {property.policies?.checkOut}
</div>
{property.policies?.petPolicy && (
<div className="policy-item">
<strong>Pets:</strong> {property.policies.petPolicy}
</div>
)}
</div>
<div className="contact">
<h3>Contact</h3>
{property.contactInfo?.phone && (
<div>📞 {property.contactInfo.phone}</div>
)}
{property.contactInfo?.email && (
<div>✉️ {property.contactInfo.email}</div>
)}
{property.contactInfo?.website && (
<div>
<a href={property.contactInfo.website} target="_blank" rel="noopener">
🌐 Visit Website
</a>
</div>
)}
</div>
</section>
</div>
);
};SEO-Friendly URLs
// Use slug-based routing for better SEO
const PropertyRouter = () => {
return (
<Routes>
{/* SEO-friendly route */}
<Route
path="/properties/:slug"
element={<PropertyPageBySlug />}
/>
{/* Fallback for direct ID access */}
<Route
path="/properties/id/:propertyId"
element={<PropertyPageById />}
/>
</Routes>
);
};
const PropertyPageBySlug = () => {
const { slug } = useParams();
const [property, setProperty] = useState(null);
useEffect(() => {
const fetchProperty = async () => {
try {
const data = await getPropertyBySlug(slug, ['keyFeatures']);
setProperty(data);
// Update page title and meta tags
document.title = `${data.name} | Journey Resorts`;
// Update meta description
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) {
metaDesc.content = data.shortDescription || data.description;
}
} catch (error) {
console.error('Property not found:', error);
}
};
fetchProperty();
}, [slug]);
return property ? <PropertyDetailPage property={property} /> : <NotFound />;
};Data Optimization
Selective Hydration
// Only request additional data when needed
const usePropertyDetails = (propertyId, options = {}) => {
const [property, setProperty] = useState(null);
const [loading, setLoading] = useState(true);
const {
includeRestaurants = false,
includeSuggestedPlaces = false,
includeKeyFeatures = false
} = options;
useEffect(() => {
const fetchProperty = async () => {
const hydrate = [];
if (includeRestaurants) hydrate.push('restaurants');
if (includeSuggestedPlaces) hydrate.push('suggestedPlaces');
if (includeKeyFeatures) hydrate.push('keyFeatures');
try {
const data = await getPropertyDetails(propertyId, hydrate);
setProperty(data);
} finally {
setLoading(false);
}
};
fetchProperty();
}, [propertyId, includeRestaurants, includeSuggestedPlaces, includeKeyFeatures]);
return { property, loading };
};Performance Considerations
- Request only needed hydrated data
- Use appropriate image sizes (thumbnail vs full resolution)
- Implement lazy loading for gallery images
- Cache property data for repeated access