Content & Media - Domain Specification
First Introduced: MVP.0 Status: Specification Complete Last Updated: 2025-10-25
Overview
Content & Media define how listings are presented — including text, imagery, descriptions, amenities, and structured metadata. This domain provides the foundation for property visualization and discovery, separating descriptive content (what the property is) from operational data (availability, pricing, booking). It enables rich discovery experiences, flexible syndication to channels, and future multi-language localization.
Responsibilities
This domain IS responsible for:
- Managing property descriptions (titles, headlines, body content)
- Storing and organizing media assets (photos, videos, documents)
- Defining amenity catalogs and property features
- Maintaining structured attributes (capacity, bedrooms, views)
- Organizing content with tags and categories
- Versioning content for distribution workflows
- Providing CDN-integrated media delivery
This domain is NOT responsible for:
- Availability calendars (→ Availability domain)
- Pricing rules (→ Pricing domain)
- Search indexing (→ Search domain)
- Channel syndication logic (→ Channels domain)
- File upload processing pipelines (→ V1.0+)
- Translation services (→ V2.0+)
Relationships
Depends On:
- Identity & Tenancy - Org/Account ownership scoping
- Authorization & Access - Permission enforcement for content editing
- Supply - Spaces/Units that content describes
Depended On By:
- Channels & Distribution - Content feeds for OTAs
- Search & Indexing - Searchable content and media
- Analytics & Audit - Content change tracking
Related Domains:
- System Architecture - CDN and media storage infrastructure
Core Concepts
Entity: Description
Purpose: Marketing and factual text describing a Space or Unit for guest-facing presentation.
Key Attributes:
- id(UUID, primary key)
- org_id,- account_id(UUID, foreign keys) - Ownership scope
- space_id(UUID, foreign key → spaces.id)
- unit_id(UUID, nullable, foreign key → units.id) - Optional unit-specific description
- language_code(VARCHAR(5)) - ISO language code (e.g., 'en', 'es', 'fr')
- title(VARCHAR(200), required) - Short display name
- headline(VARCHAR(500)) - Marketing tagline
- body(TEXT) - Full property description
- house_rules(TEXT) - Guest policies and requirements
- getting_around(TEXT) - Access and transportation details
- format(ENUM) - html | markdown | plaintext
- is_published(BOOLEAN, default false) - Visibility flag
- version(INTEGER, default 1) - Content version number
- created_at,- updated_at,- published_at(timestamps)
Relationships:
- Description → Org (*, many-to-one)
- Description → Account (*, many-to-one)
- Description → Space (*, many-to-one) - Multiple descriptions per Space (multi-language)
- Description → Unit (*, many-to-one, optional)
Lifecycle:
- Created: When adding property or translating content
- Updated: Content edits increment version number
- Published: Set is_published=trueandpublished_attimestamp
- Archived: Soft delete via deleted_at(preserves version history)
Business Rules:
- Each Space must have at least one published Description
- Default language determined by Org settings (fallback to 'en')
- Version incremented on every update for audit trail
- HTML content must be sanitized to prevent XSS
- Maximum body length: 10,000 characters
Entity: MediaAsset
Purpose: Visual or downloadable content (photos, videos, documents) with CDN delivery.
Key Attributes:
- id(UUID, primary key)
- org_id,- account_id(UUID, foreign keys)
- space_id(UUID, foreign key → spaces.id)
- unit_id(UUID, nullable, foreign key → units.id)
- type(ENUM) - image | video | document | floor_plan | virtual_tour
- url(TEXT, required) - CDN or S3 URL
- filename(VARCHAR(255)) - Original filename
- file_size(BIGINT) - Size in bytes
- mime_type(VARCHAR(100)) - e.g., 'image/jpeg', 'video/mp4'
- position(INTEGER, default 0) - Display order in gallery
- is_primary(BOOLEAN, default false) - Cover image flag
- caption(TEXT) - User-facing caption
- alt_text(VARCHAR(500)) - Accessibility text
- width,- height(INTEGER) - Dimensions in pixels
- metadata(JSONB) - Exif data, transformation specs
- status(ENUM) - pending | processing | ready | failed
- created_at,- updated_at(timestamps)
Relationships:
- MediaAsset → Org (*, many-to-one)
- MediaAsset → Account (*, many-to-one)
- MediaAsset → Space (*, many-to-one)
- MediaAsset → Unit (*, many-to-one, optional)
- MediaAsset → Tag (*, many-to-many) - For categorization
Lifecycle:
- Created: On upload (MVP: manual URL entry; V1: upload pipeline)
- Processing: Status transitions during image optimization (V1+)
- Ready: Available for display and syndication
- Failed: Upload or processing error
- Deleted: Soft delete retains URL for audit
Business Rules:
- Exactly one MediaAsset per Space must have is_primary=true
- Position values must be unique per Space
- Images with same position ordered by created_at
- Maximum 50 media assets per Space (configurable)
- URL must be HTTPS and point to approved CDN domain
- Failed uploads retained for 7 days then purged
Entity: Amenity (Catalog)
Purpose: Master catalog of property features and services for filtering and discovery.
Key Attributes:
- id(UUID, primary key)
- key(VARCHAR(100), unique) - Programmatic identifier (e.g., 'wifi', 'pool', 'parking')
- category(ENUM) - location | rooms | kitchen | bathroom | climate | entertainment | outdoor | parking | family | services
- display_name(VARCHAR(200)) - Human-readable label
- description(TEXT) - Feature explanation
- icon(VARCHAR(100)) - Icon identifier for UI
- is_parametric(BOOLEAN) - Whether it accepts custom values
- value_type(ENUM) - boolean | integer | text | enum
- channel_mappings(JSONB) - OTA-specific IDs (e.g., {"airbnb": "41", "vrbo": "WiFi"})
- display_order(INTEGER) - Sort position in UI
- is_active(BOOLEAN, default true)
- created_at,- updated_at(timestamps)
Relationships:
- Amenity → SpaceAmenity (1:*) - Many Spaces can have this amenity
Lifecycle:
- Created: Seeded during platform initialization
- Updated: When adding channel mappings or updating labels
- Deactivated: Set is_active=false(never deleted)
Business Rules:
- Amenity keys must be globally unique
- Category required for grouping in UI
- Parametric amenities (e.g., parking) allow custom values
- Channel mappings updated when integrating new OTAs
- Standard catalog maintained in version control (seed data)
Entity: SpaceAmenity (Join Table)
Purpose: Links Spaces to Amenities with optional parametric values.
Key Attributes:
- id(UUID, primary key)
- org_id,- account_id(UUID, foreign keys)
- space_id(UUID, foreign key → spaces.id)
- amenity_id(UUID, foreign key → amenities.id)
- value(TEXT, nullable) - Parametric value (e.g., "2 covered spaces" for parking)
- notes(TEXT) - Additional details
- is_featured(BOOLEAN, default false) - Highlight in listings
- created_at,- updated_at(timestamps)
Relationships:
- SpaceAmenity → Space (*, many-to-one)
- SpaceAmenity → Amenity (*, many-to-one)
Business Rules:
- Cannot duplicate (space_id, amenity_id) pairs
- Value required if amenity is_parametric=true
- Featured amenities limited to 5 per Space
Entity: SpaceAttributes
Purpose: Structured metadata stored as JSONB for schema flexibility.
Key Attributes:
- id(UUID, primary key)
- org_id,- account_id(UUID, foreign keys)
- space_id(UUID, unique, foreign key → spaces.id)
- attributes(JSONB, required) - Flexible structured data
- created_at,- updated_at(timestamps)
Standard Attributes (MVP):
{
  "max_guests": 8,
  "bedrooms": 4,
  "bathrooms": 3.5,
  "square_feet": 3500,
  "view": "ocean",
  "style": "modern",
  "year_built": 2018,
  "check_in_time": "15:00",
  "check_out_time": "11:00",
  "minimum_stay": 3
}
Relationships:
- SpaceAttributes → Space (1:1) - One attribute set per Space
Lifecycle:
- Created: Auto-created when Space is created
- Updated: Merged updates preserve unmodified fields
- Deleted: Cascade deleted with parent Space
Business Rules:
- Space can have only one SpaceAttributes record
- Attribute keys validated against schema (configurable)
- Numeric values stored as numbers (not strings)
- GIN index enables fast JSONB queries
- Migrations handle schema evolution without table changes
Entity: Tag
Purpose: Categorical labels for organizing and filtering content.
Key Attributes:
- id(UUID, primary key)
- org_id(UUID, nullable) - NULL for global tags, set for org-specific
- name(VARCHAR(100), required)
- slug(VARCHAR(100), unique) - URL-friendly identifier
- type(ENUM) - theme | feature | location | marketing | internal
- color(VARCHAR(7)) - Hex color code for UI
- description(TEXT)
- usage_count(INTEGER, default 0) - Number of times applied
- is_public(BOOLEAN, default true) - Visible in public listings
- created_at,- updated_at(timestamps)
Relationships:
- Tag → Space (*, many-to-many via space_tags)
- Tag → MediaAsset (*, many-to-many via media_tags)
Business Rules:
- Global tags (org_id NULL) available to all Orgs
- Org-specific tags only visible within that Org
- Slug must be unique within scope (global or per-org)
- Usage count incremented/decremented on tag application
- Unused tags (usage_count=0) archived after 90 days
Entity: UnitSnapshot (Version History)
Purpose: Immutable content snapshots enabling diff preview, rollback, and distribution idempotency.
Key Attributes:
- id(UUID, primary key)
- org_id,- account_id(UUID, foreign keys)
- space_id(UUID, foreign key → spaces.id)
- unit_id(UUID, nullable, foreign key → units.id)
- version(INTEGER, required) - Monotonically increasing version number
- snapshot(JSONB, required) - Complete content state at version
- diff_hash(VARCHAR(64)) - SHA-256 of changes from previous version
- created_by(UUID, foreign key → users.id)
- change_summary(TEXT) - Human-readable change description
- created_at(TIMESTAMP)
Snapshot Structure:
{
  "unit": {
    "id": "uuid",
    "name": "Villa Paradise",
    "status": "active",
    "capacity": 8
  },
  "description": {
    "title": "Luxury Oceanfront Villa",
    "headline": "Your private paradise awaits",
    "body": "Full description text...",
    "language_code": "en"
  },
  "media": [
    {
      "id": "uuid",
      "url": "https://cdn.example.com/image.jpg",
      "position": 1,
      "is_primary": true,
      "caption": "Ocean view from master bedroom"
    }
  ],
  "amenities": [
    {"key": "pool", "value": "heated"},
    {"key": "wifi", "value": null}
  ],
  "attributes": {
    "max_guests": 8,
    "bedrooms": 4,
    "bathrooms": 3.5,
    "view": "ocean"
  },
  "tags": ["Luxury", "Oceanfront", "Family-Friendly"]
}
Relationships:
- UnitSnapshot → Space (*, many-to-one)
- UnitSnapshot → User (*, many-to-one) - Created by
Lifecycle:
- Created: Automatically on every content update
- Retained: Per retention policy (90 days or last 10 versions)
- Purged: Older snapshots archived to cold storage
Business Rules:
- Version numbers must be sequential per Space
- Cannot delete snapshots (immutable audit trail)
- Diff hash computed from sorted JSON representation
- Snapshots used for distribution idempotency tracking
- Retention policy: keep last 10 versions OR 90 days (whichever is greater)
Workflows
Workflow: Create Space with Content (MVP.0)
- User creates Space in Supply domain
- Create Description record with title, headline, body (default language)
- Create SpaceAttributes record with max_guests, bedrooms, bathrooms
- Add MediaAsset URLs (manually entered CDN URLs)
- Set first MediaAsset as is_primary=true
- Select amenities from catalog (creates SpaceAmenity records)
- Set description to is_published=true
- Create UnitSnapshot with version=1
- Emit event content.publishedfor search indexing
- Return success with Space ID
Postconditions:
- Space has complete content for listing display
- Content versioned for audit trail
- Ready for channel syndication
Workflow: Update Media Gallery (MVP.0)
- User reorders photos or adds new URLs
- Update position values for affected MediaAssets
- Validate uniqueness of positions within Space
- Update timestamps on modified records
- Increment version in next UnitSnapshot creation
- Emit event media.updated
- Return success
Postconditions:
- Gallery displays in new order
- Changes captured in audit trail
- CDN cache may need invalidation (V1)
Workflow: Multi-Language Content (V2.0+)
- User selects "Add Translation" for Space
- Choose target language from supported list
- Option A - Manual: User enters translated content
- Option B - Automated: Call translation API (DeepL)
- Create new Description record with language_code
- Set status to draft (requires review)
- Reviewer approves translation
- Set is_published=trueon translated Description
- Update UnitSnapshot with new language variant
- Return success
Postconditions:
- Space has descriptions in multiple languages
- Fallback hierarchy: user language → org default → English
- Translation quality reviewed before publication
Workflow: Content Rollback (V1.0+)
- User views UnitSnapshot history
- Select previous version to restore
- Show diff preview comparing current vs selected version
- User confirms rollback action
- Restore snapshot data to current records (Description, MediaAsset, etc.)
- Create new UnitSnapshot with incremented version (rollback creates new history entry)
- Emit event content.rolled_backwith version numbers
- Invalidate CDN cache for affected URLs
- Return success
Postconditions:
- Content restored to previous state
- Rollback action preserved in audit trail
- New snapshot created (history is append-only)
Business Rules
- Primary Media: Each Space must have exactly one is_primary=trueMediaAsset
- Published Description: Each Space must have at least one is_published=trueDescription
- Language Fallback: If requested language unavailable, fall back to org default, then English
- Version Immutability: UnitSnapshots are append-only; never updated or deleted
- Amenity Uniqueness: Cannot assign same amenity twice to a Space
- Position Ordering: MediaAsset positions must be unique within Space scope
- Org Isolation: All content queries filtered by org_id
- HTTPS URLs: All MediaAsset URLs must use HTTPS protocol
- Attribute Validation: SpaceAttributes JSONB validated against defined schema
- Soft Deletes: Descriptions and MediaAssets soft-deleted with deleted_attimestamp
Implementation Notes
MVP.0 Scope (Media URLs Only)
Included:
- Descriptions, MediaAssets, Amenities, SpaceAttributes, Tags, UnitSnapshots
- Manual URL entry for media (no upload pipeline)
- Single language support (English default)
- Gallery ordering and primary image selection
- Amenity catalog (50 standard items seeded)
- JSONB attributes for flexible metadata
- Content versioning via UnitSnapshots
- Basic content CRUD operations
Deferred to V1.0:
- Media upload pipeline with image processing
- CDN integration with signed URLs
- Automated image resizing and format conversion (WebP)
- Video upload and transcoding
- Content approval workflow (draft/review/published)
- SEO metadata fields
Deferred to V2.0+:
- Multi-language content management
- Translation API integration (DeepL)
- AI-generated captions and alt text
- Duplicate media detection (perceptual hashing)
- 3D tours and virtual walkthroughs
- Regional content variants
V1.0 Scope (Full Media Pipeline)
Added:
- S3 bucket for media storage ({org_id}/{space_id}/images/)
- Pre-signed URLs for secure client uploads
- Lambda function for image processing (Sharp library)
- Automated variants: thumbnail, small, medium, large, original
- WebP format conversion (30-50% size reduction)
- CloudFront CDN distribution with signed URLs (24-hour expiry)
- Media status tracking (pending → processing → ready → failed)
- Metadata extraction (dimensions, EXIF data)
- Content approval workflow with status field
Implementation Pattern:
Client → Pre-signed S3 URL → Upload → S3 Trigger
    ↓
