Skip to main content
Glama

Hostaway MCP Server

data-model.md45.9 kB
# Data Model: Hostaway MCP Server **Feature Branch**: `001-we-need-to` **Created**: 2025-10-12 **Status**: Complete ## Overview This document defines all Pydantic data models for the Hostaway MCP Server. Models are organized by domain (Authentication, Listings, Bookings, Guests, Financial) and include field definitions, validation rules, relationships, and example values. **Key Design Principles**: - All models use Pydantic v2 with strict type validation - Field constraints enforce API requirements (min/max values, regex patterns) - DateTime fields use timezone-aware datetime objects - Enums enforce valid values for status fields - Optional fields have clear defaults or None - Example values demonstrate real-world usage --- ## 1. Authentication Models ### 1.1 HostawayConfig Configuration model for OAuth credentials and API settings. ```python from pydantic import Field, SecretStr from pydantic_settings import BaseSettings class HostawayConfig(BaseSettings): """Hostaway API configuration loaded from environment variables. Required environment variables: - HOSTAWAY_ACCOUNT_ID: OAuth client ID - HOSTAWAY_SECRET_KEY: OAuth client secret - HOSTAWAY_API_BASE_URL: Base API URL (default: https://api.hostaway.com/v1) """ account_id: str = Field( ..., description="Hostaway account ID (OAuth client ID)", min_length=1, alias="HOSTAWAY_ACCOUNT_ID" ) secret_key: SecretStr = Field( ..., description="Hostaway secret key (OAuth client secret)", alias="HOSTAWAY_SECRET_KEY" ) api_base_url: str = Field( default="https://api.hostaway.com/v1", description="Hostaway API base URL", alias="HOSTAWAY_API_BASE_URL" ) rate_limit_ip: int = Field( default=15, description="IP-based rate limit (requests per 10 seconds)", ge=1, le=15, alias="RATE_LIMIT_IP" ) rate_limit_account: int = Field( default=20, description="Account-based rate limit (requests per 10 seconds)", ge=1, le=20, alias="RATE_LIMIT_ACCOUNT" ) max_concurrent_requests: int = Field( default=10, description="Maximum concurrent requests", ge=1, le=50, alias="MAX_CONCURRENT_REQUESTS" ) token_refresh_threshold_days: int = Field( default=7, description="Days before expiration to refresh token", ge=1, le=30, alias="TOKEN_REFRESH_THRESHOLD_DAYS" ) class Config: env_file = ".env" env_file_encoding = "utf-8" case_sensitive = True # Example usage config = HostawayConfig( HOSTAWAY_ACCOUNT_ID="12345", HOSTAWAY_SECRET_KEY="secret_abc123xyz" ) ``` ### 1.2 AccessToken OAuth access token with expiration tracking. ```python from datetime import datetime, timedelta from pydantic import BaseModel, Field, field_validator from typing import Optional class AccessToken(BaseModel): """OAuth 2.0 access token for Hostaway API authentication. Tokens are valid for 24 months from issuance. """ access_token: str = Field( ..., description="Bearer token for API authentication", min_length=20 ) token_type: str = Field( default="Bearer", description="Token type (always 'Bearer' for Hostaway)", pattern=r"^Bearer$" ) expires_in: int = Field( ..., description="Token lifetime in seconds (typically 63072000 = 24 months)", ge=1 ) issued_at: datetime = Field( default_factory=datetime.utcnow, description="Timestamp when token was issued (UTC)" ) scope: str = Field( default="general", description="OAuth scope (Hostaway uses 'general')" ) @property def expires_at(self) -> datetime: """Calculate token expiration timestamp.""" return self.issued_at + timedelta(seconds=self.expires_in) @property def is_expired(self) -> bool: """Check if token is currently expired.""" return datetime.utcnow() >= self.expires_at @property def days_until_expiration(self) -> int: """Calculate days remaining until token expires.""" delta = self.expires_at - datetime.utcnow() return max(0, delta.days) def should_refresh(self, threshold_days: int = 7) -> bool: """Determine if token should be refreshed. Args: threshold_days: Refresh if expiring within this many days Returns: True if token should be refreshed """ return self.days_until_expiration <= threshold_days class Config: json_schema_extra = { "example": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 63072000, "issued_at": "2025-10-12T10:30:00Z", "scope": "general" } } # Example usage token = AccessToken( access_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", expires_in=63072000 # 24 months ) print(f"Token expires in {token.days_until_expiration} days") print(f"Should refresh: {token.should_refresh(threshold_days=7)}") ``` ### 1.3 TokenRefreshRequest Request model for OAuth token exchange. ```python from pydantic import BaseModel, Field class TokenRefreshRequest(BaseModel): """OAuth 2.0 token request using Client Credentials Grant. Sent as application/x-www-form-urlencoded to POST /v1/accessTokens """ grant_type: str = Field( default="client_credentials", description="OAuth grant type (always 'client_credentials')", pattern=r"^client_credentials$" ) client_id: str = Field( ..., description="Hostaway account ID", min_length=1 ) client_secret: str = Field( ..., description="Hostaway secret key", min_length=1 ) scope: str = Field( default="general", description="OAuth scope (Hostaway uses 'general')" ) def to_form_data(self) -> dict[str, str]: """Convert to form-encoded data for HTTP request.""" return { "grant_type": self.grant_type, "client_id": self.client_id, "client_secret": self.client_secret, "scope": self.scope } class Config: json_schema_extra = { "example": { "grant_type": "client_credentials", "client_id": "12345", "client_secret": "secret_abc123xyz", "scope": "general" } } ``` ### 1.4 TokenRefreshResponse Response model from OAuth token endpoint. ```python from pydantic import BaseModel, Field class TokenRefreshResponse(BaseModel): """OAuth token response from Hostaway API. Returned from POST /v1/accessTokens """ access_token: str = Field( ..., description="JWT access token", min_length=20 ) token_type: str = Field( ..., description="Token type (always 'Bearer')" ) expires_in: int = Field( ..., description="Token lifetime in seconds", ge=1 ) scope: str = Field( default="general", description="OAuth scope" ) def to_access_token(self) -> AccessToken: """Convert to AccessToken model.""" return AccessToken( access_token=self.access_token, token_type=self.token_type, expires_in=self.expires_in, scope=self.scope ) class Config: json_schema_extra = { "example": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 63072000, "scope": "general" } } ``` --- ## 2. Listing Models ### 2.1 PricingInfo Pricing details for a property listing. ```python from pydantic import BaseModel, Field from decimal import Decimal from typing import Optional class PricingInfo(BaseModel): """Pricing information for a property listing.""" base_price: Decimal = Field( ..., description="Base nightly rate in USD", ge=0, decimal_places=2 ) currency: str = Field( default="USD", description="Currency code (ISO 4217)", pattern=r"^[A-Z]{3}$" ) weekend_price: Optional[Decimal] = Field( None, description="Weekend nightly rate (if different)", ge=0, decimal_places=2 ) cleaning_fee: Decimal = Field( default=Decimal("0.00"), description="One-time cleaning fee", ge=0, decimal_places=2 ) security_deposit: Decimal = Field( default=Decimal("0.00"), description="Refundable security deposit", ge=0, decimal_places=2 ) extra_guest_fee: Optional[Decimal] = Field( None, description="Fee per additional guest beyond base capacity", ge=0, decimal_places=2 ) min_stay_nights: int = Field( default=1, description="Minimum stay in nights", ge=1 ) class Config: json_schema_extra = { "example": { "base_price": "150.00", "currency": "USD", "weekend_price": "200.00", "cleaning_fee": "75.00", "security_deposit": "500.00", "extra_guest_fee": "25.00", "min_stay_nights": 2 } } ``` ### 2.2 AvailabilityInfo Availability and calendar information for a listing. ```python from datetime import date from pydantic import BaseModel, Field from typing import List, Optional class AvailabilityInfo(BaseModel): """Availability and calendar information for a property.""" is_available: bool = Field( ..., description="Whether property is currently available for booking" ) available_from: Optional[date] = Field( None, description="Date when property becomes available" ) blocked_dates: List[date] = Field( default_factory=list, description="Dates when property is unavailable" ) booked_dates: List[date] = Field( default_factory=list, description="Dates with existing reservations" ) calendar_notes: Optional[str] = Field( None, description="Notes about availability or scheduling", max_length=500 ) class Config: json_schema_extra = { "example": { "is_available": True, "available_from": "2025-10-15", "blocked_dates": ["2025-12-24", "2025-12-25", "2025-12-31"], "booked_dates": ["2025-11-10", "2025-11-11", "2025-11-12"], "calendar_notes": "Holiday dates blocked for owner use" } } ``` ### 2.3 Listing Complete property listing details. ```python from pydantic import BaseModel, Field, HttpUrl from typing import List, Optional, Dict from decimal import Decimal class Listing(BaseModel): """Complete property listing with all details. Represents a rental property in the Hostaway system. """ id: int = Field( ..., description="Unique listing ID", gt=0 ) name: str = Field( ..., description="Property name/title", min_length=1, max_length=200 ) address: str = Field( ..., description="Full property address", min_length=1, max_length=500 ) city: str = Field( ..., description="City name", min_length=1, max_length=100 ) state: Optional[str] = Field( None, description="State/province", max_length=100 ) country: str = Field( ..., description="Country name or ISO code", min_length=2, max_length=100 ) postal_code: Optional[str] = Field( None, description="Postal/ZIP code", max_length=20 ) latitude: Optional[Decimal] = Field( None, description="GPS latitude", ge=-90, le=90, decimal_places=6 ) longitude: Optional[Decimal] = Field( None, description="GPS longitude", ge=-180, le=180, decimal_places=6 ) description: str = Field( ..., description="Full property description", min_length=1, max_length=5000 ) capacity: int = Field( ..., description="Maximum guest capacity", ge=1, le=50 ) bedrooms: int = Field( ..., description="Number of bedrooms", ge=0, le=20 ) bathrooms: Decimal = Field( ..., description="Number of bathrooms (0.5 for half bath)", ge=0, le=20, decimal_places=1 ) beds: int = Field( default=0, description="Total number of beds", ge=0, le=50 ) property_type: str = Field( ..., description="Type of property (apartment, house, villa, etc.)", max_length=100 ) amenities: List[str] = Field( default_factory=list, description="List of amenities (WiFi, parking, pool, etc.)" ) pricing: PricingInfo = Field( ..., description="Pricing information" ) availability: AvailabilityInfo = Field( ..., description="Availability and calendar info" ) cancellation_policy: str = Field( default="moderate", description="Cancellation policy type", max_length=100 ) check_in_time: Optional[str] = Field( None, description="Check-in time (e.g., '15:00')", pattern=r"^\d{2}:\d{2}$" ) check_out_time: Optional[str] = Field( None, description="Check-out time (e.g., '11:00')", pattern=r"^\d{2}:\d{2}$" ) images: List[HttpUrl] = Field( default_factory=list, description="URLs to property images" ) channel_ids: Dict[str, str] = Field( default_factory=dict, description="External platform listing IDs (airbnb, vrbo, booking_com)" ) is_active: bool = Field( default=True, description="Whether listing is active and bookable" ) house_rules: Optional[str] = Field( None, description="Property-specific rules for guests", max_length=2000 ) class Config: json_schema_extra = { "example": { "id": 12345, "name": "Cozy Downtown Apartment", "address": "123 Main St, Apt 4B", "city": "San Francisco", "state": "California", "country": "USA", "postal_code": "94102", "latitude": "37.774929", "longitude": "-122.419416", "description": "Beautiful 2BR apartment in the heart of downtown...", "capacity": 4, "bedrooms": 2, "bathrooms": "1.5", "beds": 2, "property_type": "apartment", "amenities": ["WiFi", "Kitchen", "Parking", "Air Conditioning"], "pricing": { "base_price": "150.00", "currency": "USD", "cleaning_fee": "75.00" }, "availability": { "is_available": True }, "cancellation_policy": "moderate", "check_in_time": "15:00", "check_out_time": "11:00", "images": [ "https://example.com/image1.jpg", "https://example.com/image2.jpg" ], "channel_ids": { "airbnb": "abc123", "vrbo": "xyz789" }, "is_active": True, "house_rules": "No smoking, no pets, quiet hours after 10pm" } } ``` ### 2.4 ListingSummary Abbreviated listing information for list views. ```python from pydantic import BaseModel, Field, HttpUrl from decimal import Decimal from typing import Optional class ListingSummary(BaseModel): """Abbreviated listing information for list/search results. Used when returning multiple properties to reduce payload size. """ id: int = Field( ..., description="Unique listing ID", gt=0 ) name: str = Field( ..., description="Property name", min_length=1, max_length=200 ) city: str = Field( ..., description="City name" ) state: Optional[str] = Field( None, description="State/province" ) capacity: int = Field( ..., description="Maximum guest capacity", ge=1 ) bedrooms: int = Field( ..., description="Number of bedrooms", ge=0 ) bathrooms: Decimal = Field( ..., description="Number of bathrooms", ge=0, decimal_places=1 ) base_price: Decimal = Field( ..., description="Base nightly rate", ge=0, decimal_places=2 ) is_available: bool = Field( ..., description="Current availability status" ) property_type: str = Field( ..., description="Type of property" ) thumbnail_url: Optional[HttpUrl] = Field( None, description="URL to primary property image" ) class Config: json_schema_extra = { "example": { "id": 12345, "name": "Cozy Downtown Apartment", "city": "San Francisco", "state": "California", "capacity": 4, "bedrooms": 2, "bathrooms": "1.5", "base_price": "150.00", "is_available": True, "property_type": "apartment", "thumbnail_url": "https://example.com/image1.jpg" } } ``` --- ## 3. Booking Models ### 3.1 BookingStatus Enumeration of valid booking statuses. ```python from enum import Enum class BookingStatus(str, Enum): """Valid booking/reservation statuses in Hostaway.""" CONFIRMED = "confirmed" PENDING = "pending" CANCELLED = "cancelled" COMPLETED = "completed" INQUIRY = "inquiry" DECLINED = "declined" EXPIRED = "expired" def is_active(self) -> bool: """Check if booking is currently active.""" return self in ( BookingStatus.CONFIRMED, BookingStatus.PENDING ) def is_finalized(self) -> bool: """Check if booking status is final (no further changes).""" return self in ( BookingStatus.CANCELLED, BookingStatus.COMPLETED, BookingStatus.DECLINED, BookingStatus.EXPIRED ) ``` ### 3.2 PaymentStatus Enumeration of payment statuses. ```python from enum import Enum class PaymentStatus(str, Enum): """Payment status for bookings.""" PAID = "paid" PARTIALLY_PAID = "partially_paid" PENDING = "pending" REFUNDED = "refunded" FAILED = "failed" def is_complete(self) -> bool: """Check if payment is fully complete.""" return self == PaymentStatus.PAID ``` ### 3.3 PaymentInfo Payment details for a booking. ```python from pydantic import BaseModel, Field from decimal import Decimal from typing import Optional from datetime import datetime class PaymentInfo(BaseModel): """Payment information for a booking/reservation.""" total_price: Decimal = Field( ..., description="Total booking price", ge=0, decimal_places=2 ) currency: str = Field( default="USD", description="Currency code (ISO 4217)", pattern=r"^[A-Z]{3}$" ) amount_paid: Decimal = Field( default=Decimal("0.00"), description="Amount already paid", ge=0, decimal_places=2 ) amount_due: Decimal = Field( default=Decimal("0.00"), description="Remaining amount due", ge=0, decimal_places=2 ) payment_status: PaymentStatus = Field( ..., description="Current payment status" ) payment_method: Optional[str] = Field( None, description="Payment method (credit_card, bank_transfer, etc.)", max_length=50 ) paid_at: Optional[datetime] = Field( None, description="Timestamp when payment was completed (UTC)" ) @property def is_fully_paid(self) -> bool: """Check if booking is fully paid.""" return self.amount_paid >= self.total_price class Config: json_schema_extra = { "example": { "total_price": "525.00", "currency": "USD", "amount_paid": "525.00", "amount_due": "0.00", "payment_status": "paid", "payment_method": "credit_card", "paid_at": "2025-10-12T10:30:00Z" } } ``` ### 3.4 Booking Complete booking/reservation details. ```python from pydantic import BaseModel, Field, EmailStr from datetime import date, datetime from typing import Optional from decimal import Decimal class Booking(BaseModel): """Complete booking/reservation with guest and payment information. Represents a guest reservation for a property listing. """ id: int = Field( ..., description="Unique booking ID", gt=0 ) listing_id: int = Field( ..., description="ID of the booked property", gt=0 ) listing_name: Optional[str] = Field( None, description="Name of the booked property", max_length=200 ) guest_id: int = Field( ..., description="ID of the guest making the reservation", gt=0 ) guest_name: str = Field( ..., description="Full name of guest", min_length=1, max_length=200 ) guest_email: EmailStr = Field( ..., description="Guest email address" ) guest_phone: Optional[str] = Field( None, description="Guest phone number", max_length=50 ) check_in: date = Field( ..., description="Check-in date" ) check_out: date = Field( ..., description="Check-out date" ) num_guests: int = Field( ..., description="Number of guests", ge=1, le=50, alias="guests" ) num_adults: Optional[int] = Field( None, description="Number of adult guests", ge=0 ) num_children: Optional[int] = Field( None, description="Number of child guests", ge=0 ) num_nights: int = Field( ..., description="Number of nights for stay", ge=1 ) status: BookingStatus = Field( ..., description="Current booking status" ) payment: PaymentInfo = Field( ..., description="Payment information" ) booking_source: str = Field( ..., description="Booking channel (direct, airbnb, vrbo, booking_com)", max_length=100 ) confirmation_code: Optional[str] = Field( None, description="Booking confirmation code", max_length=50 ) created_at: datetime = Field( ..., description="Booking creation timestamp (UTC)" ) updated_at: Optional[datetime] = Field( None, description="Last update timestamp (UTC)" ) special_requests: Optional[str] = Field( None, description="Guest special requests or notes", max_length=1000 ) @property def is_upcoming(self) -> bool: """Check if check-in date is in the future.""" from datetime import date as date_module return self.check_in > date_module.today() @property def is_current(self) -> bool: """Check if guest is currently checked in.""" from datetime import date as date_module today = date_module.today() return self.check_in <= today <= self.check_out @property def is_past(self) -> bool: """Check if check-out date has passed.""" from datetime import date as date_module return self.check_out < date_module.today() class Config: json_schema_extra = { "example": { "id": 67890, "listing_id": 12345, "listing_name": "Cozy Downtown Apartment", "guest_id": 54321, "guest_name": "John Smith", "guest_email": "john.smith@example.com", "guest_phone": "+1-555-123-4567", "check_in": "2025-11-10", "check_out": "2025-11-13", "guests": 2, "num_adults": 2, "num_children": 0, "num_nights": 3, "status": "confirmed", "payment": { "total_price": "525.00", "currency": "USD", "amount_paid": "525.00", "amount_due": "0.00", "payment_status": "paid", "payment_method": "credit_card" }, "booking_source": "airbnb", "confirmation_code": "ABCD1234", "created_at": "2025-10-01T14:30:00Z", "updated_at": "2025-10-01T14:30:00Z", "special_requests": "Early check-in if possible" } } ``` ### 3.5 BookingSearchFilters Search filters for booking queries. ```python from pydantic import BaseModel, Field, EmailStr from datetime import date from typing import Optional, List class BookingSearchFilters(BaseModel): """Filters for searching bookings/reservations. All filters are optional and combined with AND logic. """ listing_id: Optional[int] = Field( None, description="Filter by specific property", gt=0 ) check_in_from: Optional[date] = Field( None, description="Filter bookings with check-in on or after this date" ) check_in_to: Optional[date] = Field( None, description="Filter bookings with check-in on or before this date" ) check_out_from: Optional[date] = Field( None, description="Filter bookings with check-out on or after this date" ) check_out_to: Optional[date] = Field( None, description="Filter bookings with check-out on or before this date" ) status: Optional[List[BookingStatus]] = Field( None, description="Filter by booking status (multiple allowed)" ) guest_email: Optional[EmailStr] = Field( None, description="Filter by guest email address" ) booking_source: Optional[str] = Field( None, description="Filter by booking channel (airbnb, vrbo, etc.)", max_length=100 ) min_guests: Optional[int] = Field( None, description="Filter bookings with at least this many guests", ge=1 ) max_guests: Optional[int] = Field( None, description="Filter bookings with at most this many guests", ge=1 ) limit: int = Field( default=100, description="Maximum results to return", ge=1, le=1000 ) offset: int = Field( default=0, description="Number of results to skip (for pagination)", ge=0 ) class Config: json_schema_extra = { "example": { "listing_id": 12345, "check_in_from": "2025-11-01", "check_in_to": "2025-11-30", "status": ["confirmed", "pending"], "min_guests": 2, "limit": 50, "offset": 0 } } ``` --- ## 4. Guest Models ### 4.1 MessageChannel Enumeration of communication channels. ```python from enum import Enum class MessageChannel(str, Enum): """Communication channels for guest messaging.""" EMAIL = "email" SMS = "sms" AIRBNB = "airbnb" BOOKING_COM = "booking_com" VRBO = "vrbo" DIRECT = "direct" def is_platform_messaging(self) -> bool: """Check if channel is platform-specific (vs direct).""" return self in ( MessageChannel.AIRBNB, MessageChannel.BOOKING_COM, MessageChannel.VRBO ) ``` ### 4.2 MessageDeliveryStatus Enumeration of message delivery statuses. ```python from enum import Enum class MessageDeliveryStatus(str, Enum): """Message delivery status.""" PENDING = "pending" SENT = "sent" DELIVERED = "delivered" FAILED = "failed" READ = "read" def is_successful(self) -> bool: """Check if message was successfully delivered.""" return self in ( MessageDeliveryStatus.SENT, MessageDeliveryStatus.DELIVERED, MessageDeliveryStatus.READ ) ``` ### 4.3 Message Guest message/communication. ```python from pydantic import BaseModel, Field from datetime import datetime from typing import Optional class Message(BaseModel): """Guest communication message. Represents a message sent to or received from a guest. """ id: Optional[int] = Field( None, description="Message ID (null for unsent messages)", gt=0 ) booking_id: int = Field( ..., description="Associated booking ID", gt=0 ) guest_id: int = Field( ..., description="Recipient/sender guest ID", gt=0 ) channel: MessageChannel = Field( ..., description="Communication channel" ) subject: Optional[str] = Field( None, description="Message subject (for email)", max_length=200 ) content: str = Field( ..., description="Message body/content", min_length=1, max_length=5000 ) sender: str = Field( default="host", description="Sender identifier (host or guest)" ) delivery_status: MessageDeliveryStatus = Field( default=MessageDeliveryStatus.PENDING, description="Current delivery status" ) sent_at: Optional[datetime] = Field( None, description="Timestamp when message was sent (UTC)" ) delivered_at: Optional[datetime] = Field( None, description="Timestamp when message was delivered (UTC)" ) read_at: Optional[datetime] = Field( None, description="Timestamp when message was read (UTC)" ) error_message: Optional[str] = Field( None, description="Error details if delivery failed", max_length=500 ) class Config: json_schema_extra = { "example": { "id": 11111, "booking_id": 67890, "guest_id": 54321, "channel": "email", "subject": "Check-in Instructions", "content": "Welcome! Here are your check-in instructions...", "sender": "host", "delivery_status": "sent", "sent_at": "2025-11-09T10:00:00Z" } } ``` ### 4.4 Guest Guest profile and preferences. ```python from pydantic import BaseModel, Field, EmailStr from typing import Optional, List from datetime import datetime class Guest(BaseModel): """Guest profile with contact information and preferences. Represents a guest who has made or is making a booking. """ id: int = Field( ..., description="Unique guest ID", gt=0 ) first_name: str = Field( ..., description="Guest first name", min_length=1, max_length=100 ) last_name: str = Field( ..., description="Guest last name", min_length=1, max_length=100 ) email: EmailStr = Field( ..., description="Primary email address" ) phone: Optional[str] = Field( None, description="Phone number", max_length=50 ) language: str = Field( default="en", description="Preferred language (ISO 639-1 code)", pattern=r"^[a-z]{2}$" ) country: Optional[str] = Field( None, description="Country of residence", max_length=100 ) city: Optional[str] = Field( None, description="City of residence", max_length=100 ) total_bookings: int = Field( default=0, description="Total number of bookings made", ge=0 ) preferred_channels: List[MessageChannel] = Field( default_factory=lambda: [MessageChannel.EMAIL], description="Preferred communication channels" ) notes: Optional[str] = Field( None, description="Internal notes about guest", max_length=1000 ) created_at: datetime = Field( ..., description="Guest profile creation timestamp (UTC)" ) last_booking_at: Optional[datetime] = Field( None, description="Timestamp of most recent booking (UTC)" ) @property def full_name(self) -> str: """Get guest's full name.""" return f"{self.first_name} {self.last_name}" @property def is_repeat_guest(self) -> bool: """Check if guest has made multiple bookings.""" return self.total_bookings > 1 class Config: json_schema_extra = { "example": { "id": 54321, "first_name": "John", "last_name": "Smith", "email": "john.smith@example.com", "phone": "+1-555-123-4567", "language": "en", "country": "USA", "city": "New York", "total_bookings": 3, "preferred_channels": ["email", "sms"], "notes": "VIP guest - prefers early check-in", "created_at": "2024-01-15T10:00:00Z", "last_booking_at": "2025-10-01T14:30:00Z" } } ``` --- ## 5. Financial Models ### 5.1 FinancialReportPeriod Enumeration of reporting periods. ```python from enum import Enum class FinancialReportPeriod(str, Enum): """Financial reporting period types.""" DAILY = "daily" WEEKLY = "weekly" MONTHLY = "monthly" QUARTERLY = "quarterly" YEARLY = "yearly" CUSTOM = "custom" ``` ### 5.2 RevenueBreakdown Revenue breakdown by source. ```python from pydantic import BaseModel, Field from decimal import Decimal class RevenueBreakdown(BaseModel): """Revenue breakdown by booking source/channel.""" total_revenue: Decimal = Field( ..., description="Total revenue for period", ge=0, decimal_places=2 ) direct_bookings: Decimal = Field( default=Decimal("0.00"), description="Revenue from direct bookings", ge=0, decimal_places=2 ) airbnb: Decimal = Field( default=Decimal("0.00"), description="Revenue from Airbnb", ge=0, decimal_places=2 ) vrbo: Decimal = Field( default=Decimal("0.00"), description="Revenue from VRBO", ge=0, decimal_places=2 ) booking_com: Decimal = Field( default=Decimal("0.00"), description="Revenue from Booking.com", ge=0, decimal_places=2 ) other: Decimal = Field( default=Decimal("0.00"), description="Revenue from other sources", ge=0, decimal_places=2 ) class Config: json_schema_extra = { "example": { "total_revenue": "12500.00", "direct_bookings": "3000.00", "airbnb": "6000.00", "vrbo": "2500.00", "booking_com": "1000.00", "other": "0.00" } } ``` ### 5.3 ExpenseBreakdown Expense breakdown by category. ```python from pydantic import BaseModel, Field from decimal import Decimal class ExpenseBreakdown(BaseModel): """Expense breakdown by category.""" total_expenses: Decimal = Field( ..., description="Total expenses for period", ge=0, decimal_places=2 ) cleaning: Decimal = Field( default=Decimal("0.00"), description="Cleaning service expenses", ge=0, decimal_places=2 ) maintenance: Decimal = Field( default=Decimal("0.00"), description="Maintenance and repairs", ge=0, decimal_places=2 ) utilities: Decimal = Field( default=Decimal("0.00"), description="Utilities (water, electricity, internet)", ge=0, decimal_places=2 ) platform_fees: Decimal = Field( default=Decimal("0.00"), description="Platform commission fees", ge=0, decimal_places=2 ) supplies: Decimal = Field( default=Decimal("0.00"), description="Supplies and amenities", ge=0, decimal_places=2 ) other: Decimal = Field( default=Decimal("0.00"), description="Other expenses", ge=0, decimal_places=2 ) class Config: json_schema_extra = { "example": { "total_expenses": "3250.00", "cleaning": "1200.00", "maintenance": "500.00", "utilities": "300.00", "platform_fees": "1000.00", "supplies": "200.00", "other": "50.00" } } ``` ### 5.4 FinancialReport Comprehensive financial report. ```python from pydantic import BaseModel, Field from datetime import date from decimal import Decimal from typing import Optional class FinancialReport(BaseModel): """Financial report with revenue, expenses, and profitability metrics. Aggregates financial data for a specific period and optionally for a specific property. """ period_start: date = Field( ..., description="Report period start date" ) period_end: date = Field( ..., description="Report period end date" ) period_type: FinancialReportPeriod = Field( ..., description="Type of reporting period" ) listing_id: Optional[int] = Field( None, description="Specific listing ID (null for all properties)", gt=0 ) listing_name: Optional[str] = Field( None, description="Listing name (if listing_id specified)", max_length=200 ) revenue: RevenueBreakdown = Field( ..., description="Revenue breakdown" ) expenses: ExpenseBreakdown = Field( ..., description="Expense breakdown" ) net_income: Decimal = Field( ..., description="Net income (revenue - expenses)", decimal_places=2 ) total_bookings: int = Field( ..., description="Number of bookings in period", ge=0 ) total_nights_booked: int = Field( ..., description="Total nights booked in period", ge=0 ) average_daily_rate: Decimal = Field( ..., description="Average nightly rate for period", ge=0, decimal_places=2 ) occupancy_rate: Decimal = Field( ..., description="Occupancy rate as percentage (0-100)", ge=0, le=100, decimal_places=2 ) currency: str = Field( default="USD", description="Currency code (ISO 4217)", pattern=r"^[A-Z]{3}$" ) @property def profit_margin(self) -> Decimal: """Calculate profit margin as percentage.""" if self.revenue.total_revenue == 0: return Decimal("0.00") return (self.net_income / self.revenue.total_revenue) * 100 class Config: json_schema_extra = { "example": { "period_start": "2025-10-01", "period_end": "2025-10-31", "period_type": "monthly", "listing_id": 12345, "listing_name": "Cozy Downtown Apartment", "revenue": { "total_revenue": "12500.00", "direct_bookings": "3000.00", "airbnb": "6000.00", "vrbo": "2500.00", "booking_com": "1000.00" }, "expenses": { "total_expenses": "3250.00", "cleaning": "1200.00", "platform_fees": "1000.00" }, "net_income": "9250.00", "total_bookings": 15, "total_nights_booked": 75, "average_daily_rate": "166.67", "occupancy_rate": "80.65", "currency": "USD" } } ``` --- ## 6. Model Relationships ### Entity Relationship Diagram ``` ┌──────────────────┐ │ HostawayConfig │ │ (Singleton) │ └──────────────────┘ │ │ configures ▼ ┌──────────────────┐ │ AccessToken │ │ (Singleton) │ └──────────────────┘ │ │ authenticates ▼ ┌──────────────────┐ ┌──────────────────┐ │ Listing │◄──────┤ ListingSummary │ │ (1:1 full) │ │ (abbreviated) │ └──────────────────┘ └──────────────────┘ │ │ has ▼ ┌──────────────────┐ │ PricingInfo │ │ AvailabilityInfo│ └──────────────────┘ │ │ referenced by ▼ ┌──────────────────┐ ┌──────────────────┐ │ Booking │──────►│ PaymentInfo │ │ │ │ │ └──────────────────┘ └──────────────────┘ │ │ references ▼ ┌──────────────────┐ ┌──────────────────┐ │ Guest │◄──────┤ Message │ │ │ │ │ └──────────────────┘ └──────────────────┘ │ │ generates ▼ ┌──────────────────┐ │ FinancialReport │ │ │ └──────────────────┘ │ │ aggregates ▼ ┌──────────────────┐ │ RevenueBreakdown │ │ ExpenseBreakdown │ └──────────────────┘ ``` ### Key Relationships 1. **HostawayConfig → AccessToken**: Configuration provides credentials for token generation 2. **Listing → Booking**: Bookings reference a specific listing (listing_id) 3. **Guest → Booking**: Bookings reference a specific guest (guest_id) 4. **Booking → Message**: Messages are associated with a booking and guest 5. **Booking → PaymentInfo**: Each booking has payment details 6. **Listing → FinancialReport**: Reports can be property-specific or aggregated 7. **Listing → PricingInfo/AvailabilityInfo**: Listings embed pricing and availability --- ## Summary This data model document defines **19 core Pydantic models** organized into 5 domains: | Domain | Models | Purpose | |--------|--------|---------| | **Authentication** | HostawayConfig, AccessToken, TokenRefreshRequest, TokenRefreshResponse | OAuth credential management | | **Listings** | Listing, ListingSummary, PricingInfo, AvailabilityInfo | Property data and availability | | **Bookings** | Booking, BookingSearchFilters, PaymentInfo, BookingStatus, PaymentStatus | Reservation management | | **Guests** | Guest, Message, MessageChannel, MessageDeliveryStatus | Guest profiles and communication | | **Financial** | FinancialReport, RevenueBreakdown, ExpenseBreakdown, FinancialReportPeriod | Revenue and expense tracking | **Key Benefits**: - Full type safety with mypy strict compliance - Automatic validation of API data - Clear documentation through field descriptions - Example values for every model - Helper methods for common operations - Enum types for constrained values **Next Steps**: 1. Implement models in `src/models/` directory 2. Use models in API client (`src/services/hostaway_client.py`) 3. Integrate models into MCP tools 4. Add comprehensive unit tests 5. Update API contracts to reference these models

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/darrentmorgan/hostaway-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server