# Get Reservation Preview
> Source: /api-reference/public/reservations/get-reservation-preview
> Get basic reservation information for claiming by guests
> Endpoint: GET /public/reservations/{externalId}/preview

## Overview [#overview]

Returns basic reservation information including guest name, property details, and points earned. Used by guests to preview their reservation before claiming it in the Journey loyalty program.

## Authentication [#authentication]

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

## Rate Limiting [#rate-limiting]

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

## Path Parameters [#path-parameters]

<ParamField path="externalId" type="string" required>
  External reservation ID (UUID format)
</ParamField>

<RequestExample>

```bash cURL
curl -X GET "https://api.journey.com/v1/public/reservations/12345678-1234-5678-9012-123456789012/preview"
```

```typescript TypeScript
const getReservationPreview = async (externalId: string) => {
  const response = await fetch(`https://api.journey.com/v1/public/reservations/${externalId}/preview`, {
    method: 'GET'
  });
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
};
```

```javascript JavaScript
const getReservationPreview = async (externalId) => {
  const response = await fetch(`https://api.journey.com/v1/public/reservations/${externalId}/preview`, {
    method: 'GET'
  });
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
};
```

</RequestExample>

## Response [#response]

<ResponseField name="externalId" type="string" required>
  External reservation identifier
</ResponseField>

<ResponseField name="isClaimed" type="boolean" required>
  Whether the reservation has been claimed by a Journey member
</ResponseField>

<ResponseField name="totalAwardedPoints" type="number" required>
  Total loyalty points that will be awarded for this stay
</ResponseField>

<ResponseField name="guest" type="object" required>
  Guest information
  
  <Expandable title="guest object">
    <ResponseField name="firstName" type="string" required>
      Primary guest's first name
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="property" type="object" required>
  Property information
  
  <Expandable title="property object">
    <ResponseField name="name" type="string" required>
      Property name
    </ResponseField>
    
    <ResponseField name="allianceMember" type="boolean" required>
      Whether the property is part of the Journey alliance
    </ResponseField>
    
    <ResponseField name="media" type="array" required>
      Property images
      
      <Expandable title="media items">
        <ResponseField name="url" type="string" required>
          Full resolution image URL
        </ResponseField>
        
        <ResponseField name="thumbnailUrl" type="string" required>
          Thumbnail image URL
        </ResponseField>
      </Expandable>
    </ResponseField>
    
    <ResponseField name="address" type="object" required>
      Property location
      
      <Expandable title="address object">
        <ResponseField name="city" type="string" required>
          City name
        </ResponseField>
        
        <ResponseField name="state" type="string" required>
          State or province
        </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" required>
      Brand information
      
      <Expandable title="brand object">
        <ResponseField name="name" type="string" required>
          Brand name
        </ResponseField>
        
        <ResponseField name="logo" type="string" required>
          Brand logo URL
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>

```json 200 Response - Unclaimed Reservation
{
  "externalId": "12345678-1234-5678-9012-123456789012",
  "isClaimed": false,
  "totalAwardedPoints": 2500,
  "guest": {
    "firstName": "Sarah"
  },
  "property": {
    "name": "Journey Beach Resort & Spa",
    "allianceMember": true,
    "media": [
      {
        "url": "https://cdn.journey.com/properties/beach-resort-hero.jpg",
        "thumbnailUrl": "https://cdn.journey.com/properties/beach-resort-hero-thumb.jpg"
      },
      {
        "url": "https://cdn.journey.com/properties/beach-resort-pool.jpg",
        "thumbnailUrl": "https://cdn.journey.com/properties/beach-resort-pool-thumb.jpg"
      }
    ],
    "address": {
      "city": "Cancun",
      "state": "Quintana Roo",
      "latitude": 21.1619,
      "longitude": -86.8515
    },
    "brand": {
      "name": "Journey Resorts",
      "logo": "https://cdn.journey.com/brands/journey-resorts-logo.svg"
    }
  }
}
```

```json 200 Response - Already Claimed
{
  "externalId": "12345678-1234-5678-9012-123456789012",
  "isClaimed": true,
  "totalAwardedPoints": 2500,
  "guest": {
    "firstName": "Sarah"
  },
  "property": {
    "name": "Journey Beach Resort & Spa",
    "allianceMember": true,
    "media": [
      {
        "url": "https://cdn.journey.com/properties/beach-resort-hero.jpg",
        "thumbnailUrl": "https://cdn.journey.com/properties/beach-resort-hero-thumb.jpg"
      }
    ],
    "address": {
      "city": "Cancun",
      "state": "Quintana Roo",
      "latitude": 21.1619,
      "longitude": -86.8515
    },
    "brand": {
      "name": "Journey Resorts",
      "logo": "https://cdn.journey.com/brands/journey-resorts-logo.svg"
    }
  }
}
```

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

```json 400 Error - Invalid UUID
{
  "statusCode": 400,
  "message": "Validation failed (uuid is expected)",
  "error": "Bad Request"
}
```

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

</ResponseExample>

## Use Cases [#use-cases]

### Reservation Claim Flow [#reservation-claim-flow]
```typescript
const ReservationClaimPreview = ({ externalId }) => {
  const [reservation, setReservation] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchReservation = async () => {
      try {
        const data = await getReservationPreview(externalId);
        setReservation(data);
      } catch (error) {
        console.error('Failed to fetch reservation:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchReservation();
  }, [externalId]);
  
  if (loading) return <div>Loading reservation...</div>;
  if (!reservation) return <div>Reservation not found</div>;
  
  return (
    <div className="reservation-preview">
      <div className="property-info">
        <img 
          src={reservation.property.media[0]?.thumbnailUrl} 
          alt={reservation.property.name}
        />
        <h2>{reservation.property.name}</h2>
        <p>{reservation.property.address.city}, {reservation.property.address.state}</p>
      </div>
      
      <div className="guest-info">
        <h3>Welcome, {reservation.guest.firstName}!</h3>
        {reservation.isClaimed ? (
          <p>This reservation has already been claimed.</p>
        ) : (
          <div>
            <p>Earn {reservation.totalAwardedPoints.toLocaleString()} points for this stay!</p>
            <button onClick={() => claimReservation(externalId)}>
              Claim Reservation
            </button>
          </div>
        )}
      </div>
    </div>
  );
};
```

### Points Display [#points-display]
```typescript
const formatPointsEarned = (points) => {
  if (points === 0) return 'No points available';
  if (points < 1000) return `${points} points`;
  if (points < 10000) return `${(points / 1000).toFixed(1)}K points`;
  return `${Math.round(points / 1000)}K points`;
};

const PointsEarnedBadge = ({ points, isClaimed }) => {
  const formattedPoints = formatPointsEarned(points);
  
  return (
    <div className={`points-badge ${isClaimed ? 'claimed' : 'available'}`}>
      <span className="points-amount">{formattedPoints}</span>
      <span className="points-status">
        {isClaimed ? 'Earned' : 'Available to Earn'}
      </span>
    </div>
  );
};
```

### Property Location Display [#property-location-display]
```typescript
const PropertyLocation = ({ property }) => {
  const { address, brand } = property;
  
  const openInMaps = () => {
    const url = `https://maps.google.com/?q=${address.latitude},${address.longitude}`;
    window.open(url, '_blank');
  };
  
  return (
    <div className="property-location">
      <div className="brand-info">
        <img src={brand.logo} alt={brand.name} className="brand-logo" />
        <span className="brand-name">{brand.name}</span>
      </div>
      
      <div className="location-info">
        <span className="location">{address.city}, {address.state}</span>
        <button onClick={openInMaps} className="map-link">
          View on Map
        </button>
      </div>
      
      {property.allianceMember && (
        <span className="alliance-badge">Journey Alliance Member</span>
      )}
    </div>
  );
};
```

## Error Handling [#error-handling]

### Common Error Scenarios [#common-error-scenarios]
- **Invalid UUID Format**: Ensure external ID is valid UUID
- **Reservation Not Found**: Handle gracefully with user-friendly message
- **Rate Limiting**: Implement retry logic with exponential backoff
- **Network Errors**: Show offline/error states

### Retry Logic [#retry-logic]
```typescript
const fetchWithRetry = async (url, options = {}, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 429) {
        // Rate limited, wait and retry
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      return response;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      // Wait before retry
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
};
```

## Security Considerations [#security-considerations]

### Rate Limiting [#rate-limiting-1]
- Implemented to prevent abuse
- IP-based tracking
- Headers indicate remaining requests

### Data Privacy [#data-privacy]
- Only returns basic guest first name
- No sensitive personal information exposed
- External ID acts as secure token

### Input Validation [#input-validation]
- External ID must be valid UUID format
- Malformed requests return 400 error

## Related Endpoints [#related-endpoints]

- [Lookup Reservation](/api-reference/public/reservations/lookup-reservation) - Find reservation by details
- [Resolve Legacy Booking](/api-reference/public/reservations/resolve-legacy-booking) - Convert legacy bookings
