Friday, September 12, 2025

Integrating with Rightmove's Enterprise Property API

Paul (Founder)
Development

Rightmove dominates UK property search with over 130 million visits monthly. For letting agencies, advertising properties on Rightmove isn't optional—it's essential. But Rightmove's Real Time Datafeed (RTDF) API is notoriously complex: it requires P12 certificate authentication, expects deeply nested XML-style JSON payloads, enforces strict data validation, and has particular requirements about photo URLs and property addresses.

Week 37 implemented complete Rightmove integration as the first real-world test of our new apps system. This article explores the technical challenges we encountered and how we solved them whilst maintaining the flexibility our apps architecture provides.

What Your Team Will Notice

Once configured, Rightmove integration operates invisibly. Create a property, add photos, set the rent price—and within minutes that property appears on Rightmove exactly as entered in LetAdmin. Update the description or change the rent, and Rightmove reflects those changes automatically through webhook-triggered synchronisation.

The integration respects your advertising workflow. Properties marked "Not For Advertising" never send to Rightmove. Those marked "Available" or "Under Offer" sync with appropriate status indicators. When a property lets, marking it "Let" automatically removes it from Rightmove, keeping your portal listings aligned with actual availability.

Photo management becomes seamless. Drag photos to reorder them in LetAdmin, and that ordering flows to Rightmove. Add a new photo, and Rightmove receives it within seconds. Delete a photo, and it disappears from the portal automatically. The integration maintains photo ordering consistency across platforms, ensuring your best property images always display prominently.

For agencies managing multiple branches, the integration handles branch-specific configuration. Each branch maintains its own Rightmove credentials and branch identifiers, with properties routing to appropriate Rightmove branches automatically based on agency structure.

Under the Bonnet: Enterprise API Authentication

Rightmove's API uses P12 certificate authentication rather than API keys. This provides stronger security but requires careful implementation:

# High-level authentication approach
# Actual implementation includes additional security measures
class RightmoveApiService
  def initialize(property)
    @property = property
    @installation = fetch_rightmove_installation
    @certificate = parse_p12_certificate(@installation.settings['certificate'])
  end

  def send_property
    # Constructs HTTP client with certificate authentication
    # Makes API request with proper headers and certificate verification
    # Handles certificate expiry and renewal workflows
  end
end

The certificate lives in the app installation's encrypted settings, accessed only when making API calls. Certificate parsing happens securely without exposing the certificate data to logs or error tracking.

Complex Property Serialisation

Rightmove expects properties in a specific nested structure that differs significantly from LetAdmin's database schema. A serialiser class handles this transformation:

class RightmovePropertySerializer
  def initialize(property, branch_id)
    @property = property
    @branch_id = branch_id
  end

  def to_rightmove_payload
    {
      network: {
        network_id: @branch_id
      },
      branch: {
        branch_id: @branch_id,
        channel: 1,  # Lettings channel
        overseas: false
      },
      property: build_property_data,
      details: build_property_details,
      media: build_media_section,
      price_information: build_pricing
    }
  end
end

This serialiser consolidates all Rightmove-specific mapping logic in one place, making it easy to update when Rightmove's API evolves. The serialiser handles numerous edge cases:

Address Privacy

Rightmove requires a display_address that shows general location without revealing the exact property:

def build_display_address
  # Removes house numbers and property identifiers for security
  # Shows only: street name, town, postcode area
  # Example: "High Street, Worcester, WR1" (not "123 High Street")

  parts = [
    sanitised_street_name,  # Removes leading numbers
    @property.town,
    @property.postcode_area  # First part only: "WR1" not "WR1 2AB"
  ].compact

  parts.join(', ')
end

This privacy protection prevents prospective tenants identifying properties before viewing arrangements are made, a security requirement for occupied properties.

Property Type Mapping

LetAdmin's property types don't map directly to Rightmove's taxonomy. The serialiser handles translation:

def map_property_type
  mapping = {
    'flat' => { type: 0, style: 'Apartment' },
    'house' => { type: 1, style: 'Terraced' },
    'bungalow' => { type: 2, style: 'Detached' },
    'studio' => { type: 0, style: 'Studio' }
  }

  mapped = mapping[@property.property_type] || { type: 1, style: 'House' }

  {
    property_type: mapped[:type],
    property_sub_type: mapped[:style]
  }
end

This ensures property types display correctly on Rightmove regardless of how agencies categorise them internally.

Outside Space and Parking

Rightmove has specific enumerations for outside space types and parking arrangements:

def build_outside_space
  return [] unless @property.outside_space_type.present?

  spaces = []
  spaces << 'Private Garden' if @property.outside_space_type == 'private_garden'
  spaces << 'Communal Gardens' if @property.outside_space_type == 'communal_garden'
  spaces << 'Balcony' if @property.outside_space_type == 'balcony'
  spaces << 'Terrace' if @property.outside_space_type == 'terrace'
  spaces
