# Resolve Legacy Booking
> Source: /api-reference/public/reservations/resolve-legacy-booking
> Convert legacy Strapi v4 booking references to current system
> Endpoint: GET /public/reservations/legacy/{documentId}/resolve

## Overview [#overview]

Resolves legacy booking references from Strapi v4 to current external reservation IDs. Used for backward compatibility with old booking systems and member communications that reference legacy document IDs.

## 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="documentId" type="string" required>
  Strapi v5 document ID (24-character alphanumeric string)
</ParamField>

<RequestExample>

```bash cURL
curl -X GET "https://api.journey.com/v1/public/reservations/legacy/abc123def456ghi789jkl012/resolve"
```

```typescript TypeScript
const resolveLegacyBooking = async (documentId: string) => {
  // Validate document ID format
  const documentIdPattern = /^[a-z0-9]{24}$/i;
  if (!documentIdPattern.test(documentId)) {
    throw new Error('Invalid document ID format');
  }
  
  const response = await fetch(
    `https://api.journey.com/v1/public/reservations/legacy/${documentId}/resolve`,
    { method: 'GET' }
  );
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
};
```

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

</RequestExample>

## Response [#response]

<ResponseField name="externalReservationId" type="string" required>
  Current system external reservation identifier
</ResponseField>

<ResponseExample>

```json 200 Response - Legacy Booking Resolved
{
  "externalReservationId": "12345678-1234-5678-9012-123456789012"
}
```

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

```json 400 Error - Invalid Document ID Format
{
  "statusCode": 400,
  "message": "Invalid document ID format",
  "error": "Bad Request"
}
```

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

</ResponseExample>

## Use Cases [#use-cases]

### Legacy Link Handler [#legacy-link-handler]
```typescript
const LegacyBookingHandler = ({ legacyId }) => {
  const [externalId, setExternalId] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const resolveAndRedirect = async () => {
      try {
        const result = await resolveLegacyBooking(legacyId);
        setExternalId(result.externalReservationId);
        
        // Redirect to current reservation URL
        window.location.href = `/reservations/${result.externalReservationId}`;
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    resolveAndRedirect();
  }, [legacyId]);
  
  if (loading) {
    return (
      <div className="legacy-resolver">
        <p>Resolving legacy booking reference...</p>
        <div className="loading-spinner" />
      </div>
    );
  }
  
  if (error) {
    return (
      <div className="legacy-error">
        <h3>Booking Reference Not Found</h3>
        <p>We couldn't find a booking associated with this legacy reference.</p>
        <p>Please try using our reservation lookup tool instead.</p>
        <a href="/api-reference/public/reservations/lookup-reservation">Look up reservation</a>
      </div>
    );
  }
  
  return null; // Should redirect before rendering
};
```

### Email Link Migration [#email-link-migration]
```typescript
// Handle legacy email links that may contain old document IDs
const handleLegacyEmailLink = async (url) => {
  const legacyPattern = /\/legacy\/([a-z0-9]{24})\//i;
  const match = url.match(legacyPattern);
  
  if (match) {
    const documentId = match[1];
    
    try {
      const resolved = await resolveLegacyBooking(documentId);
      
      // Update URL to current format
      const newUrl = url.replace(
        `/legacy/${documentId}/`, 
        `/reservations/${resolved.externalReservationId}/`
      );
      
      return newUrl;
    } catch (error) {
      // Fallback to reservation lookup
      return '/reservations/lookup';
    }
  }
  
  return url; // No legacy reference found
};

// Usage in email template or link handler
const EmailLinkHandler = ({ href, children }) => {
  const [resolvedHref, setResolvedHref] = useState(href);
  
  useEffect(() => {
    const resolveLegacyLink = async () => {
      const newHref = await handleLegacyEmailLink(href);
      setResolvedHref(newHref);
    };
    
    resolveLegacyLink();
  }, [href]);
  
  return <a href={resolvedHref}>{children}</a>;
};
```

### Batch Legacy Resolution [#batch-legacy-resolution]
```typescript
const resolveLegacyBatch = async (documentIds) => {
  const results = await Promise.allSettled(
    documentIds.map(async (docId) => {
      try {
        const resolved = await resolveLegacyBooking(docId);
        return {
          legacy: docId,
          external: resolved.externalReservationId,
          success: true
        };
      } catch (error) {
        return {
          legacy: docId,
          error: error.message,
          success: false
        };
      }
    })
  );
  
  return results.map(result => result.value);
};