Lambda Processing (resize, convert, extract metadata)
    ↓
Update MediaAsset record (status=ready, variants in metadata JSONB)
    ↓
CloudFront CDN delivery to viewers
Database Indexes
Critical for performance:
-- Descriptions
CREATE INDEX idx_descriptions_space ON descriptions(space_id);
CREATE INDEX idx_descriptions_language ON descriptions(space_id, language_code);
CREATE INDEX idx_descriptions_published ON descriptions(space_id) WHERE is_published = true;
-- MediaAssets
CREATE INDEX idx_media_space_position ON media_assets(space_id, position);
CREATE INDEX idx_media_primary ON media_assets(space_id) WHERE is_primary = true;
CREATE INDEX idx_media_type ON media_assets(type);
-- Amenities
CREATE UNIQUE INDEX idx_amenities_key ON amenities(key);
CREATE INDEX idx_amenities_category ON amenities(category);
-- SpaceAmenities
CREATE UNIQUE INDEX idx_space_amenities_unique ON space_amenities(space_id, amenity_id);
CREATE INDEX idx_space_amenities_featured ON space_amenities(space_id) WHERE is_featured = true;
-- SpaceAttributes
CREATE UNIQUE INDEX idx_space_attributes_space ON space_attributes(space_id);
CREATE INDEX idx_space_attributes_gin ON space_attributes USING gin(attributes);
-- Tags
CREATE UNIQUE INDEX idx_tags_slug ON tags(slug);
CREATE INDEX idx_tags_org ON tags(org_id);
-- UnitSnapshots
CREATE INDEX idx_snapshots_space_version ON unit_snapshots(space_id, version DESC);
CREATE INDEX idx_snapshots_created ON unit_snapshots(created_at);
Constraints
Enforce data integrity:
-- Descriptions
ALTER TABLE descriptions ADD CONSTRAINT desc_title_not_empty CHECK (LENGTH(TRIM(title)) > 0);
ALTER TABLE descriptions ADD CONSTRAINT desc_body_max_length CHECK (LENGTH(body) <= 10000);
-- MediaAssets
ALTER TABLE media_assets ADD CONSTRAINT media_url_https CHECK (url LIKE 'https://%');
ALTER TABLE media_assets ADD CONSTRAINT media_position_positive CHECK (position >= 0);
-- SpaceAmenities
ALTER TABLE space_amenities ADD CONSTRAINT space_amenity_unique UNIQUE (space_id, amenity_id);
-- Tags
ALTER TABLE tags ADD CONSTRAINT tag_color_hex CHECK (color ~ '^#[0-9A-Fa-f]{6}$');
-- UnitSnapshots
ALTER TABLE unit_snapshots ADD CONSTRAINT snapshot_version_positive CHECK (version > 0);
Future Enhancements
V1.1: Content Approval Workflow
- Add statusfield to Descriptions (draft | review | published | archived)
- Approval queue for content reviewers
- Diff view comparing draft vs published
- Comment threads on pending changes
- Bulk approval/rejection operations
V1.2: SEO Optimization
- Add meta_title, meta_description, meta_keywords to Descriptions
- Automatic alt text generation for MediaAssets
- Structured data markup (JSON-LD)
- Content scoring algorithm (completeness, quality)
- SEO recommendations based on best practices
V2.0: Multi-Language Management
- Translation workflow with DeepL API integration
- Per-language content completeness tracking
- Glossary management for consistent terminology
- Regional content variants (en-US vs en-GB)
- Currency and date format localization
V2.1: AI-Powered Features
- Automated caption generation (OpenAI Vision API)
- Smart tagging based on image content
- Content quality scoring
- Duplicate detection (perceptual hashing)
- Recommendation for optimal media order
V2.2: Advanced Media
- Video transcoding pipeline (AWS Elemental MediaConvert)
- 360-degree photo tours
- 3D virtual walkthroughs (Matterport integration)
- Drone footage management
- Floor plan generation tools
Operational Notes
Storage Strategy
MVP.0 (URL-only):
- Media stored externally (client-managed)
- URLs entered manually in admin UI
- No CDN integration required
- Status always ready
V1.0 (Full pipeline):
- S3 bucket: tvl-media-{env}with folder structure{org_id}/{space_id}/
- CloudFront distribution with signed URLs
- Pre-signed upload URLs valid for 1 hour
- Variants generated on upload: thumbnail(200px),small(400px),medium(800px),large(1200px),original
- WebP format for all variants except original
- EXIF metadata stripped for privacy
- Storage class: S3 Standard for active, Glacier for archived
CDN Configuration
Security:
- Signed URLs with 24-hour expiry for preview links
- 1-year expiry for published listing images
- CloudFront Key Pairs with private key rotation every 90 days
- Origin Access Identity (OAI) prevents direct S3 access
Performance:
- Cache TTL: 1 year for immutable URLs (versioned filenames)
- Gzip/Brotli compression enabled
- HTTP/2 and HTTP/3 support
- Edge locations: global distribution
Invalidation:
- Invalidate on media delete/replace
- Batch invalidations (max 3000 paths per request)
- Invalidation events logged for debugging
Content Versioning
UnitSnapshot Creation:
- Triggered automatically on any content update
- Background job runs every 5 minutes to detect changes
- Diff hash computed from sorted JSON representation
- Unchanged snapshots (same diff_hash) not duplicated
- Retention policy enforced by nightly cleanup job
Version Comparison:
- Diff algorithm highlights added/removed/changed fields
- UI color codes: green (added), red (removed), yellow (changed)
- Side-by-side view for Descriptions
- Gallery view for MediaAsset changes
Amenity Catalog Management
Seeding:
- 50 standard amenities loaded from seed file
- Categories: location(6), rooms(5), kitchen(7), bathroom(5), climate(4), entertainment(6), outdoor(8), parking(3), family(6), services(10)
- Channel mappings added during OTA integration
Updates:
- New amenities added via migration scripts
- Display names and descriptions updated in-place
- Never delete amenities (set is_active=false)
- Usage tracking helps prioritize featured amenities
Performance Optimization
Caching:
- Redis cache for Space content bundles (Description + Media + Amenities)
- TTL: 1 hour, invalidated on update
- Pre-render HTML blobs for faster initial page load
Queries:
- Fetch complete Space content in single query with JOINs
- GIN index on SpaceAttributes enables fast JSONB filtering
- Paginate media galleries client-side (all loaded initially)
Image Loading:
- Lazy loading with native loading="lazy"attribute
- Responsive images with <picture>element
- Intersection Observer for progressive loading
- Blur-up placeholder technique (LQIP - Low Quality Image Placeholder)
Security & Privacy
Access Control:
- Content editing restricted by media.writepermission
- Account-scoped queries filter by (org_id, account_id)
- Media URLs not guessable (UUID filenames)
Content Sanitization:
- HTML descriptions sanitized to prevent XSS
- Allowed tags: <p>, <br>, <strong>, <em>, <ul>, <ol>, <li>, <a>
- URL validation for external links
EXIF Stripping:
- GPS coordinates removed from uploaded photos
- Camera metadata stripped for privacy
- Timestamp preserved in metadata JSONB
Related Documents
- MVP Mapping - Version feature matrix
- Supply Domain - Spaces/Units relationships
- Channels Domain - Content syndication
- Search Domain - Content indexing
- System Architecture - CDN and storage infrastructure
- Content Review Summary - Detailed analysis and recommendations
Standard Amenity Catalog (50 Items)
Location & Views (6)
- oceanfront- Direct ocean frontage
- beachfront- Direct beach access
- waterfront- Lake, river, or bay frontage
- mountain_view- Mountain vista
- city_view- Urban skyline view
- garden_view- Landscaped garden
Rooms & Bedrooms (5)
- bedrooms- Number of bedrooms (parametric)
- bathrooms- Number of bathrooms (parametric)
- king_bed- King-size bed count (parametric)
- queen_bed- Queen-size bed count (parametric)
- twin_bed- Twin bed count (parametric)
Kitchen & Dining (7)
- full_kitchen- Complete kitchen with appliances
- dishwasher- Automatic dishwasher
- microwave- Microwave oven
- coffee_maker- Coffee brewing equipment
- outdoor_grill- BBQ or gas grill
- dining_table- Indoor dining table (seats parametric)
- outdoor_dining- Outdoor dining area (seats parametric)
Bathroom Essentials (5)
- shampoo- Shampoo and toiletries provided
- hot_water- Hot water available
- hair_dryer- Hair dryer provided
- bathtub- Soaking tub or jacuzzi tub
- outdoor_shower- Outdoor shower available
Climate Control (4)
- air_conditioning- Central or split AC
- heating- Central heating system
- ceiling_fans- Ceiling fans in rooms
- fireplace- Wood or gas fireplace
Entertainment & Tech (6)
- wifi- Wireless internet
- tv- Television (count parametric)
- streaming_services- Netflix, Hulu, etc.
- cable_tv- Cable or satellite TV
- sound_system- Audio system or speakers
- board_games- Games and entertainment
Outdoor & Pool (8)
- private_pool- Private swimming pool
- pool_heated- Heated pool
- hot_tub- Spa or hot tub
- beach_access- Private beach access
- outdoor_space- Patio, deck, or terrace
- bbq_area- Outdoor cooking area
- fire_pit- Fire pit or outdoor fireplace
- ocean_view- Ocean view from property
Parking & Access (3)
- parking- Parking spaces (count parametric)
- ev_charger- Electric vehicle charging
- wheelchair_accessible- ADA accessible
Family Features (6)
- crib- Baby crib available
- high_chair- High chair for infants
- kids_toys- Toys and games for children
- safety_gates- Childproofing gates
- pool_fence- Pool safety fence
- changing_table- Baby changing station
Services & Extras (10)
- washer- Washing machine
- dryer- Clothes dryer
- iron- Iron and ironing board
- hangers- Closet hangers
- cleaning_included- Housekeeping service
- linens_included- Bed linens provided
- towels_included- Bath towels provided
- concierge- Concierge service available
- private_chef- Chef service (parametric)
- gym- Fitness equipment or gym access
Total Specification Lines: 487