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 Field | Hostaway Field | Type | Transform | Required | 
|---|---|---|---|---|
| unit.name | name | string(255) | Direct | ✅ | 
| unit.unit_code | internalName | string(50) | Direct | ❌ | 
| space.property_type | propertyTypeName | string(50) | Map to enum | ✅ | 
| unit.capacity_adults | accommodates | integer | Direct | ✅ | 
| unit.bedrooms | bedrooms | integer | Direct | ✅ | 
| unit.bathrooms | bathrooms | decimal(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 Field | Hostaway Field | Required | 
|---|---|---|
| space.address_line1 | address | ✅ | 
| space.city | city | ✅ | 
| space.state_province | state | ✅ | 
| space.country_code | countryCode | ✅ (ISO 3166-1 alpha-2) | 
| space.postal_code | zipcode | ❌ | 
| space.latitude | latitude | ❌ (decimal) | 
| space.longitude | longitude | ❌ (decimal) | 
Media Fields
| TVL Field | Hostaway Field | Transform | Notes | 
|---|---|---|---|
| media_assets[is_primary=true].url | picture | Use first primary image | Required | 
| media_assets[].url | photos[] | Array of image URLs | Up 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 Field | Airbnb Field | Type | Transform | Required | 
|---|---|---|---|---|
| unit.name | name | string(50) | Truncate if > 50 | ✅ | 
| space.property_type | property_type_id | integer | Map to Airbnb enum | ✅ | 
| unit.capacity_adults | person_capacity | integer | Direct | ✅ | 
| unit.bedrooms | bedrooms | integer | Min 0 | ✅ | 
| unit.bathrooms | bathrooms | integer | Round 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 Field | Airbnb Field | Max Length | Required | 
|---|---|---|---|
| descriptions[0].title | name | 50 | ✅ | 
| descriptions[0].body | description | 500 | ✅ | 
| descriptions[0].body(full) | summary | 500 | ❌ | 
| Space-specific rules | house_rules | 500 | ❌ | 
⚠️ 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 Field | VRBO Field | Type | Transform | Required | 
|---|---|---|---|---|
| unit.name | headline | string(150) | Direct | ✅ | 
| space.property_type | propertyType | string | Map to enum | ✅ | 
| unit.capacity_adults | maxOccupancy | integer | Direct | ✅ | 
| unit.bedrooms | bedrooms | integer | Direct | ✅ | 
| unit.bathrooms | bathrooms.full | integer | Floor value | ✅ | 
| TVL 1.5 bath → | bathrooms.half | integer | Remainder | ❌ | 
⚠️ 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 Field | VRBO Field | Requirements | 
|---|---|---|
| media_assets[is_primary=true].url | images.primary | Min 1200x900, JPEG only | 
| media_assets[].url | images.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:
- Required fields present
- Field length limits
- Data type constraints
- Enum value validity
- 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
| Error | Channel | Cause | Fix | 
|---|---|---|---|
| field_too_long | Airbnb | Name > 50 chars | Truncate in transform | 
| invalid_property_type | All | Unmapped type | Add to mapping YAML | 
| invalid_image_format | VRBO | PNG image | Convert to JPEG or exclude | 
| missing_required_field | All | Incomplete unit data | Fill 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);
  });
});
Related Documents
- Channels & Distribution Specification
- MVP.0 Specification - Hostaway integration
- MVP.2 Specification - Multi-channel