Journey Docs
Public APIAvailabilities

Get Property Availability

Get unified availability for a single property from multiple sources

GET/public/properties/{propertyId}/availabilities

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

No authentication required. This is a public endpoint.

Path Parameters

propertyIdpathstringrequired

Property ID (internal numeric ID or document ID)

Query Parameters

dateFromquerystring

Start date filter (YYYY-MM-DD format)

dateToquerystring

End date filter (YYYY-MM-DD format)

checkBookingConflictsquerybooleandefault: true

Whether to check for booking conflicts (pending payment intents and confirmed bookings)

Request

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

Response

propertyIdstringrequired

Property ID that was queried

sourcestringrequired

Source of the availability data
Options: liteapi, strapi, none

availabilitiesarrayrequired

Availability data for each day

availability items
datestringrequired

Date in YYYY-MM-DD format

pricenumberrequired

Nightly rate in USD

isAvailablebooleanrequired

Whether the date is available for booking

restrictionsobject

Stay restrictions for this date

restrictions object
minNightsnumberrequired

Minimum nights required for stay starting on this date

maxNightsnumber

Maximum nights allowed for stay starting on this date (null if unlimited)

closedForArrivalboolean

Whether arrivals are closed for this date

closedForDepartureboolean

Whether departures are closed for this date

lastSyncAtstring

When the data was last synced (ISO timestamp, null if never synced)

bookingConflictsarrayrequired

Booking conflicts affecting availability

conflict items
checkInDatestringrequired

Check-in date of the conflicting booking

checkOutDatestringrequired

Check-out date of the conflicting booking

typestringrequired

Type of booking conflict
Options: pending_payment_intent, confirmed_booking

expiresAtstring

When the hold expires (only for pending payment intents)

metaobjectrequired

Response metadata

meta object
resolvedIdsobjectrequired

Resolved property IDs across all sources

resolved IDs
documentIdstring

Strapi document ID (null if not found)

liteapiIdstring

LiteAPI property ID (null if not found)

pmsIdstring

PMS system ID (null if not found)

hostNamestring

Host name identifier (null if not found)

sourcesCheckedstring[]required

Sources that were checked for availability data

Response

{
  "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"]
  }
}

Use Cases

Availability Calendar

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

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

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

Source Priority

  1. LiteAPI: Real-time hotel inventory systems
  2. Strapi: Property management system data
  3. None: No availability data found

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

  • Real-time: LiteAPI provides live data
  • Batch Sync: Strapi data synced periodically
  • Conflict Detection: Real-time checking against active bookings

Booking Conflicts

Conflict Types

  • Pending Payment Intent: Temporary hold on inventory, expires automatically
  • Confirmed Booking: Definite reservation, blocks availability

Conflict Resolution

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;
  });
};

On this page