How to Build a Zillow Rental Listing Feed: XML Examples for the 2026 Spec
Zillow's rental feed has said version 2.1 since 2014, but its XML changed in 2024 and 2026. Build to the August 2026 guide, with working code.
By Yas Rajah
· 23 min read
Contents
- The six steps, in Zillow’s words
- Which version of Zillow’s feed spec should you build to?
- What’s the smallest listing Zillow accepts?
- A complete listing: pets, parking, fees, amenities and photos
- How do fees work in the 2026 spec?
- Example code: build the feed in Python
- The same feed in Node
- Example input
- Check the file before Zillow does
- Getting your feed live with Zillow
- Mistakes the guide warns about
- Where Reallyo fits
- What we’d do
- FAQ
- Sources
A Zillow rental listing feed is one XML file at a fixed URL that Zillow downloads on a schedule: a <hotPadsItems version="2.1"> root, a <Company> block, and one <listing> element per rental, built to Zillow’s Rental Listing Bulk Feed Guide (last revised 13 August 2026). Each listing needs an ID, a property type, a street address, a contact email and phone, the rent, bedrooms, full baths and a last-updated time. Zillow approves every feed before you build it (“All new feeds must be approved by the Zillow Rentals business team”), and it reads each file as “a complete snapshot of available listings”, so a listing you leave out comes down.
Build to the dated revision, not to the version number. Every Zillow and HotPads rental feed guide we found since November 2014 says version="2.1", yet the XML underneath has changed. The 2026 guides write <listing> and <listingTag> where every earlier one wrote <Listing> and <ListingTag>, and fees grew from a single <deposit> number into named fee fields in May 2024 and a typed <fee> list in June 2026. XML is case-sensitive, so two “version 2.1” feeds can be different files.
We collected six revisions of the guide, from 24 November 2014 (an Internet Archive copy of HotPads’ own file server) to 13 August 2026, and compared them element by element. Every example below follows the August 2026 guide. We ran the Python and Node generators, confirmed they produce identical XML, and checked the output against every field the guide marks required.
The six steps, in Zillow’s words
| Step | What you do | What Zillow’s guide says |
|---|---|---|
| 1. Get approval | Ask Zillow’s Rentals team before you start | ”All new feeds must be approved by the Zillow Rentals business team.” |
| 2. Write the XML | One <listing> per unit, with the 2026 element names | ”Version must be 2.1.” |
| 3. Host the file | A static URL over HTTPS or SFTP, UTF-8 | ”The feed file must have a static, unchanging URL/path.” |
| 4. Test | Zillow sets your feed up in its test environment | ”Once your feed is built, email rentalfeeds@zillow.com to request testing setup.” |
| 5. Go live | Hand over the production URL and hold a go-live meeting | ”Confirm that you have provided Zillow with the production file URL, not the test endpoint.” |
| 6. Read the reports | The Feed Run Report and the listing status API | ”The feed run report shows feed status (success or failure) and per-listing validity.” |
Checked 6 November 2026 against the guide at Zillow’s link. Zillow’s own guide also says who shouldn’t build one: “For owners or property managers with a small number of properties, consider creating listings directly via Zillow Rental Manager … or using a third-party syndication platform, rather than building and maintaining your own feed.” Our Zillow Feed Connect guide covers that choice.
Should you build your own Zillow feed?
4 questions, 20 seconds. Find the way to get your rentals onto Zillow that fits your team.
Question 1 of 4
How many rentals do you list at a time?
Question 2 of 4
Who would build and look after a feed?
Question 3 of 4
Which sites do you need your rentals on?
Question 4 of 4
Where do your listings live today?
Your answer
Build your own feed to the August 2026 guide
You have the volume, a developer and the data in your own system, which is who Zillow's feed program is for. Ask Zillow to approve the feed first: its guide says "All new feeds must be approved by the Zillow Rentals business team." Then start from the smallest listing in this guide, get it through Zillow's test system, and add pets, parking and fees one block at a time, reading the Feed Run Report after each.
How Zillow rental syndication worksYour answer
Send listings to one API instead of one feed per site
A developer and many sites point to an API rather than a Zillow-only feed. Reallyo's Listings API takes one listing per request from your software, and Reallyo sends your rentals to Zillow's network and other rental sites and keeps them in sync. It works on any Reallyo account, and there's an MCP server for AI assistants too.
See the Listings APIYour answer
Use a syndication platform, not your own feed
Zillow's own guide points small portfolios to "a third-party syndication platform, rather than building and maintaining your own feed." Reallyo is one: enter a rental once or import a spreadsheet, and it goes to Zillow's network and other rental sites and stays in sync. It's free for one active listing; paid plans run from $39 to $89 a month for 10 to 100.
See rental listing syndicationYour answer
Post in Zillow Rental Manager
With a few units and only Zillow's sites to reach, a feed is more than you need. Zillow's guide suggests "creating listings directly via Zillow Rental Manager" for owners and managers with a small number of properties. It's Zillow's own tool, so there's nothing to build or host.
How to list a rental on ZillowGet our next Zillow feed research
We'll email you when Zillow revises its feed guide or we publish new research on rental listing feeds.
No spam, unsubscribe anytime.
Which version of Zillow’s feed spec should you build to?
The 13 August 2026 revision of the Rental Listing Bulk Feed Guide. It’s the one Zillow links to today, and it’s the first to ship with a downloadable test file of valid, published listings. Every revision we found keeps the same root, <hotPadsItems version="2.1">, so the version number won’t tell you which one a feed follows. The cover date will.
| Revision | Title on the cover | What changed |
|---|---|---|
| 24 Nov 2014 | Rental Listings Feed Guide (HotPads) | Earliest we found. <Listing> and <ListingTag>, pets as tags such as CATS_ALLOWED, one <deposit> number |
| 11 Sep 2015 | Rental Listings Feed Guide (HotPads) | Same elements |
| 3 Dec 2018 | Rental Listings Feed Guide (HotPads) | Same model in a 50-page guide. HotPads’ download link still served this exact file in August 2025 |
| 22 May 2024 | Zillow Rentals Listing Feed Guide | A <pets> block replaces the pet tags. Fees by name: application and administrative fee tags, parking and pet fees, <costAndFeeDescription> |
| 30 Jun 2026 | Rental Listing Feed Guide (bulk) | Lowercase <listing>, <listingTag>, <listingPhoto>. Fees rebuilt as one <fee> list, “aligned with MITS 5.0”. New garageAttached parking type |
| 13 Aug 2026 | Rental Listing Feed Guide (bulk) | The June changes, plus a “Downloadable Test File with multiple examples of valid and published listings” |
Two things the guide doesn’t settle. It doesn’t say whether Zillow still reads the capitalised names of the older revisions, so a new feed should use the 2026 names. And it keeps older fee fields working: “Legacy tags (e.g., APPLICATION_FEE, DEPOSIT listingTag values) continue to function but the <fees> section is the preferred and more capable method.”
Zillow takes two other formats, with limits. It “also accepts MITS”, the multifamily industry standard, at versions up to 5.0. It no longer takes its own older broker format for rentals: “ZIF (legacy) is no longer accepted for rental listings.”
What’s the smallest listing Zillow accepts?
These are the elements the August 2026 guide marks required for a single unit (a condo, house or townhouse). Everything else, including photos and the description, is optional in its table.
| Element | Where | Format |
|---|---|---|
version="2.1" | <hotPadsItems> | ”Version must be 2.1.” |
<city>, <state> | <Company> | Two-letter state code |
id, type, companyId, propertyType | attributes of <listing> | type: RENTAL, CORPORATE (furnished), SUBLET or VACATION (“not published”). propertyType: CONDO, HOUSE or TOWNHOUSE |
<street>, <city>, <state>, <zip> | <listing> | hide="true" on the street “suppress[es] display and offset[s] map pin” |
<unit> | <listing> | ”Required when applicable. Send verbatim, no prefix/suffix.” |
<lastUpdated> | <listing> | ISO 8601 UTC, e.g. 2024-08-13T00:36:00.000Z |
<contactEmail>, <contactPhone> | <listing> | |
<price>, <numBedrooms>, <numFullBaths> | <listing> | Numbers |
Put together, that’s this file. It’s a complete, valid feed with one listing:
<?xml version="1.0" encoding="UTF-8"?>
<hotPadsItems version="2.1">
<Company id="acme01">
<name>Acme Property Management</name>
<city>Austin</city>
<state>TX</state>
</Company>
<listing id="unit2417" type="RENTAL" companyId="acme01" propertyType="CONDO">
<unit>4B</unit>
<street hide="false">2417 Example Avenue</street>
<city>Austin</city>
<state>TX</state>
<zip>78701</zip>
<lastUpdated>2026-11-06T09:00:00.000Z</lastUpdated>
<contactEmail>leasing@yoursite.com</contactEmail>
<contactPhone>5125550100</contactPhone>
<price>2100</price>
<numBedrooms>2</numBedrooms>
<numFullBaths>1</numFullBaths>
</listing>
</hotPadsItems>
<Company> keeps its capital C in the 2026 guide; <listing> doesn’t. The listing’s companyId must match a <Company id>, and IDs should stay the same forever: the guide asks for IDs that are “Unique, static over time”.
A complete listing: pets, parking, fees, amenities and photos
Here’s the same unit with the optional parts renters filter on. The order follows the guide’s own structure, and this is the exact output of the code further down.
<?xml version="1.0" encoding="UTF-8"?>
<hotPadsItems version="2.1">
<Company id="acme01">
<name>Acme Property Management</name>
<city>Austin</city>
<state>TX</state>
</Company>
<listing id="unit2417" type="RENTAL" companyId="acme01" propertyType="CONDO">
<name>Two-bedroom condo near Lady Bird Lake</name>
<unit>4B</unit>
<street hide="false">2417 Example Avenue</street>
<city>Austin</city>
<state>TX</state>
<zip>78701</zip>
<lastUpdated>2026-11-06T09:00:00.000Z</lastUpdated>
<contactName>Leasing Office</contactName>
<contactEmail>leasing@yoursite.com</contactEmail>
<contactPhone>5125550100</contactPhone>
<description>Corner unit with a balcony, two blocks from the lake & the trail.</description>
<leaseTerm>12 Months</leaseTerm>
<isFurnished>false</isFurnished>
<smokingAllowed>false</smokingAllowed>
<parking>
<parkingType>garageAttached</parkingType>
</parking>
<pets>
<pet>
<petType>dogs</petType>
<petSize>small</petSize>
<allowed>true</allowed>
</pet>
<pet>
<petType>dogs</petType>
<petSize>large</petSize>
<allowed>false</allowed>
</pet>
<pet>
<petType>cats</petType>
<allowed>true</allowed>
</pet>
</pets>
<fees>
<fee>
<feeCalculationType value="50" valueType="flatFee" />
<feeType>applicationFee</feeType>
<feeTimingType>atApplication</feeTimingType>
<feeRequirementType>mandatory</feeRequirementType>
<feeRefundableType>nonRefundable</feeRefundableType>
</fee>
<fee>
<feeCalculationType value="2100" valueType="flatFee">
<detailedCalculation>One month's rent</detailedCalculation>
</feeCalculationType>
<feeType>securityDeposit</feeType>
<feeTimingType>moveIn</feeTimingType>
<feeRequirementType>mandatory</feeRequirementType>
<feeRefundableType>refundable</feeRefundableType>
</fee>
<fee>
<feeCalculationType value="35" valueType="flatFee" perType="item" />
<feeType>petRent</feeType>
<feeTimingType>monthly</feeTimingType>
<feeRequirementType>situational</feeRequirementType>
<feeRequirementDescription>Charged for each pet.</feeRequirementDescription>
<feeItemMaxOccurrences>2</feeItemMaxOccurrences>
</fee>
<fee>
<feeCalculationType value="0" valueType="flatFee" />
<feeType>waterFee</feeType>
<feeTimingType>monthly</feeTimingType>
<feeRequirementType>included</feeRequirementType>
</fee>
</fees>
<listingTag type="PROPERTY_AMENITY">
<tag>FitnessCenter</tag>
</listingTag>
<listingTag type="MODEL_AMENITY">
<tag>Dishwasher</tag>
</listingTag>
<listingTag type="LAUNDRY">
<tag>IN_UNIT</tag>
</listingTag>
<listingPhoto source="https://yoursite.com/photos/unit2417-1.jpg">
<caption>Living room</caption>
</listingPhoto>
<listingPhoto source="https://yoursite.com/photos/unit2417-2.jpg">
<caption>Kitchen</caption>
</listingPhoto>
<price>2100</price>
<numBedrooms>2</numBedrooms>
<numFullBaths>1</numFullBaths>
<numHalfBaths>1</numHalfBaths>
<squareFeet>850</squareFeet>
<dateAvailable>2026-12-01</dateAvailable>
</listing>
</hotPadsItems>
What each part takes, from the guide’s reference tables:
<leaseTerm>: “contactForDetails | monthly | n Months”, for example12 Months. Other lengths go in the free-text<terms>.<parkingType>: garageAttached, garageLot, coveredLot, street, surfaceLot, other or none.<pet>:petTypeis dogs, cats, birds, reptiles, fish or other;petSizeis small or large, and is only used for dogs.<listingTag>: a type plus a value.PROPERTY_AMENITYis building-wide (Pool, FitnessCenter, GatedEntry …),MODEL_AMENITYis inside the unit (Dishwasher, WasherDryer, Fireplace …), andLAUNDRYtakes IN_UNIT, SHARED, NONE, HOOKUPS, OTHER or WASHER_ONLY. Send only the values the guide names: its amenity lists end in “etc.”, but a value it doesn’t name isn’t certain to be read.<listingPhoto>: the URL insource, an optional<caption>(“Max 60 characters”) and<label>(“Max 30 characters”).<description>: escape&,<,>,"and', or wrap the text in CDATA, as the guide shows.
How do fees work in the 2026 spec?
Each fee is one <fee> block inside <fees>: what it is (feeType), when it’s charged (feeTimingType), whether it’s required (feeRequirementType) and how it’s calculated (feeCalculationType). That last element takes a valueType of flatFee, range, rangePerItem, multiplier, percentage, stepped or variableUsageBased, plus a perType of item, leaseholder, person or period for per-pet or per-applicant fees.
The four requirement types are the part renters see most clearly. In the guide’s words: mandatory (“The renter must pay this fee”), optional (“The renter chooses whether to incur this fee”), included (“Service is provided at no additional cost”, which is how you say water is included) and situational (“The fee applies only under specific conditions”, explained in <feeRequirementDescription>). For deposits, add <feeRefundableType>: the guide marks it “Required for deposit-type fees”.
Here’s how the same two charges looked in May 2024 and look now, for a single unit with a $50 application fee and a $2,100 deposit:
<!-- May 2024 guide: fees as separate fields -->
<ListingTag type="BUILDING_APPLICATION_FEE">
<tag>50</tag>
</ListingTag>
<price>2100</price>
<deposit>2100</deposit>
<!-- August 2026 guide: one <fee> block per fee -->
<fees>
<fee>
<feeCalculationType value="50" valueType="flatFee" />
<feeType>applicationFee</feeType>
<feeTimingType>atApplication</feeTimingType>
<feeRequirementType>mandatory</feeRequirementType>
<feeRefundableType>nonRefundable</feeRefundableType>
</fee>
<fee>
<feeCalculationType value="2100" valueType="flatFee" />
<feeType>securityDeposit</feeType>
<feeTimingType>moveIn</feeTimingType>
<feeRequirementType>mandatory</feeRequirementType>
<feeRefundableType>refundable</feeRefundableType>
</fee>
</fees>
Send deposits as dollar amounts, not as a multiple of rent. The guide allows a deposit of “1.5x one month’s rent” (valueType="multiplier" operandType="rent"), but it also warns: “For a multiplier or percentage fee to resolve correctly, the fee it references (e.g., rent, securityDeposit) must be defined earlier in the same feed file.” Its troubleshooting page lists “Calculated Fees Resolving to Zero” as a common issue. Yet in its own listing structure, <price> comes after <fees>. Until Zillow clears that up, a flat amount can’t come out as $0. Our example keeps the wording in <detailedCalculation>, which the guide describes as “Displayed to renters”.
Example code: build the feed in Python
This reads a JSON file of listings and writes the feed. It uses only Python’s standard library, and ElementTree handles the escaping. The full listing above is its output for this input.
"""Build a Zillow rental listing feed (hotPadsItems 2.1, the 13 Aug 2026 guide) from listing.json.
Standard library only. Usage: python3 build_feed.py listing.json feed.xml
"""
import json
import sys
import xml.etree.ElementTree as ET
def text(parent, tag, value, **attrs):
"""Add <tag>value</tag>. ElementTree escapes &, < and > for you."""
el = ET.SubElement(parent, tag, attrs)
el.text = str(value)
return el
def boolean(value):
return "true" if value else "false"
def add_fee(fees, fee):
el = ET.SubElement(fees, "fee")
calc = ET.SubElement(el, "feeCalculationType", value=str(fee["amount"]), valueType="flatFee")
if "per" in fee:
calc.set("perType", fee["per"])
if "note" in fee:
text(calc, "detailedCalculation", fee["note"])
text(el, "feeType", fee["type"])
text(el, "feeTimingType", fee["when"])
text(el, "feeRequirementType", fee["requirement"])
if "condition" in fee:
text(el, "feeRequirementDescription", fee["condition"])
if "refundable" in fee:
text(el, "feeRefundableType", "refundable" if fee["refundable"] else "nonRefundable")
if "maxItems" in fee:
text(el, "feeItemMaxOccurrences", fee["maxItems"])
def add_listing(root, company_id, l):
el = ET.SubElement(root, "listing", id=l["id"], type="RENTAL",
companyId=company_id, propertyType=l["propertyType"])
text(el, "name", l["name"])
text(el, "unit", l["unit"])
text(el, "street", l["street"], hide="false")
text(el, "city", l["city"])
text(el, "state", l["state"])
text(el, "zip", l["zip"])
text(el, "lastUpdated", l["updatedAt"])
text(el, "contactName", l["contactName"])
text(el, "contactEmail", l["contactEmail"])
text(el, "contactPhone", l["contactPhone"])
text(el, "description", l["description"])
text(el, "leaseTerm", l["leaseTerm"])
text(el, "isFurnished", boolean(l["furnished"]))
text(el, "smokingAllowed", boolean(l["smokingAllowed"]))
parking = ET.SubElement(el, "parking")
text(parking, "parkingType", l["parkingType"])
pets = ET.SubElement(el, "pets")
for pet_type, size, allowed in (("dogs", "small", l["pets"]["smallDogs"]),
("dogs", "large", l["pets"]["largeDogs"]),
("cats", None, l["pets"]["cats"])):
pet = ET.SubElement(pets, "pet")
text(pet, "petType", pet_type)
if size:
text(pet, "petSize", size)
text(pet, "allowed", boolean(allowed))
fees = ET.SubElement(el, "fees")
for fee in l["fees"]:
add_fee(fees, fee)
for tag_type, value in l["tags"]:
tag = ET.SubElement(el, "listingTag", type=tag_type)
text(tag, "tag", value)
for photo in l["photos"]:
p = ET.SubElement(el, "listingPhoto", source=photo["url"])
text(p, "caption", photo["caption"][:60]) # the guide's limit: 60 characters
text(el, "price", l["rent"])
text(el, "numBedrooms", l["bedrooms"])
text(el, "numFullBaths", l["fullBaths"])
text(el, "numHalfBaths", l["halfBaths"])
text(el, "squareFeet", l["squareFeet"])
text(el, "dateAvailable", l["availableFrom"])
def build(data):
root = ET.Element("hotPadsItems", version="2.1")
c = data["company"]
company = ET.SubElement(root, "Company", id=c["id"]) # capital C, as the guide writes it
text(company, "name", c["name"])
text(company, "city", c["city"])
text(company, "state", c["state"])
for listing in data["listings"]:
add_listing(root, c["id"], listing)
ET.indent(root, space=" ")
return ET.ElementTree(root)
if __name__ == "__main__":
src, out = sys.argv[1], sys.argv[2]
with open(src, encoding="utf-8") as f:
tree = build(json.load(f))
tree.write(out, encoding="UTF-8", xml_declaration=True) # the guide requires UTF-8
Run it with python3 build_feed.py listing.json feed.xml (Python 3.9 or later, for ET.indent).
The same feed in Node
No dependencies. It escapes the five characters the guide lists and writes the same XML as the Python version: we compared the two outputs in canonical form and they match.
// Build a Zillow rental listing feed (hotPadsItems 2.1, the 13 Aug 2026 guide) from listing.json.
// No dependencies. Usage: node build_feed.mjs listing.json feed.xml
import { readFileSync, writeFileSync } from 'node:fs';
// The five entity references the guide lists: & < > " '
const esc = (v) =>
String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
const attrs = (a) => Object.entries(a).map(([k, v]) => ` ${k}="${esc(v)}"`).join('');
const bool = (v) => (v ? 'true' : 'false');
function writer() {
const lines = [];
let depth = 0;
const pad = () => ' '.repeat(depth);
return {
el: (tag, value, a = {}) => lines.push(`${pad()}<${tag}${attrs(a)}>${esc(value)}</${tag}>`),
empty: (tag, a = {}) => lines.push(`${pad()}<${tag}${attrs(a)} />`),
open: (tag, a = {}) => { lines.push(`${pad()}<${tag}${attrs(a)}>`); depth++; },
close: (tag) => { depth--; lines.push(`${pad()}</${tag}>`); },
toString: () => '<?xml version="1.0" encoding="UTF-8"?>\n' + lines.join('\n') + '\n',
};
}
function fee(x, f) {
x.open('fee');
const calc = { value: f.amount, valueType: 'flatFee', ...(f.per ? { perType: f.per } : {}) };
if (f.note) {
x.open('feeCalculationType', calc);
x.el('detailedCalculation', f.note);
x.close('feeCalculationType');
} else x.empty('feeCalculationType', calc);
x.el('feeType', f.type);
x.el('feeTimingType', f.when);
x.el('feeRequirementType', f.requirement);
if (f.condition) x.el('feeRequirementDescription', f.condition);
if ('refundable' in f) x.el('feeRefundableType', f.refundable ? 'refundable' : 'nonRefundable');
if (f.maxItems) x.el('feeItemMaxOccurrences', f.maxItems);
x.close('fee');
}
function listing(x, companyId, l) {
x.open('listing', { id: l.id, type: 'RENTAL', companyId, propertyType: l.propertyType });
x.el('name', l.name);
x.el('unit', l.unit);
x.el('street', l.street, { hide: 'false' });
x.el('city', l.city);
x.el('state', l.state);
x.el('zip', l.zip);
x.el('lastUpdated', l.updatedAt);
x.el('contactName', l.contactName);
x.el('contactEmail', l.contactEmail);
x.el('contactPhone', l.contactPhone);
x.el('description', l.description);
x.el('leaseTerm', l.leaseTerm);
x.el('isFurnished', bool(l.furnished));
x.el('smokingAllowed', bool(l.smokingAllowed));
x.open('parking');
x.el('parkingType', l.parkingType);
x.close('parking');
x.open('pets');
for (const [type, size, allowed] of [['dogs', 'small', l.pets.smallDogs], ['dogs', 'large', l.pets.largeDogs], ['cats', null, l.pets.cats]]) {
x.open('pet');
x.el('petType', type);
if (size) x.el('petSize', size);
x.el('allowed', bool(allowed));
x.close('pet');
}
x.close('pets');
x.open('fees');
for (const f of l.fees) fee(x, f);
x.close('fees');
for (const [type, value] of l.tags) {
x.open('listingTag', { type });
x.el('tag', value);
x.close('listingTag');
}
for (const p of l.photos) {
x.open('listingPhoto', { source: p.url });
x.el('caption', p.caption.slice(0, 60)); // the guide's limit: 60 characters
x.close('listingPhoto');
}
x.el('price', l.rent);
x.el('numBedrooms', l.bedrooms);
x.el('numFullBaths', l.fullBaths);
x.el('numHalfBaths', l.halfBaths);
x.el('squareFeet', l.squareFeet);
x.el('dateAvailable', l.availableFrom);
x.close('listing');
}
const [src, out] = process.argv.slice(2);
const data = JSON.parse(readFileSync(src, 'utf8'));
const x = writer();
x.open('hotPadsItems', { version: '2.1' });
x.open('Company', { id: data.company.id }); // capital C, as the guide writes it
x.el('name', data.company.name);
x.el('city', data.company.city);
x.el('state', data.company.state);
x.close('Company');
for (const l of data.listings) listing(x, data.company.id, l);
x.close('hotPadsItems');
writeFileSync(out, x.toString(), 'utf8');
Example input
Both programs read this. In a real feed the listings come from your database, and updatedAt is the time the listing last changed, not the time the file was built.
{
"company": { "id": "acme01", "name": "Acme Property Management", "city": "Austin", "state": "TX" },
"listings": [
{
"id": "unit2417",
"propertyType": "CONDO",
"name": "Two-bedroom condo near Lady Bird Lake",
"unit": "4B",
"street": "2417 Example Avenue",
"city": "Austin",
"state": "TX",
"zip": "78701",
"updatedAt": "2026-11-06T09:00:00.000Z",
"contactName": "Leasing Office",
"contactEmail": "leasing@yoursite.com",
"contactPhone": "5125550100",
"description": "Corner unit with a balcony, two blocks from the lake & the trail.",
"leaseTerm": "12 Months",
"furnished": false,
"smokingAllowed": false,
"parkingType": "garageAttached",
"pets": { "smallDogs": true, "largeDogs": false, "cats": true },
"fees": [
{ "type": "applicationFee", "amount": 50, "when": "atApplication", "requirement": "mandatory", "refundable": false },
{ "type": "securityDeposit", "amount": 2100, "when": "moveIn", "requirement": "mandatory", "refundable": true,
"note": "One month's rent" },
{ "type": "petRent", "amount": 35, "per": "item", "when": "monthly", "requirement": "situational",
"condition": "Charged for each pet.", "maxItems": 2 },
{ "type": "waterFee", "amount": 0, "when": "monthly", "requirement": "included" }
],
"tags": [
["PROPERTY_AMENITY", "FitnessCenter"],
["MODEL_AMENITY", "Dishwasher"],
["LAUNDRY", "IN_UNIT"]
],
"photos": [
{ "url": "https://yoursite.com/photos/unit2417-1.jpg", "caption": "Living room" },
{ "url": "https://yoursite.com/photos/unit2417-2.jpg", "caption": "Kitchen" }
],
"rent": 2100,
"bedrooms": 2,
"fullBaths": 1,
"halfBaths": 1,
"squareFeet": 850,
"availableFrom": "2026-12-01"
}
]
}
Check the file before Zillow does
First make sure it’s well-formed XML. On a Mac or Linux machine, xmllint is usually already installed:
xmllint --noout feed.xml && echo "well-formed"
Well-formed isn’t the same as accepted, so we also wrote a short checker for the guide’s required fields and a few of its rules: IDs without dashes or underscores, an ISO 8601 UTC lastUpdated, values from the guide’s lists, a name on every custom fee, refundability on deposits, and captions within 60 characters.
"""Check a Zillow rental feed against the rules in the 13 Aug 2026 guide. Standard library only.
Usage: python3 check_feed.py feed.xml (exit code 1 if anything fails)
Not a substitute for Zillow's Feed Run Report: it checks the guide's required fields and a few of its rules.
"""
import re
import sys
import xml.etree.ElementTree as ET
REQUIRED = ["street", "city", "state", "zip", "lastUpdated", "contactEmail", "contactPhone",
"price", "numBedrooms", "numFullBaths"]
ENUMS = {
"type": {"RENTAL", "CORPORATE", "SUBLET", "VACATION"},
"propertyType": {"CONDO", "HOUSE", "TOWNHOUSE"}, # single-unit listings
"parkingType": {"garageAttached", "garageLot", "coveredLot", "street", "surfaceLot", "other", "none"},
"petType": {"dogs", "cats", "birds", "reptiles", "fish", "other"},
"feeRequirementType": {"mandatory", "optional", "included", "situational"},
}
ISO_UTC = re.compile(r"^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d{3})?Z$")
def check(path):
problems = []
root = ET.parse(path).getroot()
if root.tag != "hotPadsItems" or root.get("version") != "2.1":
problems.append('root must be <hotPadsItems version="2.1">')
companies = {c.get("id") for c in root.findall("Company")}
for c in root.findall("Company"):
for tag in ("city", "state"):
if not (c.findtext(tag) or "").strip():
problems.append(f"Company {c.get('id')}: missing <{tag}>")
for l in root.findall("listing"):
lid = l.get("id") or "?"
for attr in ("id", "type", "companyId", "propertyType"):
if not l.get(attr):
problems.append(f"listing {lid}: missing {attr}")
if re.search(r"[-_]", lid):
problems.append(f"listing {lid}: avoid dashes and underscores in ids")
if l.get("companyId") not in companies:
problems.append(f"listing {lid}: companyId has no <Company>")
for tag in REQUIRED:
if not (l.findtext(tag) or "").strip():
problems.append(f"listing {lid}: missing <{tag}>")
if not ISO_UTC.match(l.findtext("lastUpdated") or ""):
problems.append(f"listing {lid}: lastUpdated must be ISO 8601 UTC, e.g. 2026-11-06T09:00:00.000Z")
for attr in ("type", "propertyType"):
if l.get(attr) and l.get(attr) not in ENUMS[attr]:
problems.append(f"listing {lid}: {attr}={l.get(attr)} is not in the guide's list")
for tag in ("parkingType", "petType", "feeRequirementType"):
for el in l.iter(tag):
if el.text not in ENUMS[tag]:
problems.append(f"listing {lid}: <{tag}>{el.text}</{tag}> is not in the guide's list")
for fee in l.iter("fee"):
if fee.findtext("feeType") == "customFee" and not fee.findtext("feeName"):
problems.append(f"listing {lid}: customFee needs <feeName>")
if "Deposit" in (fee.findtext("feeType") or "") and not fee.findtext("feeRefundableType"):
problems.append(f"listing {lid}: deposit fees need <feeRefundableType>")
for photo in l.findall("listingPhoto"):
if len(photo.findtext("caption") or "") > 60:
problems.append(f"listing {lid}: photo caption over 60 characters")
return problems
if __name__ == "__main__":
found = check(sys.argv[1])
print("\n".join(found) if found else "OK: no problems found")
sys.exit(1 if found else 0)
On the example feed it prints OK: no problems found. We also broke a copy on purpose (an ID of unit_24-17, no <numFullBaths>, no refundability on the deposit) and it reported all three. It checks single-unit listings only; apartment communities use the guide’s multifamily structure, with <model> elements inside one <listing>.
Getting your feed live with Zillow
Once the file passes your own checks, the rest runs through Zillow. From the August 2026 guide:
- Approval and consent. Besides Zillow’s approval, your product must do one of three things: offer “A flag in the user interface that allows syndication to Zillow to be triggered”, show “a clear notification” that listings publish on Zillow, or say so in your Terms and Conditions.
- Real data only. “Placeholder or lorem ipsum content is not accepted for development, testing, or production publication.” Our example is for reading, not for Zillow’s test system.
- Hosting. HTTP, HTTPS, FTPS or SFTP (“FTP (unencrypted) is no longer accepted”), at a fixed URL with no date in the name. “Compressed formats (.zip or .gz) are accepted and recommended for large feeds.”
- Schedule. Zillow downloads the file “on a per-feed cron schedule that you configure with the Rental Integrations team”, and recommends regenerating it “20–30 minutes before the cron job runs”.
- Testing. Email rentalfeeds@zillow.com with the URL, not the file. “The test platform resets every Monday morning”, which clears listing data but keeps your configuration.
- Removing a listing. Leave it out: “It will be removed when the next feed file is processed.”
- The 20% guard. “any single feed update that increases or drops listing count by more than 20% is flagged and held until confirmed as intentional.” Tell Zillow first if you expect a big change.
- Reports. The Feed Run Report shows whether each run succeeded, with lines such as “Rejected listing id=3846, Missing required address information.” A per-listing status API and a feed-wide CSV show where each listing is live.
Mistakes the guide warns about
- Underscores in text. “Underscores in text content are stripped during parsing. Avoid them in IDs, names, and descriptions.”
- Dashes in IDs. “Dashes (”-”) cause issues and should be avoided whenever possible.”
- The wrong encoding. “XML must be UTF-8 encoded. Windows systems default to UTF-16, which is not valid for this feed.”
- Replacing a photo at the same URL. “If you replace a photo, change its URL — Zillow caches the URL hash and will not automatically re-download an image unless the URL is also updated.” Photos must also be at least 415 by 330 pixels, in .jpg, .png or .webp.
- New addresses. For new construction, “new addresses will not map correctly unless pre-registered with the Feed Operations team”, and each lot needs its exact latitude and longitude.
- Unit numbers with extras. “Send unit numbers verbatim, without any prefix, suffix, or additional address information inside the unit tag.”
- A stale
lastUpdated. If you also use Zillow’s listing API, “stale timestamps can cause older data to override newer updates.”
Where Reallyo fits
Reallyo (our product) is the kind of third-party syndication platform Zillow’s guide mentions. You enter a rental once, or import it from a spreadsheet, an XML feed, an MLS (with the MLS’s approval) or a property management system export, and Reallyo sends it to Zillow’s network and other rental sites and keeps it in sync. Software can send listings to Reallyo through its Listings API, one listing per request, or its MCP server for AI assistants, on any account. Our guide to whether Zillow has an API for posting rentals compares that route with building your own feed.
It sends long-term rentals in the US only, and it isn’t a Zillow Feed Connect advertising contract. Reallyo is free for one active listing, and paid plans run from $39 to $89 a month for 10 to 100 active listings. See rental listing syndication or post a listing free.
US listings only. Acceptance depends on partner rules, listing quality, location, account status and platform policies outside our control; some partners may require identity verification or additional fees.
What we’d do
- Under a handful of units, don’t build a feed. Zillow’s own guide points small portfolios to Rental Manager or a syndication platform.
- Build to the 13 August 2026 guide, and write that date in your code. The version number won’t tell the next developer which revision you followed.
- Ship the smallest listing first. Get the required fields through Zillow’s test system, then add pets, parking, fees and amenities one block at a time, reading the Feed Run Report after each.
- Send deposits as dollar amounts until Zillow clears up the rent-multiplier order, and keep the wording in
<detailedCalculation>. - Use the guide’s values only. Where its lists end in “etc.”, send the values it names and leave the rest in your description.
FAQ
What format does Zillow use for rental listing feeds?
XML in Zillow’s own rental format, which starts with <hotPadsItems version="2.1"> and is defined in the Rental Listing Bulk Feed Guide (last revised 13 August 2026). Zillow also accepts MITS. Its older Zillow Interchange Format is “no longer accepted for rental listings”.
What is hotPadsItems version 2.1? It’s the root element of Zillow’s rental feed, named after HotPads, which wrote the format. Every revision of the guide we found, from 24 November 2014 to 13 August 2026, uses version 2.1, even though the element names and the fee model changed in 2024 and 2026. Build to the guide’s revision date.
What fields are required in a Zillow rental feed?
For a single unit: a <Company> with city and state; a <listing> with id, type, companyId and propertyType; the street, city, state and zip; lastUpdated; a contact email and phone; and the price, bedrooms and full baths. The unit number is required when there is one.
How do I remove a listing from a Zillow feed? Leave it out of the next file. Zillow’s guide says the feed “is a complete snapshot of available listings. To remove a listing, simply omit it from the feed.” A change of more than 20% in listing count is held until Zillow confirms it.
Can I include fees and deposits in a Zillow feed?
Yes. The 2026 guide has a <fees> section with one <fee> block per charge: its type, timing, whether it’s required, and the amount. Deposits need a refundability value. Fees can sit on the listing or, for apartment communities, on each floorplan or unit.
How often does Zillow download a rental feed? On a schedule you set with Zillow’s Rental Integrations team. The guide recommends regenerating your file 20 to 30 minutes before each scheduled download, so Zillow always gets current data.
How do I know whether Zillow accepted my listings? Read the Feed Run Report at feeds.hotpads.com, which shows whether the run succeeded and lists rejected listings with the reason. Zillow’s listing status API and its feed-wide CSV show where each listing is live on Zillow, Trulia and HotPads.
Does Zillow still accept the Zillow Interchange Format for rentals? No. The August 2026 guide says “ZIF (legacy) is no longer accepted for rental listings” and asks existing ZIF feeds to move to the rental format. Other rental sites still take ZIF.
Free syndication & listing marketing
List Once. Syndicate and Market your listings. Everywhere.
Reallyo sends your rental to Zillow, Apartments.com and the rest of its partner rental network, then turns it into a listing page, a video, social posts and flyers. Free for one active listing, no card.
Sources
Checked 6 November 2026. Quotes are verbatim from Zillow’s and HotPads’ own documents.
- The current guide. Zillow Rentals, Rental Listing Bulk Feed Guide, “Last Revised: Aug 13, 2026”: the required fields, element formats, fee model and examples, transport, scheduling, testing, thresholds, reports, “ZIF (legacy) is no longer accepted for rental listings” and “Zillow also accepts MITS”.
- The June 2026 revision. Zillow Rentals, Rental Listing Feed Guide, “Last Revised: Jun 30, 2026” (our copy): the What’s New list on fees “aligned with MITS 5.0” and
garageAttached. - The May 2024 revision. Zillow Rentals, Listing Feed Guide, “Last Revised May 22, 2024” (our copy): fee tags, the parking and pet sections,
<costAndFeeDescription>,BUILDING_APPLICATION_FEEand<deposit>. - The 2018 revision. HotPads, Rental Listings Feed Guide, December 3, 2018. The Internet Archive’s captures of 18 September 2020 and 10 August 2025 are the same file.
- The 2015 revision. HotPads, Rental Listings Feed Guide, 09/11/2015 (our copy).
- The 2014 revision, the earliest we found. HotPads, Rental Listings Feed Guide, 11/24/2014, captured by the Internet Archive on 14 March 2015.
- The older broker format. Zillow, Zillow Listings Feed Guide V1.0.20 (12 January 2017), the Zillow Interchange Format the current rental guide marks deprecated.
- MITS. RETTC, MITS data models, which the guide cites for MITS 5.0.
- Reallyo. Rental listing syndication, Listings API and pricing.