Skip to main content

Channel Field Mappings Guide

Version: 1.0 Last Updated: 2025-10-25 Applies To: MVP.0+ (all channel integrations)


Overview

This guide documents how TVL platform fields map to external channel APIs (Hostaway, Airbnb, VRBO). Each channel has different field names, data types, and validation rules.

Purpose:

  • Ensure accurate data transformation during sync
  • Prevent sync errors due to format mismatches
  • Document channel-specific quirks

Field Mapping Strategy

Transformation Pipeline

TVL Unit Model

[Field Mapper] ← Channel-specific rules

Channel Payload

[API Client] → External API

Hostaway Field Mappings

API Version: v1 Documentation: https://api.hostaway.com/v1/docs

Basic Property Fields

TVL FieldHostaway FieldTypeTransformRequired
unit.namenamestring(255)Direct
unit.unit_codeinternalNamestring(50)Direct
space.property_typepropertyTypeNamestring(50)Map to enum
unit.capacity_adultsaccommodatesintegerDirect
unit.bedroomsbedroomsintegerDirect
unit.bathroomsbathroomsdecimal(3,1)Direct

Property Type Mapping (Hostaway)

# TVL → Hostaway propertyTypeName
mappings:
villa: "Villa"
apartment: "Apartment"
hotel: "Hotel Room"
house: "House"
condo: "Condominium"
resort: "Resort"
cabin: "Cabin"
chalet: "Chalet"
cottage: "Cottage"

Address Fields

TVL FieldHostaway FieldRequired
space.address_line1address
space.citycity
space.state_provincestate
space.country_codecountryCode✅ (ISO 3166-1 alpha-2)
space.postal_codezipcode
space.latitudelatitude❌ (decimal)
space.longitudelongitude❌ (decimal)

Media Fields

TVL FieldHostaway FieldTransformNotes
media_assets[is_primary=true].urlpictureUse first primary imageRequired
media_assets[].urlphotos[]Array of image URLsUp to 50 images

Photo Requirements (Hostaway):

  • Min resolution: 1024x768
  • Max file size: 10MB
  • Formats: JPEG, PNG
  • Aspect ratio: 4:3 or 16:9 recommended

Airbnb Field Mappings

API Version: v2024 Documentation: https://airbnb.com/partner/docs

Basic Property Fields

TVL FieldAirbnb FieldTypeTransformRequired
unit.namenamestring(50)Truncate if > 50
space.property_typeproperty_type_idintegerMap to Airbnb enum
unit.capacity_adultsperson_capacityintegerDirect
unit.bedroomsbedroomsintegerMin 0
unit.bathroomsbathroomsintegerRound up

⚠️ CRITICAL: Airbnb requires bathrooms as INTEGER, not DECIMAL. TVL stores 1.5 baths, Airbnb needs 2.

Transform Function:

function mapBathroomsForAirbnb(tvl_bathrooms) {
return Math.ceil(tvl_bathrooms); // 1.5 → 2, 2.0 → 2, 2.5 → 3
}

Property Type Mapping (Airbnb)

# TVL → Airbnb property_type_id
mappings:
villa: 2 # "Villa"
apartment: 1 # "Apartment"
house: 3 # "House"
condo: 5 # "Condominium"
# ... See full mapping at https://airbnb.com/partner/property-types

Description Fields

TVL FieldAirbnb FieldMax LengthRequired
descriptions[0].titlename50
descriptions[0].bodydescription500
descriptions[0].body (full)summary500
Space-specific ruleshouse_rules500

⚠️ Airbnb character limits are STRICT - API will reject if exceeded.

Amenities Mapping

Airbnb uses numeric IDs for amenities. Example:

# TVL amenity → Airbnb amenity_id
wifi: 4
pool: 7
parking: 9
air_conditioning: 5
kitchen: 8
washer: 33
dryer: 34
tv: 1
gym: 15
hot_tub: 25

Full list: See airbnb_amenity_mapping.yaml (generated from API)


VRBO Field Mappings

API Version: v2 Documentation: https://developers.vrbo.com

Basic Property Fields

