# Get Property Details
> Source: /api-reference/public/properties/get-property-details
> Get comprehensive property information without authentication
> Endpoint: GET /public/properties/{propertyId}

## Overview [#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 [#authentication]

No authentication required. This is a public endpoint.

## Path Parameters [#path-parameters]

<ParamField path="propertyId" type="string" required>
  Property ID (internal numeric ID or document ID)
</ParamField>

## Query Parameters [#query-parameters]

<ParamField query="hydrate" type="string[]">
  Additional data to include in response
  <br />Options: `suggestedPlaces`, `keyFeatures`, `restaurants`
</ParamField>

<RequestExample>

```bash cURL - Basic Property Details
curl -X GET "https://api.journey.com/v1/public/properties/abc123def456ghi789jkl012"
```

```bash cURL - With Additional Data
curl -X GET "https://api.journey.com/v1/public/properties/abc123def456ghi789jkl012?hydrate=suggestedPlaces&hydrate=keyFeatures&hydrate=restaurants"
```

```typescript TypeScript
const getPropertyDetails = async (propertyId: string, hydrate = []) => {
  const params = new URLSearchParams();
  hydrate.forEach(option => params.append('hydrate', option));
  
  const url = `https://api.journey.com/v1/public/properties/${propertyId}${params.toString() ? `?${params}` : ''}`;
  
  const response = await fetch(url, { method: 'GET' });
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
};
```

```javascript JavaScript
const getPropertyDetails = async (propertyId, hydrate = []) => {
  const params = new URLSearchParams();
  hydrate.forEach(option => params.append('hydrate', option));
  
  const url = `https://api.journey.com/v1/public/properties/${propertyId}${params.toString() ? `?${params}` : ''}`;
  
  const response = await fetch(url, { method: 'GET' });
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
};
```

</RequestExample>

## Response [#response]

<ResponseField name="id" type="number" required>
  Internal property ID
</ResponseField>

<ResponseField name="documentId" type="string" required>
  Strapi document ID
</ResponseField>

<ResponseField name="name" type="string" required>
  Property name
</ResponseField>

<ResponseField name="slug" type="string" required>
  URL-friendly property identifier
</ResponseField>

<ResponseField name="description" type="string">
  Property description
</ResponseField>

<ResponseField name="shortDescription" type="string">
  Brief property description
</ResponseField>

<ResponseField name="propertyType" type="string" required>
  Type of property (hotel, resort, villa, etc.)
</ResponseField>

<ResponseField name="starRating" type="number">
  Star rating (1-5)
</ResponseField>

<ResponseField name="guestRating" type="number">
  Guest review rating
</ResponseField>

<ResponseField name="reviewCount" type="number">
  Number of reviews
</ResponseField>

<ResponseField name="address" type="object" required>
  Property location information
  
  <Expandable title="address object">
    <ResponseField name="street" type="string">
      Street address
    </ResponseField>
    
    <ResponseField name="city" type="string" required>
      City name
    </ResponseField>
    
    <ResponseField name="state" type="string" required>
      State or province
    </ResponseField>
    
    <ResponseField name="country" type="string" required>
      Country name
    </ResponseField>
    
    <ResponseField name="postalCode" type="string">
      Postal/ZIP code
    </ResponseField>
    
    <ResponseField name="latitude" type="number" required>
      Latitude coordinate
    </ResponseField>
    
    <ResponseField name="longitude" type="number" required>
      Longitude coordinate
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="brand" type="object">
  Brand information
  
  <Expandable title="brand object">
    <ResponseField name="id" type="number" required>
      Brand ID
    </ResponseField>
    
    <ResponseField name="name" type="string" required>
      Brand name
    </ResponseField>
    
    <ResponseField name="logo" type="string">
      Brand logo URL
    </ResponseField>
    
    <ResponseField name="description" type="string">
      Brand description
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="heroImage" type="object">
  Main property image
  
  <Expandable title="hero image">
    <ResponseField name="url" type="string" required>
      Full resolution image URL
    </ResponseField>
    
    <ResponseField name="thumbnailUrl" type="string">
      Thumbnail image URL
    </ResponseField>
    
    <ResponseField name="alt" type="string">
      Alt text for accessibility
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="gallery" type="array">
  Property image gallery
  
  <Expandable title="gallery items">
    <ResponseField name="url" type="string" required>
      Image URL
    </ResponseField>
    
    <ResponseField name="thumbnailUrl" type="string">
      Thumbnail URL
    </ResponseField>
    
    <ResponseField name="alt" type="string">
      Alt text
    </ResponseField>
    
    <ResponseField name="type" type="string">
      Image type (exterior, interior, amenity, etc.)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="amenities" type="string[]" required>
  List of property amenities
</ResponseField>

<ResponseField name="policies" type="object">
  Property policies
  
  <Expandable title="policies object">
    <ResponseField name="checkIn" type="string">
      Check-in time
    </ResponseField>
    
    <ResponseField name="checkOut" type="string">
      Check-out time
    </ResponseField>
    
    <ResponseField name="petPolicy" type="string">
      Pet policy details
    </ResponseField>
    
    <ResponseField name="smokingPolicy" type="string">
      Smoking policy
    </ResponseField>
    
    <ResponseField name="childPolicy" type="string">
      Children policy
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="contactInfo" type="object">
  Property contact information
  
  <Expandable title="contact info">
    <ResponseField name="phone" type="string">
      Primary phone number
    </ResponseField>
    
    <ResponseField name="email" type="string">
      Contact email
    </ResponseField>
    
    <ResponseField name="website" type="string">
      Property website URL
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="suggestedPlaces" type="array">
  Nearby places of interest (when hydrated)
  
  <Expandable title="suggested places">
    <ResponseField name="name" type="string" required>
      Place name
    </ResponseField>
    
    <ResponseField name="category" type="string">
      Place category (restaurant, attraction, etc.)
    </ResponseField>
    
    <ResponseField name="distance" type="number">
      Distance in kilometers
    </ResponseField>
    
    <ResponseField name="rating" type="number">
      Place rating
    </ResponseField>
    
    <ResponseField name="description" type="string">
      Place description
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="keyFeatures" type="array">
  Property key features (when hydrated)
  
  <Expandable title="key features">
    <ResponseField name="title" type="string" required>
      Feature title
    </ResponseField>
    
    <ResponseField name="description" type="string">
      Feature description
    </ResponseField>
    
    <ResponseField name="icon" type="string">
      Feature icon identifier
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="restaurants" type="array">
  On-property restaurants (when hydrated)
  
  <Expandable title="restaurants">
    <ResponseField name="name" type="string" required>
      Restaurant name
    </ResponseField>
    
    <ResponseField name="cuisine" type="string">
      Cuisine type
    </ResponseField>
    
    <ResponseField name="description" type="string">
      Restaurant description
    </ResponseField>
    
    <ResponseField name="hours" type="object">
      Operating hours
    </ResponseField>
    
    <ResponseField name="reservationsRequired" type="boolean">
      Whether reservations are required
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>

```json 200 Response - Basic Property Details
{
  "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"
  }
}
```

```json 200 Response - With Hydrated Data
{
  "id": 12345,
  "documentId": "abc123def456ghi789jkl012",
  "name": "Journey Beach Resort & Spa",
  "slug": "journey-beach-resort-spa",
  "description": "An oceanfront paradise offering luxury accommodations...",
  "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"
    }
  ],
  "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"
  },
  "suggestedPlaces": [
    {
      "name": "Chichen Itza",
      "category": "Historical Site",
      "distance": 120.5,
      "rating": 4.8,
      "description": "Ancient Maya archaeological site and UNESCO World Heritage Site"
    },
    {
      "name": "Xcaret Park",
      "category": "Theme Park",
      "distance": 8.2,
      "rating": 4.6,
      "description": "Eco-archaeological park with cultural shows and natural attractions"
    },
    {
      "name": "Downtown Cancun",
      "category": "Shopping & Dining",
      "distance": 15.3,
      "rating": 4.2,
      "description": "Local markets, authentic restaurants, and cultural experiences"
    }
  ],
  "keyFeatures": [
    {
      "title": "Oceanfront Location",
      "description": "Direct beach access with panoramic ocean views from all rooms",
      "icon": "ocean"
    },
    {
      "title": "Award-Winning Spa",
      "description": "Full-service spa with traditional Mayan treatments and modern therapies",
      "icon": "spa"
    },
    {
      "title": "Gourmet Dining",
      "description": "Multiple restaurants featuring international and local cuisine",
      "icon": "restaurant"
    },
    {
      "title": "Family Friendly",
      "description": "Kids' club, family suites, and activities for all ages",
      "icon": "family"
    }
  ],
  "restaurants": [
    {
      "name": "Marea",
      "cuisine": "Mediterranean",
      "description": "Oceanfront fine dining with fresh seafood and Mediterranean flavors",
      "hours": {
        "dinner": "6:00 PM - 10:00 PM",
        "closed": "Monday"
      },
      "reservationsRequired": true
    },
    {
      "name": "Coba Café",
      "cuisine": "International",
      "description": "Casual all-day dining with international favorites and local specialties",
      "hours": {
        "breakfast": "6:00 AM - 11:00 AM",
        "lunch": "12:00 PM - 3:00 PM",
        "dinner": "6:00 PM - 9:00 PM"
      },
      "reservationsRequired": false
    },
    {
      "name": "Sunset Bar",
      "cuisine": "Bar & Lounge",
      "description": "Beachfront bar with craft cocktails and light bites",
      "hours": {
        "open": "11:00 AM - 11:00 PM"
      },
      "reservationsRequired": false
    }
  ]
}
```

```json 404 Error - Property Not Found
{
  "statusCode": 404,
  "message": "Property not found",
  "error": "Not Found"
}
```

</ResponseExample>

## Alternative Endpoint: By Slug [#alternative-endpoint-by-slug]

You can also retrieve property details using a URL-friendly slug:

<ParamField path="slug" type="string" required>
  Property slug (URL-friendly identifier)
</ParamField>

<RequestExample>

```bash cURL - Get by Slug
curl -X GET "https://api.journey.com/v1/public/properties/by-slug/journey-beach-resort-spa?hydrate=keyFeatures"
```

```typescript TypeScript
const getPropertyBySlug = async (slug: string, hydrate = []) => {
  const params = new URLSearchParams();
  hydrate.forEach(option => params.append('hydrate', option));
  
  const url = `https://api.journey.com/v1/public/properties/by-slug/${slug}${params.toString() ? `?${params}` : ''}`;
  
  const response = await fetch(url, { method: 'GET' });
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
};
```

</RequestExample>

## Use Cases [#use-cases]

### Property Detail Page [#property-detail-page]
```typescript
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 [#seo-friendly-urls]
```typescript
// 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 [#data-optimization]

### Selective Hydration [#selective-hydration]
```typescript
// 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 [#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
