Prepare Property Photos for MLS in Seconds, Not Hours

6 min read Image Transformation

The MLS Photo Bottleneck

An agent walks a property and takes 30 photos on their phone. The photos are 4000x3000 pixels, 8MB each. They need to upload them to the MLS by end of day.

The MLS rejects half of them. Too large. Wrong dimensions. File size exceeds the limit. The agent opens each photo in Preview, exports at lower quality, checks the size, adjusts, and re-uploads. For 30 photos, this takes an hour. For an agent listing three properties a week, that’s three hours of image editing instead of selling houses.

Most MLS systems accept JPEG images with a maximum dimension around 2048px and file sizes under 5MB. Some are stricter — 1024px max, 2MB limit. The specifics vary by MLS, but the pattern is the same: phone cameras produce photos that are too big, and the MLS wants them smaller.

The Image Transformation API processes property photos to MLS specifications automatically. One API call per image, no manual resizing, no quality guessing.

The Standard MLS Pipeline

Here’s a pipeline that handles the most common MLS requirements: constrain dimensions, adjust contrast, sharpen after downscaling, and convert to JPEG within a file size limit.

import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });

const { data: { buffer: mlsReadyBase64 } } = await client.transform({
  file: { type: "url", name: "kitchen-01.jpg", url: sourceUrl },
  operations: [
    // Cap dimensions to MLS max — preserves aspect ratio
    { type: "resize", width_in_px: 2048, height_in_px: 1536, fit: "inside" },
    // Normalize contrast — phone photos are often flat
    { type: "auto_contrast" },
    // Sharpen after downscale — resizing softens detail
    { type: "sharpen", sigma: 0.5 },
    // Ensure JPEG output at good quality
    { type: "convert", format: "jpeg", quality: 85 },
    // Hit the MLS file size limit
    { type: "compress_to_size", max_file_size_in_bytes: 5_000_000 },
  ],
});

const mlsReadyImage = Buffer.from(mlsReadyBase64, "base64");

Operations execute sequentially. The 4000x3000 phone photo gets resized to fit within 2048x1536 (becoming 2048x1536 if landscape or 1152x1536 if portrait), contrast gets normalized, sharpening compensates for the downscale, and compress_to_size guarantees the result is under the MLS limit.

Why auto_contrast Matters for Property Photos

Phone cameras, especially in interior shots, produce flat images. The kitchen looks darker than it did in person. The bathroom has a yellow cast from the lighting. The living room windows blow out to white while the rest of the room is murky.

auto_contrast normalizes the histogram — it stretches the tonal range so the darkest pixels become true black and the brightest become true white. This won’t fix fundamentally bad lighting, but it makes a noticeable difference on the typical phone photo where the camera’s auto-exposure played it safe.

For exterior shots that look a bit dull, adding a slight brightness boost helps:

const exteriorOperations = [
  { type: "resize", width_in_px: 2048, height_in_px: 1536, fit: "inside" },
  { type: "auto_contrast" },
  { type: "modulate", brightness: 1.05, saturation: 1.1 },
  { type: "sharpen", sigma: 0.5 },
  { type: "convert", format: "jpeg", quality: 85 },
  { type: "compress_to_size", max_file_size_in_bytes: 5_000_000 },
];

modulate with brightness: 1.05 adds a subtle lift. saturation: 1.1 makes the lawn a bit greener and the sky a bit bluer. Subtle adjustments, but they add up when a buyer is scrolling through listings.

Processing a Full Listing

A typical property listing has 20-30 photos. Processing them individually through a manual tool is the bottleneck. With the API, you send all of them in parallel.

import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });

const MLS_MAX_FILE_SIZE_IN_BYTES = 5_000_000;

const processPropertyPhoto = async (imageUrl: string, fileName: string) => {
  const { data: { buffer } } = await client.transform({
    file: { type: "url", name: fileName, url: imageUrl },
    operations: [
      { type: "resize", width_in_px: 2048, height_in_px: 1536, fit: "inside" },
      { type: "auto_contrast" },
      { type: "sharpen", sigma: 0.5 },
      { type: "convert", format: "jpeg", quality: 85 },
      { type: "compress_to_size", max_file_size_in_bytes: MLS_MAX_FILE_SIZE_IN_BYTES },
    ],
  });

  return Buffer.from(buffer, "base64");
};