TVL FieldVRBO FieldTypeTransformRequired
unit.nameheadlinestring(150)Direct
space.property_typepropertyTypestringMap to enum
unit.capacity_adultsmaxOccupancyintegerDirect
unit.bedroomsbedroomsintegerDirect
unit.bathroomsbathrooms.fullintegerFloor value
TVL 1.5 bath →bathrooms.halfintegerRemainder

⚠️ VRBO Split Bathrooms: VRBO uses full + half integers, not decimal.

Transform Function:

function mapBathroomsForVRBO(tvl_bathrooms) {
const full = Math.floor(tvl_bathrooms);
const remainder = tvl_bathrooms - full;
const half = remainder >= 0.5 ? 1 : 0;

return { full, half };
// Example: 2.5 → { full: 2, half: 1 }
// Example: 2.0 → { full: 2, half: 0 }
}

Property Type Mapping (VRBO)

# TVL → VRBO propertyType
mappings:
villa: "VILLA"
apartment: "APARTMENT"
house: "HOUSE"
condo: "CONDO"
resort: "RESORT"
cabin: "CABIN"
cottage: "COTTAGE"

Media Requirements (VRBO)

TVL FieldVRBO FieldRequirements
media_assets[is_primary=true].urlimages.primaryMin 1200x900, JPEG only
media_assets[].urlimages.additional[]Min 800x600, max 24 images

⚠️ VRBO Image Restrictions:

  • JPEG only (no PNG)
  • Min resolution strictly enforced
  • Max 24 images per listing

Common Transform Functions

Bathroom Conversion

class FieldMapper {
static bathrooms(tvl_value, channel) {
switch (channel) {
case 'hostaway':
return tvl_value; // Decimal OK

case 'airbnb':
return Math.ceil(tvl_value); // Round up to integer

case 'vrbo':
const full = Math.floor(tvl_value);
const half = (tvl_value - full) >= 0.5 ? 1 : 0;
return { full, half };

default:
throw new Error(`Unknown channel: ${channel}`);
}
}
}

Property Type Conversion

const PROPERTY_TYPE_MAPS = {
hostaway: {
villa: 'Villa',
apartment: 'Apartment',
// ...
},
airbnb: {
villa: 2, // Numeric IDs
apartment: 1,
// ...
},
vrbo: {
villa: 'VILLA', // UPPERCASE strings
apartment: 'APARTMENT',
// ...
}
};

class FieldMapper {
static propertyType(tvl_type, channel) {
const map = PROPERTY_TYPE_MAPS[channel];
if (!map || !map[tvl_type]) {
throw new Error(
`Unknown property type '${tvl_type}' for channel '${channel}'`
);
}
return map[tvl_type];
}
}

Validation Rules

Pre-Sync Validation

Before sending to any channel, validate:

  1. Required fields present
  2. Field length limits
  3. Data type constraints
  4. Enum value validity
  5. Image URL accessibility

Example validator:

class ChannelValidator {
static validateForHostaway(unit) {
const errors = [];

if (!unit.name || unit.name.length > 255) {
errors.push('name must be 1-255 characters');
}

if (!unit.capacity_adults || unit.capacity_adults < 1) {
errors.push('capacity_adults must be >= 1');
}

if (unit.bathrooms < 0) {
errors.push('bathrooms cannot be negative');
}

// ... more checks

return errors;
}
}

Error Handling

Common Sync Errors

ErrorChannelCauseFix
field_too_longAirbnbName > 50 charsTruncate in transform
invalid_property_typeAllUnmapped typeAdd to mapping YAML
invalid_image_formatVRBOPNG imageConvert to JPEG or exclude
missing_required_fieldAllIncomplete unit dataFill before sync

Testing Field Mappings

Unit Test Example

describe('FieldMapper.bathrooms', () => {
it('rounds up for Airbnb', () => {
expect(FieldMapper.bathrooms(1.5, 'airbnb')).toBe(2);
expect(FieldMapper.bathrooms(2.0, 'airbnb')).toBe(2);
});

it('splits for VRBO', () => {
expect(FieldMapper.bathrooms(2.5, 'vrbo')).toEqual({ full: 2, half: 1 });
expect(FieldMapper.bathrooms(3.0, 'vrbo')).toEqual({ full: 3, half: 0 });
});

it('preserves decimal for Hostaway', () => {
expect(FieldMapper.bathrooms(1.5, 'hostaway')).toBe(1.5);
});
});