---
title: "Modeling Travel Data: Trips, Bookings, and the Relationships Between Them"
description: "A conceptual guide to the data model behind an AI travel planning platform — the trip as top-level entity, temporal data, and AI-queryable schemas."
canonical: https://nowah.xyz/blog/modeling-travel-data-relationships
lastModified: "2026-08-07T03:53:11.844Z"
---

# Modeling Travel Data: Trips, Bookings, and the Relationships Between Them

A conceptual guide to the data model behind an AI travel planning platform — the trip as top-level entity, temporal data, and AI-queryable schemas.

A traveler asks "What time is my flight?" The AI agent needs to answer in milliseconds. That means the data model has to make this query fast: find the user's upcoming trip, find the booking within that trip, extract the departure time, and return it. If the data model is wrong, this simple question requires multiple queries, joins across poorly indexed tables, and full table scans.

Getting the data model right was one of our earliest and most consequential decisions. Everything sits on top of it. The AI agent queries it. The booking flow writes to it. The trip view reads from it. The notification scheduler scans it. If the foundation is wrong, everything built on top is harder than it needs to be.

## The trip as the top-level entity

![Illustration for this section](https://pics.nowah.xyz/website-media/infrastructure-015-img-1.webp)

This might seem obvious, but it took us a while to land on. The top-level entity in our data model is the Trip, not the Booking and not the Flight.

A trip is the thing travelers think about. "My trip to Tokyo." "Our anniversary trip." "The family vacation in August." It's the container that gives meaning to individual bookings. A flight booking alone is an airline, a time, and a seat. A flight booking within a trip is part of a story.

Making the trip the top-level entity has practical consequences. When the traveler opens the app, they see a list of trips. Each trip contains its bookings, documents, and conversation threads. The AI agent reasons about trips, not individual bookings. "Tell me about my Tokyo trip" returns everything associated with that trip in one query path.

If we'd made bookings the top-level entity, the trip would have to be reconstructed from a collection of loosely associated bookings. "These three bookings are all part of the same trip" would be an inference, not an explicit relationship. That's fragile.

## The relationship hierarchy

The data model is a tree rooted at the Trip:

**Trip** is the root\. It has a name, dates, destination, and status \(planning, confirmed, in\_progress, completed, cancelled\)\.

**Booking** belongs to a trip. A trip can have multiple bookings: an outbound flight, a return flight, a hotel, maybe a car rental. Each booking has its own confirmation number, status, provider reference, and pricing.

**Traveler profiles** connect to bookings. A booking can have multiple travelers (a family booking), and a traveler can appear in multiple bookings (the same person has both the flight and hotel booking). This is a many-to-many relationship that we model explicitly.

**Documents** belong to bookings or trips. A boarding pass belongs to a specific flight booking. A hotel confirmation PDF belongs to a hotel booking. A general itinerary document might belong to the trip as a whole.

**Chat threads** belong to trips. The conversation that produced the bookings is linked to the trip, so the AI agent has context about how the trip was planned and what was discussed.

**Agent sessions** belong to chat threads. Each interaction session with the AI agent has its own state, including what tools were called and what results were returned.

This hierarchy lets us load a complete trip view with a single query tree: trip + bookings + travelers + documents + recent messages. The ORM handles the joins efficiently with eager loading.

## Many-to-many realities

![Supporting diagram](https://pics.nowah.xyz/website-media/infrastructure-015-img-2.webp)

Travel data has real many-to-many relationships that you can't normalize away.

A traveler can be on multiple trips. That same person traveled to London last month and is going to Barcelona next week. Each trip appears in their trip list independently.

A booking can involve multiple travelers. A family of four on the same flight has one booking with four travelers. Each traveler might have different document requirements (a child doesn't need the same paperwork as an adult).

A trip can span multiple conversation threads. The traveler might have planned the outbound flights in one conversation, then come back a week later to book hotels in another. Both threads should be associated with the same trip.

We model these with explicit join tables rather than trying to embed arrays. Join tables let us query in both directions efficiently ("which trips is this traveler on?" and "which travelers are on this trip?") and add metadata to the relationship ("is this traveler the primary contact for this booking?").

## Temporal data across time zones

A single trip can span three or more time zones. You depart New York (ET), fly to London (GMT), then continue to Dubai (GST). Each flight has a departure time in one zone and an arrival time in another. The hotel check-in is in the destination's zone.

We store all times in UTC. Every timestamp in the database is UTC. No exceptions. This eliminates an entire class of bugs where the same time is interpreted differently depending on which system reads it.

The time zone context is stored separately. A departure time is stored as a UTC timestamp plus the departure airport's time zone identifier. When the AI agent tells the traveler "Your flight departs at 9:15 AM," it converts the UTC timestamp to the airport's local time for display.

This approach means we can sort all events chronologically with a simple ORDER BY on UTC timestamps, regardless of time zones. "What's happening next on my trip?" doesn't require time zone math at query time.

Cancellation deadlines and booking windows also use UTC internally. "[Free cancellation](/blog/hidden-cost-of-free-cancellation) until 24 hours before departure" is computed as the departure UTC timestamp minus 24 hours, which gives a clean UTC deadline that the notification scheduler can work with directly.

## Soft deletes and audit trails

We don't hard-delete booking records. Ever. Compliance, customer support, and the AI agent's historical context all require knowing what existed, not just what currently exists.

When a traveler cancels a booking, the booking record gets a \`cancelled\_at\` timestamp and a \`cancellation\_reason\`\. It remains queryable\. The AI agent can still reference it: "You had a booking to London that was cancelled on March 5th\."

This applies to all entities in the trip hierarchy. Deleted trips are soft-deleted. Removed documents are soft-deleted. Even conversation messages, if the traveler deletes them from the UI, are soft-deleted in the database.

The trade-off is database size. We accumulate records that are never truly removed. But storage is cheap, and the value of historical data (for the AI agent's context, for [audit trails](/blog/audit-trails-ai-bookings), for customer support) far outweighs the storage cost.

## How the AI agent queries the model

The AI agent has access to a comprehensive toolkit, and many of those tools query the data model directly. When a traveler asks "What time is my flight?" the agent calls a tool that runs something equivalent to: find the user's upcoming trips, find the soonest flight booking, return the departure details.

The data model is designed to make these queries natural. Common questions map to straightforward query patterns:

"What time is my flight?" = upcoming trip + next flight booking + departure time.

"Where am I staying in Paris?" = trip by destination + hotel booking + property details.

"Show me my trips this year" = user's trips + date filter + booking summaries.

"When does my cancellation deadline expire?" = specific booking \+ cancellation\_deadline timestamp\.

Each of these query patterns is tested for performance with production-scale data. A query that's fast on a test database with 10 trips might be slow for a frequent traveler with 100 trips and 500 bookings. We index for the common access patterns and monitor slow queries in production.

## Design principles for your own travel data model

If you're building a travel data model, here's what we'd recommend.

Make the trip the root entity. Travelers think in trips, not bookings. Your data model should match their mental model.

Use explicit relationships, not inferred associations. A booking explicitly belongs to a trip via a foreign key. Don't rely on date overlap or destination matching to associate bookings with trips.

Store times in UTC. Always. Convert for display only. Time zone bugs in travel are particularly nasty because a 1-hour error can mean a missed flight.

Soft-delete everything. You'll need the historical data for AI context, compliance, and customer support. Hard deletes create information black holes.

Design for the AI agent's query patterns. If your AI agent needs to answer "What time is my flight?" in under a second, the data model should make that query trivial. Profile the agent's most common queries and optimize the model for them.

---

Nowah is an AI travel agent that searches and books real flights and hotels through conversation — no filters, no thirty open tabs. [Plan your next trip](https://app.nowah.xyz).
