Journey Docs
Public APIReservations

Resolve Legacy Booking

Convert legacy Strapi v4 booking references to current system

GET/public/reservations/legacy/{documentId}/resolve

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

No authentication required. This is a public endpoint with 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

documentIdpathstringrequired

Strapi v5 document ID (24-character alphanumeric string)

Request

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

Response

externalReservationIdstringrequired

Current system external reservation identifier

Response

{
  "externalReservationId": "12345678-1234-5678-9012-123456789012"
}

Use Cases

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

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

// 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

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

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

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

  • 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

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

Resolution Failures

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

  • Automatic fallback to reservation lookup
  • Clear error messages for users
  • Alternative access methods provided
  • Support contact information when needed

On this page