// Usage for migrating member data
const migrateMemberBookings = async (memberBookings) => {
  const legacyIds = memberBookings
    .filter(booking => booking.type === 'legacy')
    .map(booking => booking.documentId);
  
  if (legacyIds.length === 0) return memberBookings;
  
  const resolutions = await resolveLegacyBatch(legacyIds);
  
  return memberBookings.map(booking => {
    if (booking.type !== 'legacy') return booking;
    
    const resolution = resolutions.find(r => r.legacy === booking.documentId);
    if (resolution?.success) {
      return {
        ...booking,
        type: 'current',
        externalId: resolution.external,
        migrated: true
      };
    }
    
    return { ...booking, migrationFailed: true };
  });
};
```

### URL Router Integration [#url-router-integration]
```typescript
// React Router setup for handling legacy URLs
const LegacyRoute = ({ children }) => {
  const { documentId } = useParams();
  const navigate = useNavigate();
  const [resolving, setResolving] = useState(true);
  
  useEffect(() => {
    const resolveAndNavigate = async () => {
      try {
        const result = await resolveLegacyBooking(documentId);
        
        // Navigate to current route structure
        navigate(`/reservations/${result.externalReservationId}`, { 
          replace: true 
        });
      } catch (error) {
        // Navigate to lookup page if resolution fails
        navigate('/reservations/lookup', { 
          replace: true,
          state: { error: 'Legacy booking not found' }
        });
      }
    };
    
    resolveAndNavigate();
  }, [documentId, navigate]);
  
  return (
    <div className="legacy-route-handler">
      <p>Updating booking reference...</p>
    </div>
  );
};

// Router configuration
const AppRouter = () => (
  <Routes>
    {/* Current routes */}
    <Route path="/reservations/:externalId" element={<ReservationPage />} />
    
    {/* Legacy compatibility routes */}
    <Route path="/legacy/:documentId/*" element={<LegacyRoute />} />
    <Route path="/bookings/legacy/:documentId" element={<LegacyRoute />} />
    
    {/* Fallback */}
    <Route path="/reservations/lookup" element={<ReservationLookup />} />
  </Routes>
);
```

## Document ID Validation [#document-id-validation]

### Format Requirements [#format-requirements]
- **Length**: Exactly 24 characters
- **Characters**: Alphanumeric only (a-z, A-Z, 0-9)
- **Case**: Case insensitive matching
- **Pattern**: `/^[a-z0-9]{24}$/i`

### Validation Examples [#validation-examples]
```typescript
const validateDocumentId = (documentId) => {
  const pattern = /^[a-z0-9]{24}$/i;
  
  if (!documentId) {
    return { valid: false, error: 'Document ID is required' };
  }
  
  if (documentId.length !== 24) {
    return { 
      valid: false, 
      error: `Document ID must be 24 characters, got ${documentId.length}` 
    };
  }
  
  if (!pattern.test(documentId)) {
    return { 
      valid: false, 
      error: 'Document ID must contain only alphanumeric characters' 
    };
  }
  
  return { valid: true };
};

// Valid examples:
// abc123def456ghi789jkl012
// A1B2C3D4E5F6G7H8I9J0K1L2
// 123456789012345678901234

// Invalid examples:
// abc123 (too short)
// abc123def456ghi789jkl012xyz (too long)
// abc123-def456-ghi789-jkl012 (contains hyphens)
// abc123_def456_ghi789_jkl012 (contains underscores)
```

## Migration Context [#migration-context]

### Legacy System Background [#legacy-system-background]
- **Strapi v4**: Previous CMS version with different ID format
- **Document IDs**: 24-character alphanumeric identifiers
- **Migration**: Automatic mapping to current external reservation IDs
- **Deprecation**: Legacy IDs supported but deprecated

### Common Migration Scenarios [#common-migration-scenarios]
- **Email Links**: Old confirmation emails with legacy references
- **Member History**: Bookings made before system migration
- **Deep Links**: Saved bookmarks or shared links
- **API Integrations**: Third-party systems using old references

### Transition Timeline [#transition-timeline]
```typescript
const migrationInfo = {
  phases: {
    'Pre-2024': 'Strapi v4 document IDs only',
    '2024-Q1': 'Migration period - both systems active',
    '2024-Q2': 'New external IDs primary, legacy resolution available',
    '2024-Q3+': 'Legacy resolution maintained for compatibility'
  },
  
  supportPolicy: {
    current: 'Full support for legacy resolution',
    future: 'Continued support for existing legacy references',
    deprecation: 'No new legacy IDs created'
  }
};
```

## Error Handling [#error-handling]

### Resolution Failures [#resolution-failures]
```typescript
const handleResolutionError = (error, documentId) => {
  switch (error.status) {
    case 404:
      return {
        message: 'This booking reference is no longer valid.',
        action: 'Please use our reservation lookup tool to find your booking.',
        fallback: '/reservations/lookup'
      };
      
    case 400:
      return {
        message: 'Invalid booking reference format.',
        action: 'Please check the link and try again.',
        fallback: '/reservations/lookup'
      };
      
    case 429:
      return {
        message: 'Too many requests. Please wait a moment.',
        action: 'Try again in a few minutes.',
        retry: true
      };
      
    default:
      return {
        message: 'Unable to resolve booking reference.',
        action: 'Please try again or use reservation lookup.',
        fallback: '/reservations/lookup'
      };
  }
};
```

### Graceful Degradation [#graceful-degradation]
- Automatic fallback to reservation lookup
- Clear error messages for users
- Alternative access methods provided
- Support contact information when needed

## Related Endpoints [#related-endpoints]

- [Get Reservation Preview](/api-reference/public/reservations/get-reservation-preview) - View resolved reservation
- [Lookup Reservation](/api-reference/public/reservations/lookup-reservation) - Alternative lookup method