end

def build_parking
  return [] unless @property.parking_type.present?

  parking = []
  parking << 'Allocated Parking' if @property.parking_type == 'allocated'
  parking << 'Garage' if @property.parking_type == 'garage'
  parking << 'Off Street Parking' if @property.parking_type == 'off_street'
  parking
end

These attributes enhance property listings with features tenants frequently search for.

Council Tax Banding

Rightmove requires council tax bands for most properties:

def build_council_tax
  return nil if @property.council_tax_band.blank?
  return nil if @property.council_tax_band == 'TBC'  # Don't send "To Be Confirmed"

  @property.council_tax_band  # 'A' through 'I'
end

This conditional logic ensures only confirmed council tax bands appear on listings, avoiding confusion from placeholder values.

Photo Migration Challenge

Rightmove requires photos to have file extensions in their URLs (.jpg, .png, etc.). Our existing S3 storage used content-addressable filenames without extensions, causing Rightmove to reject photo URLs. We needed to migrate 2,600+ photos whilst maintaining existing functionality.

The solution involved a rake task that:

  1. Iterates through all photos in the database
  2. Detects current file extension from S3 metadata
  3. Copies each photo to a new key with proper extension
  4. Updates database records with new keys
  5. Verifies new URLs are accessible before committing changes
# lib/tasks/migrate_photo_extensions.rake (simplified)
task migrate_photo_extensions: :environment do
  PropertyPhoto.find_each do |photo|
    next if photo.filename.include?('.')  # Already has extension

    # Detect extension from content type
    extension = extension_from_content_type(photo.content_type)
    new_filename = "#{photo.filename}#{extension}"

    # Copy to new key with extension in cloud storage
    copy_with_extension(photo, new_filename)

    # Update database record
    photo.update!(filename: new_filename)
  end
end

This migration ran successfully across all environments, with rollback capability if issues arose.

Direct S3 URLs and Length Limits

Rightmove imposes URL length limits. Initially, we used Rails-generated URLs that included lengthy query parameters for authentication. These exceeded Rightmove's limits for properties with many photos.

The solution: pre-signed direct S3 URLs with longer expiry times:

def generate_photo_url(photo)
  # Generates direct S3 URL with extended expiry
  # Bypasses Rails URL helpers to keep URLs short
  # Includes necessary authentication without lengthy parameters

  photo.blob.service_url(expires_in: 7.days, disposition: 'inline')
end

This reduced average photo URL length by 60%, comfortably fitting within Rightmove's requirements even for properties with dozens of photos.

API Operations

The Rightmove integration supports three primary operations:

Send Property: Creates or updates a property listing on Rightmove with complete data including photos, pricing, and availability status.

Remove Property: Deletes a property from Rightmove when it lets or becomes unavailable, using the property's unique reference for identification.

Get Branch Property List: Retrieves all properties currently advertised under a branch, enabling synchronisation checks and reconciliation.

These operations integrate with the webhook system—property updates trigger webhooks that queue Rightmove sync jobs, keeping portal listings current with minimal latency.

Testing Strategy

Testing external API integrations requires careful mocking to avoid hitting production APIs during tests. We created a comprehensive test helper:

class RightmoveTestHelper
  def self.test_send_property(property_reference)
    # Provides console testing capability
    # Sends to Rightmove staging environment
    # Reports success/failure with detailed error messages
  end

  def self.verify_payload_structure(property)
    # Validates payload structure without making API call
    # Checks all required fields are present
    # Verifies data types match Rightmove expectations
  end
end

This allowed manual verification against Rightmove's staging environment before deploying to production, catching payload structure issues early.

Automated tests mock HTTP responses:

RSpec.describe RightmoveApiService do
  it "sends properties with correct payload structure" do
    # Mocks successful Rightmove response
    # Verifies payload serialisation is correct
  end

  it "handles certificate authentication errors gracefully" do
    # Mocks certificate rejection
    # Confirms error handling notifies appropriately
  end

  it "removes properties when marked as let" do
    # Verifies RemoveProperty API call triggers correctly
  end
end

This test suite provides confidence that the integration handles both happy paths and error scenarios correctly.

What's Next

The Rightmove integration validated our apps architecture—it handled complex authentication, intricate payload requirements, and significant data migration challenges. This success paves the way for additional property portal integrations (Zoopla, OnTheMarket) that follow similar patterns but with different payload structures.

Future enhancements include two-way synchronisation (importing enquiries and viewing requests from Rightmove back into LetAdmin), performance portal analytics (tracking which properties receive most views and enquiries), and automated compliance checking (ensuring all required fields are complete before attempting Rightmove sync).

The integration transforms how agencies manage their Rightmove presence: instead of maintaining property data in two systems with manual synchronisation, they manage everything in LetAdmin with automatic portal updates, reducing administrative burden whilst improving listing accuracy.