# Get Trending Searches
> Source: /api-reference/public/properties/get-trending-searches
> Get popular search destinations across all members
> Endpoint: GET /public/properties/web/search/trending

## Overview [#overview]

Returns trending searches across all members, providing unique results per location ordered by popularity within a specified time window. No authentication required, making this useful for showcasing popular destinations to all users.

## Authentication [#authentication]

No authentication required. This is a public endpoint with rate limiting.

## Rate Limiting [#rate-limiting]

- **Limit**: 20 requests per minute per IP address
- **Window**: 60 seconds
- **Headers**: Rate limit information included in response headers

## Query Parameters [#query-parameters]

<ParamField query="limit" type="number" default="4">
  Number of unique locations to return (1-50)
</ParamField>

<ParamField query="offset" type="number" default="0">
  Number of items to skip for pagination
</ParamField>

<ParamField query="daysBack" type="number" default="30">
  How many days back to count searches (1-365)
</ParamField>

<RequestExample>

```bash cURL - Default Trending
curl -X GET "https://api.journey.com/v1/public/properties/web/search/trending"
```

```bash cURL - Custom Parameters
curl -X GET "https://api.journey.com/v1/public/properties/web/search/trending?limit=10&daysBack=7"
```

```typescript TypeScript
const getTrendingSearches = async (options = {}) => {
  const params = new URLSearchParams();
  
  if (options.limit) params.append('limit', options.limit.toString());
  if (options.offset) params.append('offset', options.offset.toString());
  if (options.daysBack) params.append('daysBack', options.daysBack.toString());
  
  const url = `https://api.journey.com/v1/public/properties/web/search/trending${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 getTrendingSearches = async (options = {}) => {
  const params = new URLSearchParams();
  
  if (options.limit) params.append('limit', options.limit.toString());
  if (options.offset) params.append('offset', options.offset.toString());
  if (options.daysBack) params.append('daysBack', options.daysBack.toString());
  
  const url = `https://api.journey.com/v1/public/properties/web/search/trending${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="items" type="array" required>
  Array of trending search items (one per location)
  
  <Expandable title="trending search items">
    <ResponseField name="location" type="object" required>
      Location object from the search
      
      <Expandable title="location object">
        <ResponseField name="placeId" type="string">
          Google Places ID for location-based searches
        </ResponseField>
        
        <ResponseField name="query" type="string">
          Search query text used
        </ResponseField>
        
        <ResponseField name="bounds" type="object">
          Geographic bounds for map-based searches
          
          <Expandable title="bounds object">
            <ResponseField name="northeast" type="object" required>
              Northeast corner coordinates
            </ResponseField>
            
            <ResponseField name="southwest" type="object" required>
              Southwest corner coordinates  
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
    
    <ResponseField name="searchType" type="string" required>
      Type of search performed
      <br />Options: `location`, `property`, `bounds`
    </ResponseField>
    
    <ResponseField name="checkIn" type="string">
      Check-in date (YYYY-MM-DD) or null if not specified
    </ResponseField>
    
    <ResponseField name="checkOut" type="string">
      Check-out date (YYYY-MM-DD) or null if not specified
    </ResponseField>
    
    <ResponseField name="flexibility" type="string|object">
      Flexibility setting for flexible searches (weekend/week/month or custom object)
    </ResponseField>
    
    <ResponseField name="guests" type="number" required>
      Number of guests searched for
    </ResponseField>
    
    <ResponseField name="filters" type="object" required>
      Search filters applied (price, property types, etc.)
    </ResponseField>
    
    <ResponseField name="createdAt" type="string" required>
      When this trending search pattern was last recorded (ISO timestamp)
    </ResponseField>
    
    <ResponseField name="searchCount" type="number">
      Number of times this pattern was searched (may be omitted for privacy)
    </ResponseField>
    
    <ResponseField name="popularityScore" type="number">
      Relative popularity score (0-100)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="limit" type="number" required>
  Page size (limit) used
</ResponseField>

<ResponseField name="offset" type="number" required>
  Offset for pagination
</ResponseField>

<ResponseField name="total" type="number" required>
  Total number of items returned (may be less than limit)
</ResponseField>

<ResponseExample>

```json 200 Response - Weekly Trending Searches
{
  "items": [
    {
      "location": {
        "placeId": "ChIJ-wSojYBEQogR5PMnOeL97CE",
        "query": "Cancun, Mexico"
      },
      "searchType": "location",
      "checkIn": null,
      "checkOut": null,
      "flexibility": "weekend",
      "guests": 2,
      "filters": {
        "price": {
          "min": 200,
          "max": 600
        },
        "propertyTypes": ["resort"]
      },
      "createdAt": "2025-01-15T14:30:00.000Z",
      "popularityScore": 95
    },
    {
      "location": {
        "placeId": "ChIJ88g-4Y8FGGERd8UiUqT7AUl",
        "query": "Tulum, Mexico"
      },
      "searchType": "location",
      "checkIn": "2025-02-14",
      "checkOut": "2025-02-17",
      "flexibility": null,
      "guests": 2,
      "filters": {
        "price": {
          "min": 150,
          "max": 400
        },
        "predefinedFilters": [1, 3]
      },
      "createdAt": "2025-01-15T12:45:00.000Z",
      "popularityScore": 87
    },
    {
      "location": {
        "placeId": "ChIJdd4hrwm2hEcRmSrV3Vo6llI",
        "query": "Rome, Italy"
      },
      "searchType": "location",
      "checkIn": null,
      "checkOut": null,
      "flexibility": {
        "months": ["2025-04", "2025-05", "2025-06"]
      },
      "guests": 4,
      "filters": {
        "propertyTypes": ["hotel", "boutique"],
        "rooms": {
          "bedrooms": 2
        }
      },
      "createdAt": "2025-01-15T10:20:00.000Z",
      "popularityScore": 82
    },
    {
      "location": {
        "placeId": "ChIJOwg_06VPwokRYv534QaPC8g",
        "query": "New York, NY"
      },
      "searchType": "location",
      "checkIn": "2025-03-15",
      "checkOut": "2025-03-18",
      "flexibility": null,
      "guests": 1,
      "filters": {
        "price": {
          "max": 300
        },
        "predefinedFilters": [2, 5]
      },
      "createdAt": "2025-01-15T09:15:00.000Z",
      "popularityScore": 78
    }
  ],
  "limit": 4,
  "offset": 0,
  "total": 4
}
```

```json 200 Response - Monthly Trends with Bounds
{
  "items": [
    {
      "location": {
        "bounds": {
          "northeast": {
            "latitude": 25.3311,
            "longitude": -80.1694
          },
          "southwest": {
            "latitude": 25.2866,
            "longitude": -80.4306
          }
        }
      },
      "searchType": "bounds",
      "checkIn": null,
      "checkOut": null,
      "flexibility": "week",
      "guests": 6,
      "filters": {
        "propertyTypes": ["villa", "resort"],
        "rooms": {
          "bedrooms": 3
        }
      },
      "createdAt": "2025-01-14T16:00:00.000Z",
      "popularityScore": 92
    },
    {
      "location": {
        "placeId": "ChIJmTKBIUpbGGERJnhd_sYhV9w",
        "query": "Paris, France"
      },
      "searchType": "location",
      "checkIn": "2025-06-01",
      "checkOut": "2025-06-05",
      "flexibility": null,
      "guests": 2,
      "filters": {
        "price": {
          "min": 250,
          "max": 500
        },
        "predefinedFilters": [1, 4, 7]
      },
      "createdAt": "2025-01-14T14:30:00.000Z",
      "popularityScore": 89
    }
  ],
  "limit": 10,
  "offset": 0,
  "total": 2
}
```

```json 429 Error - Rate Limit Exceeded
{
  "statusCode": 429,
  "message": "Too many requests",
  "error": "Too Many Requests"
}
```

</ResponseExample>

## Use Cases [#use-cases]

### Trending Destinations Showcase [#trending-destinations-showcase]
```typescript
const TrendingDestinations = ({ timeframe = 'week' }) => {
  const [trending, setTrending] = useState([]);
  const [loading, setLoading] = useState(true);
  
  const daysBack = {
    week: 7,
    month: 30,
    quarter: 90
  }[timeframe] || 30;
  
  useEffect(() => {
    const fetchTrending = async () => {
      setLoading(true);
      try {
        const data = await getTrendingSearches({
          limit: 8,
          daysBack
        });
        setTrending(data.items);
      } catch (error) {
        console.error('Failed to fetch trending searches:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchTrending();
  }, [daysBack]);
  
  const formatLocation = (item) => {
    if (item.location.query) {
      return item.location.query;
    }
    
    if (item.location.bounds) {
      // Try to derive location from bounds (could use reverse geocoding)
      return 'Popular Area';
    }
    
    return 'Unknown Location';
  };
  
  const formatSearchDetails = (item) => {
    const details = [];
    
    if (item.guests > 1) {
      details.push(`${item.guests} guests`);
    }
    
    if (item.flexibility) {
      if (typeof item.flexibility === 'string') {
        details.push(`${item.flexibility} stays`);
      } else if (item.flexibility.months) {
        details.push(`flexible dates`);
      }
    } else if (item.checkIn && item.checkOut) {
      const checkIn = new Date(item.checkIn);
      const checkOut = new Date(item.checkOut);
      const nights = Math.ceil((checkOut - checkIn) / (1000 * 60 * 60 * 24));
      details.push(`${nights} nights`);
    }
    
    if (item.filters.price?.min || item.filters.price?.max) {
      const min = item.filters.price.min || 0;
      const max = item.filters.price.max || '∞';
      details.push(`$${min}-${max}/night`);
    }
    
    return details.join(' • ');
  };
  
  const handleTrendingClick = (item) => {
    // Navigate to search with similar parameters
    const searchParams = {
      location: item.location,
      guests: item.guests,
      filters: item.filters
    };
    
    if (item.flexibility) {
      searchParams.flexibility = item.flexibility;
    } else if (item.checkIn && item.checkOut) {
      searchParams.checkIn = item.checkIn;
      searchParams.checkOut = item.checkOut;
    }
    
    // Navigate to search page
    window.location.href = `/search?${encodeURIComponent(JSON.stringify(searchParams))}`;
  };
  
  if (loading) {
    return (
      <div className="trending-destinations loading">
        <h2>Trending Destinations</h2>
        <div className="loading-placeholder">Loading trending searches...</div>
      </div>
    );
  }
  
  return (
    <div className="trending-destinations">
      <div className="trending-header">
        <h2>Trending This {timeframe.charAt(0).toUpperCase() + timeframe.slice(1)}</h2>
        <p>Discover where Journey members are planning their next adventures</p>
      </div>
      
      <div className="trending-grid">
        {trending.map((item, index) => (
          <div 
            key={index}
            className="trending-card"
            onClick={() => handleTrendingClick(item)}
          >
            <div className="trend-rank">#{index + 1}</div>
            
            <div className="trend-content">
              <h3 className="destination">{formatLocation(item)}</h3>
              
              <div className="search-details">
                {formatSearchDetails(item)}
              </div>
              
              <div className="popularity">
                <div className="popularity-bar">
                  <div 
                    className="popularity-fill"
                    style={{ width: `${item.popularityScore}%` }}
                  />
                </div>
                <span className="popularity-score">{item.popularityScore}% popularity</span>
              </div>
              
              <div className="search-type">
                {item.searchType === 'location' && '📍 Location search'}
                {item.searchType === 'bounds' && '🗺️ Area search'}
                {item.searchType === 'property' && '🏨 Property search'}
              </div>
            </div>
            
            <div className="trend-arrow">→</div>
          </div>
        ))}
      </div>
      
      {trending.length === 0 && (
        <div className="no-trends">
          <h3>No trending searches</h3>
          <p>Check back soon for popular destinations!</p>
        </div>
      )}
    </div>
  );
};
```

### Homepage Inspiration Section [#homepage-inspiration-section]
```typescript
const HomepageInspiration = () => {
  const [weeklyTrends, setWeeklyTrends] = useState([]);
  const [monthlyTrends, setMonthlyTrends] = useState([]);
  
  useEffect(() => {
    const fetchTrends = async () => {
      try {
        const [weekly, monthly] = await Promise.all([
          getTrendingSearches({ limit: 4, daysBack: 7 }),
          getTrendingSearches({ limit: 4, daysBack: 30 })
        ]);
        
        setWeeklyTrends(weekly.items);
        setMonthlyTrends(monthly.items);
      } catch (error) {
        console.error('Failed to fetch trends:', error);
      }
    };
    
    fetchTrends();
  }, []);
  
  const renderTrendingLocation = (item, index) => {
    const location = item.location.query || 'Popular Area';
    const isFlexible = !!item.flexibility;
    const hasSpecificDates = !!(item.checkIn && item.checkOut);
    
    return (
      <div 
        key={index}
        className="inspiration-card"
        onClick={() => navigateToSearch(item)}
      >
        <div className="card-content">
          <h4>{location}</h4>
          
          <div className="search-pattern">
            {item.guests > 1 && <span>{item.guests} guests</span>}
            {isFlexible && <span>Flexible dates</span>}
            {hasSpecificDates && (
              <span>
                {new Date(item.checkIn).toLocaleDateString('en-US', { 
                  month: 'short', day: 'numeric' 
                })} - 
                {new Date(item.checkOut).toLocaleDateString('en-US', { 
                  month: 'short', day: 'numeric' 
                })}
              </span>
            )}
          </div>
          
          <div className="popularity-indicator">
            <div className="trend-badge">
              🔥 {item.popularityScore}% trending
            </div>
          </div>
        </div>
      </div>
    );
  };
  
  return (
    <section className="homepage-inspiration">
      <div className="inspiration-header">
        <h2>Travel Inspiration</h2>
        <p>See where our community is exploring</p>
      </div>
      
      <div className="trends-sections">
        <div className="trend-section">
          <h3>🔥 Hot This Week</h3>
          <div className="trend-cards">
            {weeklyTrends.slice(0, 4).map(renderTrendingLocation)}
          </div>
        </div>
        
        <div className="trend-section">
          <h3>📈 Popular This Month</h3>
          <div className="trend-cards">
            {monthlyTrends.slice(0, 4).map(renderTrendingLocation)}
          </div>
        </div>
      </div>
    </section>
  );
};
```

### Analytics Dashboard (Internal) [#analytics-dashboard-internal]
```typescript
const TrendingAnalytics = () => {
  const [trends, setTrends] = useState([]);
  const [timeframe, setTimeframe] = useState(7);
  const [analytics, setAnalytics] = useState(null);
  
  useEffect(() => {
    const fetchTrendingAnalytics = async () => {
      try {
        const data = await getTrendingSearches({
          limit: 50,
          daysBack: timeframe
        });
        
        setTrends(data.items);
        
        // Calculate analytics
        const analytics = calculateTrendingAnalytics(data.items);
        setAnalytics(analytics);
      } catch (error) {
        console.error('Failed to fetch trending analytics:', error);
      }
    };
    
    fetchTrendingAnalytics();
  }, [timeframe]);
  
  const calculateTrendingAnalytics = (items) => {
    const searchTypes = items.reduce((acc, item) => {
      acc[item.searchType] = (acc[item.searchType] || 0) + 1;
      return acc;
    }, {});
    
    const destinations = items
      .filter(item => item.location.query)
      .map(item => item.location.query);
    
    const avgGuests = items.reduce((sum, item) => sum + item.guests, 0) / items.length;
    
    const flexibleSearches = items.filter(item => item.flexibility).length;
    const dateSpecificSearches = items.filter(item => item.checkIn && item.checkOut).length;
    
    return {
      searchTypes,
      topDestinations: destinations.slice(0, 10),
      avgGuests: Math.round(avgGuests * 10) / 10,
      flexibilityRate: (flexibleSearches / items.length) * 100,
      specificDateRate: (dateSpecificSearches / items.length) * 100
    };
  };
  
  return (
    <div className="trending-analytics">
      <div className="analytics-header">
        <h2>Trending Search Analytics</h2>
        <select 
          value={timeframe}
          onChange={(e) => setTimeframe(parseInt(e.target.value))}
        >
          <option value={7}>Last 7 days</option>
          <option value={30}>Last 30 days</option>
          <option value={90}>Last 90 days</option>
        </select>
      </div>
      
      {analytics && (
        <div className="analytics-grid">
          <div className="analytics-card">
            <h3>Search Types</h3>
            <div className="chart">
              {Object.entries(analytics.searchTypes).map(([type, count]) => (
                <div key={type} className="chart-bar">
                  <span>{type}: {count}</span>
                </div>
              ))}
            </div>
          </div>
          
          <div className="analytics-card">
            <h3>Search Patterns</h3>
            <div className="metrics">
              <div>Avg guests: {analytics.avgGuests}</div>
              <div>Flexible dates: {analytics.flexibilityRate.toFixed(1)}%</div>
              <div>Specific dates: {analytics.specificDateRate.toFixed(1)}%</div>
            </div>
          </div>
          
          <div className="analytics-card">
            <h3>Top Destinations</h3>
            <div className="destination-list">
              {analytics.topDestinations.map((dest, index) => (
                <div key={index}>{index + 1}. {dest}</div>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  );
};
```

## Privacy and Data [#privacy-and-data]

### Privacy Considerations [#privacy-considerations]
- No individual search tracking exposed
- Aggregated and anonymized data only  
- Search counts may be omitted for additional privacy
- Popularity scores normalize individual behavior

### Data Aggregation [#data-aggregation]
- **Unique per location**: Only one result per geographic area
- **Time-based**: Results limited to specified time window
- **Popularity ranking**: Ordered by search frequency and recency
- **Filter normalization**: Common filter patterns highlighted

## Rate Limiting [#rate-limiting-1]

### Implementation [#implementation]
- IP-based rate limiting
- Generous limits for public access
- Headers provide remaining quota information
- Designed for website integration

### Best Practices [#best-practices]
```typescript
const withRateLimit = (apiCall) => {
  return async (...args) => {
    try {
      return await apiCall(...args);
    } catch (error) {
      if (error.status === 429) {
        // Handle rate limit with exponential backoff
        const retryAfter = error.headers?.['Retry-After'] || 60;
        
        console.warn(`Rate limited. Retrying after ${retryAfter}s`);
        
        await new Promise(resolve => 
          setTimeout(resolve, retryAfter * 1000)
        );
        
        return apiCall(...args);
      }
      throw error;
    }
  };
};

const rateLimitedGetTrending = withRateLimit(getTrendingSearches);
```

## Related Endpoints [#related-endpoints]

- [Property Search](/api-reference/public/properties/search) - Execute searches based on trending patterns
