# Get Property Availability
> Source: /api-reference/public/availabilities/get-property-availability
> Get unified availability for a single property from multiple sources
> Endpoint: GET /public/properties/{propertyId}/availabilities

## Overview [#overview]

Returns availability data from the highest priority source (LiteAPI > Strapi) for a single property. Includes booking conflicts from pending payment intents and confirmed bookings to provide accurate real-time availability.

## 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="dateFrom" type="string">
  Start date filter (YYYY-MM-DD format)
</ParamField>

<ParamField query="dateTo" type="string">
  End date filter (YYYY-MM-DD format)
</ParamField>

<ParamField query="checkBookingConflicts" type="boolean" default="true">
  Whether to check for booking conflicts (pending payment intents and confirmed bookings)
</ParamField>

<RequestExample>

```bash cURL
curl -X GET "https://api.journey.com/v1/public/properties/abc123/availabilities?dateFrom=2025-01-15&dateTo=2025-03-15&checkBookingConflicts=true"
```

```typescript TypeScript
const getPropertyAvailability = async (propertyId: string, options = {}) => {
  const params = new URLSearchParams();
  
  if (options.dateFrom) params.append('dateFrom', options.dateFrom);
  if (options.dateTo) params.append('dateTo', options.dateTo);
  if (options.checkBookingConflicts !== undefined) {
    params.append('checkBookingConflicts', options.checkBookingConflicts.toString());
  }
  
  const url = `https://api.journey.com/v1/public/properties/${propertyId}/availabilities${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 getPropertyAvailability = async (propertyId, options = {}) => {
  const params = new URLSearchParams();
  
  if (options.dateFrom) params.append('dateFrom', options.dateFrom);
  if (options.dateTo) params.append('dateTo', options.dateTo);
  if (options.checkBookingConflicts !== undefined) {
    params.append('checkBookingConflicts', options.checkBookingConflicts.toString());
  }
  
  const url = `https://api.journey.com/v1/public/properties/${propertyId}/availabilities${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="propertyId" type="string" required>
  Property ID that was queried
</ResponseField>

<ResponseField name="source" type="string" required>
  Source of the availability data
  <br />Options: `liteapi`, `strapi`, `none`
</ResponseField>

<ResponseField name="availabilities" type="array" required>
  Availability data for each day
  
  <Expandable title="availability items">
    <ResponseField name="date" type="string" required>
      Date in YYYY-MM-DD format
    </ResponseField>
    
    <ResponseField name="price" type="number" required>
      Nightly rate in USD
    </ResponseField>
    
    <ResponseField name="isAvailable" type="boolean" required>
      Whether the date is available for booking
    </ResponseField>
    
    <ResponseField name="restrictions" type="object">
      Stay restrictions for this date
      
      <Expandable title="restrictions object">
        <ResponseField name="minNights" type="number" required>
          Minimum nights required for stay starting on this date
        </ResponseField>
        
        <ResponseField name="maxNights" type="number">
          Maximum nights allowed for stay starting on this date (null if unlimited)
        </ResponseField>
        
        <ResponseField name="closedForArrival" type="boolean">
          Whether arrivals are closed for this date
        </ResponseField>
        
        <ResponseField name="closedForDeparture" type="boolean">
          Whether departures are closed for this date
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="lastSyncAt" type="string">
  When the data was last synced (ISO timestamp, null if never synced)
</ResponseField>

<ResponseField name="bookingConflicts" type="array" required>
  Booking conflicts affecting availability
  
  <Expandable title="conflict items">
    <ResponseField name="checkInDate" type="string" required>
      Check-in date of the conflicting booking
    </ResponseField>
    
    <ResponseField name="checkOutDate" type="string" required>
      Check-out date of the conflicting booking
    </ResponseField>
    
    <ResponseField name="type" type="string" required>
      Type of booking conflict
      <br />Options: `pending_payment_intent`, `confirmed_booking`
    </ResponseField>
    
    <ResponseField name="expiresAt" type="string">
      When the hold expires (only for pending payment intents)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object" required>
  Response metadata
  
  <Expandable title="meta object">
    <ResponseField name="resolvedIds" type="object" required>
      Resolved property IDs across all sources
      
      <Expandable title="resolved IDs">
        <ResponseField name="documentId" type="string">
          Strapi document ID (null if not found)
        </ResponseField>
        
        <ResponseField name="liteapiId" type="string">
          LiteAPI property ID (null if not found)
        </ResponseField>
        
        <ResponseField name="pmsId" type="string">
          PMS system ID (null if not found)
        </ResponseField>
        
        <ResponseField name="hostName" type="string">
          Host name identifier (null if not found)
        </ResponseField>
      </Expandable>
    </ResponseField>
    
    <ResponseField name="sourcesChecked" type="string[]" required>
      Sources that were checked for availability data
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>

```json 200 Response - LiteAPI Data with Conflicts
{
  "propertyId": "abc123",
  "source": "liteapi",
  "availabilities": [
    {
      "date": "2025-01-15",
      "price": 250.00,
      "isAvailable": true,
      "restrictions": {
        "minNights": 2,
        "maxNights": 14,
        "closedForArrival": false,
        "closedForDeparture": false
      }
    },
    {
      "date": "2025-01-16",
      "price": 275.00,
      "isAvailable": false,
      "restrictions": {
        "minNights": 2,
        "maxNights": 14,
        "closedForArrival": false,
        "closedForDeparture": false
      }
    },
    {
      "date": "2025-01-17",
      "price": 275.00,
      "isAvailable": true,
      "restrictions": {
        "minNights": 1,
        "maxNights": 14,
        "closedForArrival": false,
        "closedForDeparture": false
      }
    }
  ],
  "lastSyncAt": "2025-01-14T08:30:00Z",
  "bookingConflicts": [
    {
      "checkInDate": "2025-01-20",
      "checkOutDate": "2025-01-25",
      "type": "pending_payment_intent",
      "expiresAt": "2025-01-15T10:30:00Z"
    },
    {
      "checkInDate": "2025-02-01",
      "checkOutDate": "2025-02-05",
      "type": "confirmed_booking"
    }
  ],
  "meta": {
    "resolvedIds": {
      "documentId": "abc123def456ghi789jkl012",
      "liteapiId": "HTL_12345",
      "pmsId": "prop_001",
      "hostName": "boutique-resort"
    },
    "sourcesChecked": ["liteapi", "strapi"]
  }
}
```

```json 200 Response - Strapi Data Only
{
  "propertyId": "xyz789",
  "source": "strapi",
  "availabilities": [
    {
      "date": "2025-01-15",
      "price": 180.00,
      "isAvailable": true,
      "restrictions": {
        "minNights": 1,
        "maxNights": null,
        "closedForArrival": false,
        "closedForDeparture": false
      }
    },
    {
      "date": "2025-01-16",
      "price": 180.00,
      "isAvailable": true,
      "restrictions": {
        "minNights": 1,
        "maxNights": null,
        "closedForArrival": false,
        "closedForDeparture": false
      }
    }
  ],
  "lastSyncAt": "2025-01-13T14:22:00Z",
  "bookingConflicts": [],
  "meta": {
    "resolvedIds": {
      "documentId": "xyz789abc012def345ghi678",
      "liteapiId": null,
      "pmsId": "prop_002",
      "hostName": "mountain-lodge"
    },
    "sourcesChecked": ["liteapi", "strapi"]
  }
}
```

```json 200 Response - No Data Available
{
  "propertyId": "notfound",
  "source": "none",
  "availabilities": [],
  "lastSyncAt": null,
  "bookingConflicts": [],
  "meta": {
    "resolvedIds": {
      "documentId": null,
      "liteapiId": null,
      "pmsId": null,
      "hostName": null
    },
    "sourcesChecked": ["liteapi", "strapi"]
  }
}
```

```json 400 Error - Invalid Date Format
{
  "statusCode": 400,
  "message": [
    "dateFrom must be a valid ISO 8601 date string"
  ],
  "error": "Bad Request"
}
```

</ResponseExample>

## Use Cases [#use-cases]

### Availability Calendar [#availability-calendar]
```typescript
const AvailabilityCalendar = ({ propertyId, month, year }) => {
  const [availability, setAvailability] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchAvailability = async () => {
      const startDate = `${year}-${month.toString().padStart(2, '0')}-01`;
      const endDate = new Date(year, month, 0).toISOString().split('T')[0];
      
      try {
        const data = await getPropertyAvailability(propertyId, {
          dateFrom: startDate,
          dateTo: endDate,
          checkBookingConflicts: true
        });
        setAvailability(data);
      } catch (error) {
        console.error('Failed to fetch availability:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchAvailability();
  }, [propertyId, month, year]);
  
  if (loading) return <div>Loading availability...</div>;
  if (!availability) return <div>No availability data</div>;
  
  const renderDay = (date) => {
    const dayData = availability.availabilities.find(a => a.date === date);
    if (!dayData) return null;
    
    const hasConflict = availability.bookingConflicts.some(conflict => 
      date >= conflict.checkInDate && date < conflict.checkOutDate
    );
    
    return (
      <div 
        className={`calendar-day ${dayData.isAvailable ? 'available' : 'unavailable'} ${hasConflict ? 'conflict' : ''}`}
        title={`$${dayData.price} per night`}
      >
        <div className="date">{new Date(date).getDate()}</div>
        <div className="price">${dayData.price}</div>
        {dayData.restrictions.minNights > 1 && (
          <div className="min-nights">{dayData.restrictions.minNights}n min</div>
        )}
      </div>
    );
  };
  
  return (
    <div className="availability-calendar">
      <div className="source-info">
        Data from: {availability.source}
        {availability.lastSyncAt && (
          <span>Last updated: {new Date(availability.lastSyncAt).toLocaleString()}</span>
        )}
      </div>
      
      {/* Calendar grid rendering logic */}
      <div className="calendar-grid">
        {availability.availabilities.map(day => renderDay(day.date))}
      </div>
      
      {availability.bookingConflicts.length > 0 && (
        <div className="conflicts-notice">
          <h4>Booking Conflicts:</h4>
          {availability.bookingConflicts.map((conflict, index) => (
            <div key={index} className="conflict-item">
              {conflict.checkInDate} to {conflict.checkOutDate} 
              ({conflict.type})
              {conflict.expiresAt && (
                <span> - expires {new Date(conflict.expiresAt).toLocaleString()}</span>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
};
```

### Booking Validation [#booking-validation]
```typescript
const validateBookingDates = (availability, checkIn, checkOut) => {
  const checkInData = availability.availabilities.find(a => a.date === checkIn);
  const stayDates = availability.availabilities.filter(a => 
    a.date >= checkIn && a.date < checkOut
  );
  
  const validation = {
    valid: true,
    errors: [],
    warnings: [],
    totalCost: 0
  };
  
  // Check if check-in date exists and is available
  if (!checkInData) {
    validation.valid = false;
    validation.errors.push('Check-in date not found in availability data');
    return validation;
  }
  
  if (!checkInData.isAvailable) {
    validation.valid = false;
    validation.errors.push('Check-in date is not available');
  }
  
  if (checkInData.restrictions?.closedForArrival) {
    validation.valid = false;
    validation.errors.push('Check-in date is closed for arrivals');
  }
  
  // Check minimum nights requirement
  const nights = stayDates.length;
  if (checkInData.restrictions?.minNights > nights) {
    validation.valid = false;
    validation.errors.push(`Minimum ${checkInData.restrictions.minNights} nights required`);
  }
  
  // Check maximum nights requirement
  if (checkInData.restrictions?.maxNights && nights > checkInData.restrictions.maxNights) {
    validation.valid = false;
    validation.errors.push(`Maximum ${checkInData.restrictions.maxNights} nights allowed`);
  }
  
  // Check all stay dates are available
  const unavailableDates = stayDates.filter(day => !day.isAvailable);
  if (unavailableDates.length > 0) {
    validation.valid = false;
    validation.errors.push(`Unavailable dates: ${unavailableDates.map(d => d.date).join(', ')}`);
  }
  
  // Check for booking conflicts
  const conflicts = availability.bookingConflicts.filter(conflict => {
    const conflictStart = new Date(conflict.checkInDate);
    const conflictEnd = new Date(conflict.checkOutDate);
    const stayStart = new Date(checkIn);
    const stayEnd = new Date(checkOut);
    
    return stayStart < conflictEnd && stayEnd > conflictStart;
  });
  
  if (conflicts.length > 0) {
    const pendingConflicts = conflicts.filter(c => c.type === 'pending_payment_intent');
    const confirmedConflicts = conflicts.filter(c => c.type === 'confirmed_booking');
    
    if (confirmedConflicts.length > 0) {
      validation.valid = false;
      validation.errors.push('Dates conflict with confirmed booking');
    }
    
    if (pendingConflicts.length > 0) {
      validation.warnings.push('Dates have pending payment intent (may expire soon)');
    }
  }
  
  // Calculate total cost
  validation.totalCost = stayDates.reduce((sum, day) => sum + day.price, 0);
  
  return validation;
};
```

### Price Trend Analysis [#price-trend-analysis]
```typescript
const analyzePriceTrends = (availability) => {
  const prices = availability.availabilities
    .filter(day => day.isAvailable)
    .map(day => ({ date: day.date, price: day.price }));
    
  if (prices.length === 0) return null;
  
  const minPrice = Math.min(...prices.map(p => p.price));
  const maxPrice = Math.max(...prices.map(p => p.price));
  const avgPrice = prices.reduce((sum, p) => sum + p.price, 0) / prices.length;
  
  const bestValueDates = prices
    .filter(p => p.price === minPrice)
    .map(p => p.date);
    
  const premiumDates = prices
    .filter(p => p.price === maxPrice)
    .map(p => p.date);
  
  return {
    minPrice,
    maxPrice,
    avgPrice: Math.round(avgPrice * 100) / 100,
    bestValueDates,
    premiumDates,
    priceSpread: maxPrice - minPrice,
    availability: {
      total: availability.availabilities.length,
      available: prices.length,
      percentage: Math.round((prices.length / availability.availabilities.length) * 100)
    }
  };
};
```

## Data Sources [#data-sources]

### Source Priority [#source-priority]
1. **LiteAPI**: Real-time hotel inventory systems
2. **Strapi**: Property management system data
3. **None**: No availability data found

### Source Characteristics [#source-characteristics]
- **LiteAPI**: Real-time rates and availability, industry standard
- **Strapi**: Direct property data, may include unique inventory
- **Conflict Checking**: Cross-source booking conflict detection

### Data Freshness [#data-freshness]
- **Real-time**: LiteAPI provides live data
- **Batch Sync**: Strapi data synced periodically
- **Conflict Detection**: Real-time checking against active bookings

## Booking Conflicts [#booking-conflicts]

### Conflict Types [#conflict-types]
- **Pending Payment Intent**: Temporary hold on inventory, expires automatically
- **Confirmed Booking**: Definite reservation, blocks availability

### Conflict Resolution [#conflict-resolution]
```typescript
const resolveConflicts = (conflicts) => {
  const now = new Date();
  
  return conflicts.map(conflict => {
    if (conflict.type === 'pending_payment_intent' && conflict.expiresAt) {
      const expiresAt = new Date(conflict.expiresAt);
      const isExpired = now > expiresAt;
      
      return {
        ...conflict,
        isExpired,
        remainingTime: isExpired ? 0 : Math.max(0, expiresAt - now)
      };
    }
    
    return conflict;
  });
};
```

## Related Endpoints [#related-endpoints]

- [Get Batch Availability](/api-reference/public/availabilities/get-batch-availability) - Multiple properties at once
- [Get Property Rates](/api-reference/public/rates/get-property-rates) - Detailed pricing information
- [Property Search](/api-reference/public/properties/search) - Search with availability filtering