const listingPhotos = [
  { url: "https://storage.example.com/listing-42/exterior-front.jpg", name: "exterior-front.jpg" },
  { url: "https://storage.example.com/listing-42/kitchen-01.jpg", name: "kitchen-01.jpg" },
  { url: "https://storage.example.com/listing-42/kitchen-02.jpg", name: "kitchen-02.jpg" },
  { url: "https://storage.example.com/listing-42/living-room-01.jpg", name: "living-room-01.jpg" },
  { url: "https://storage.example.com/listing-42/master-bedroom.jpg", name: "master-bedroom.jpg" },
  { url: "https://storage.example.com/listing-42/bathroom-01.jpg", name: "bathroom-01.jpg" },
  // ... 20+ more
];

const mlsReadyPhotos = await Promise.all(
  listingPhotos.map(({ url, name }) => processPropertyPhoto(url, name))
);

30 photos processed in parallel. Every one comes back within the MLS dimension and file size limits. What used to take an hour now takes seconds.

Adapting to Different MLS Requirements

Not all MLS systems have the same specs. Some are more restrictive than others.

Strict MLS (1024px max, 2MB limit):

const strictMlsOperations = [
  { type: "resize", width_in_px: 1024, height_in_px: 768, fit: "inside" },
  { type: "auto_contrast" },
  { type: "sharpen", sigma: 0.5 },
  { type: "convert", format: "jpeg" },
  { type: "compress_to_size", max_file_size_in_bytes: 2_000_000 },
];

Portal websites (optimized for web, under 500KB):

const portalOperations = [
  { type: "resize", width_in_px: 1200, height_in_px: 900, fit: "inside" },
  { type: "auto_contrast" },
  { type: "sharpen", sigma: 0.5 },
  { type: "convert", format: "webp", quality: 85 },
  { type: "compress_to_size", max_file_size_in_bytes: 500_000 },
];

WebP for portal websites saves bandwidth. Most browsers support it, and the smaller files mean listing pages load faster. Real estate portals with hundreds of listings on a single search results page benefit significantly from smaller image sizes.

Thumbnail Generation

MLS listings and property portals need thumbnails for search results and gallery grids. A center crop might cut off the house. smart_crop uses AI to detect the main subject — the building, the room — and crops around it.

const thumbnailOperations = [
  // AI-powered crop — finds the subject and frames it
  { type: "smart_crop", width_in_px: 400, height_in_px: 300 },
  { type: "sharpen", sigma: 0.5 },
  { type: "convert", format: "jpeg", quality: 80 },
];

For an exterior shot, smart_crop keeps the house in frame. For an interior shot, it keeps the room’s key features visible. This is a meaningful improvement over center-crop, which might give you a thumbnail of just the ceiling and floor.

Handling Common Photo Issues

Property photos from phones come with predictable problems.

Portrait orientation. Agents sometimes hold the phone vertically for a tall room or a staircase. Some MLS systems don’t handle portrait images well — they display them letterboxed or reject them outright. If you need landscape output, use resize with fit: "cover" to crop to landscape dimensions:

const landscapeOperations = [
  { type: "resize", width_in_px: 2048, height_in_px: 1536, fit: "cover" },
  { type: "sharpen", sigma: 0.5 },
  { type: "convert", format: "jpeg", quality: 85 },
];

fit: "cover" fills the target dimensions and trims the excess — a portrait photo becomes landscape by cropping the top and bottom.

Dark interiors. Rooms with small windows or low lighting produce dark, noisy photos. auto_contrast and modulate help with brightness, and denoise can reduce the grain that shows up in low-light phone photos:

const darkInteriorOperations = [
  { type: "auto_contrast" },
  { type: "modulate", brightness: 1.15 },
  { type: "denoise" },
  { type: "resize", width_in_px: 2048, height_in_px: 1536, fit: "inside" },
  { type: "sharpen", sigma: 0.5 },
  { type: "convert", format: "jpeg", quality: 85 },
];

Note: denoise and modulate run before resize here. Denoising works better at full resolution where the noise pattern is clearer. Resizing after denoising also helps — downsampling inherently reduces noise.

Get Started

The Image Transformation API is part of Iteration Layer. Sign up for a free account — no credit card required — and get an API key in the dashboard.

Check the Image Transformation docs for the full list of operations. For property photo workflows, the combination of resize, auto_contrast, sharpen, and compress_to_size handles the vast majority of MLS requirements in a single API call.

Start building in minutes

Free trial included. No credit card required.