A house for sale, an apartment to rent, a car. One document per listing, on the seller's own origin, so a directory reads the seller's file instead of licensing somebody else's database or scraping a marketplace that forbids it. **The name `OpenRental` was the obvious one and it is taken.** It already means renting OpenAgent and OpenSwarm members through CoinPay, with a published schema, SDK, validators and fixtures. Its own document records surviving one collision already: it was OpenFleet until 2026-09-13. Reusing the name for housing would have broken a shipped 0.1 spec, so the family is OpenListing instead. Two axes rather than a spec per combination. A house for sale and a house to rent are the same house; only the offer differs. A house for sale and a car for sale are the same offer; only the subject differs. Writing OpenHouseForSale, OpenHouseForRent, OpenCarForSale and the rest produces one spec per cell of a grid, each repeating most of the others. So `offer.type` carries sale/rent/lease/auction/free/wanted and `subject.type` carries property/vehicle, and a subject profile adds only the fields peculiar to its subject. A reader that knows OpenListing but not a profile still reads price, location, offer and dates correctly. `openhouse` is not used as a name: in real estate an open house is a viewing event, not a kind of listing. An actual one is an entry in `showings`. The problem section is grounded rather than asserted: RESO tracks 484 separate MLSs as of August 2026, down from nearly twice that in 2015; each does publish a RESO Web API feed, and each requires a real estate licence, a signed per-MLS data agreement and vendor credentials. Four hundred and eighty-four negotiations for one national view, not redistributable. Vehicle parts are the same shape with different letters (ACES/VCdb, TecDoc). Decisions worth naming. Prices are decimal STRINGS, because 2450.10 is not representable in binary floating point and a listing is a price. Absent means unstated, never zero -- with `bedrooms: 0` called out in the property profile as the one place where zero genuinely differs and a studio is not an unstated bedroom count. `location.precision` lets a seller publish a postal code rather than a street for an occupied home, and forbids a reader from drawing a pin on a building anyway. A closed listing SHOULD stay served for 90 days with `closed_at`, because a directory that learns from a 404 cannot tell "sold" from "outage" -- the failure that makes every coupon site a graveyard. The vehicle profile treats a VIN as a claim rather than a proof, points readers at NHTSA vPIC (free, keyless, public domain) for decoding rather than making sellers restate what the VIN encodes, and puts parts fitment explicitly out of scope: that is ACES/VCdb or TecDoc, and a public spec cannot restate licensed data. Registered as one parent and two blocks under it, using the registry's existing `parent` field the way OpenServer's blocks already do. Flat slugs rather than nested paths because /docs/[slug] is a single dynamic segment, not a catch-all -- a nested slug would not have routed at all. nichedb.dev is named as the reference directory. 50 contract tests pass including spec-discovery and page-metadata. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
4.3 KiB
OpenProperty
The property profile of OpenListing. It adds one key, subject.property, describing a house, an apartment, a room, a plot of land or a commercial unit. Everything else — the offer, the price, the location, the media, the provenance — is the parent specification and is not restated here.
Status: 0.1 draft, alongside the parent.
Slug: openproperty
When this profile applies
When subject.type is "property". A reader that does not know this profile still reads the whole listing correctly; it simply does not know how many bedrooms the thing has.
subject.property
"subject": {
"type": "property",
"title": "Two-bedroom flat on Elm Street",
"property": {
"kind": "apartment",
"bedrooms": 2,
"bathrooms": 1.5,
"floor_area": { "value": 780, "unit": "sqft" },
"lot_area": { "value": 0.12, "unit": "acre" },
"year_built": 1974,
"floors": 1,
"floor": 3,
"parking": { "spaces": 1, "kind": "street" },
"furnished": "unfurnished",
"heating": "gas",
"pets": "cats-only",
"features": ["balcony", "dishwasher"],
"energy": { "rating": "C", "scheme": "EPC" }
}
}
kind
house, apartment, condo, townhouse, room, land, commercial, parking, storage, other.
room is a room within a dwelling let separately, which is a different thing from a one-bedroom apartment and is routinely mislabelled as one. land carries lot_area and usually no floor_area.
Fields
| Field | Notes |
|---|---|
bedrooms |
An integer. A studio is 0, not absent — this is the one place absent and zero genuinely differ and the difference matters. |
bathrooms |
A number, because half-baths are real and 1.5 is the ordinary way to write one. |
floor_area |
value + unit (sqft, sqm). Internal area. |
lot_area |
value + unit (sqft, sqm, acre, hectare). |
year_built |
Four-digit year. |
floors |
How many storeys the dwelling has. |
floor |
Which storey it is on. Distinct from floors, and confusing them is the commonest error in property data. Ground floor is 0 in the UK sense and 1 in the US sense, so a publisher SHOULD also set floor_scheme to uk or us when it sets floor. |
parking |
spaces and kind (garage, driveway, street, permit, none). |
furnished |
furnished, part-furnished, unfurnished. |
heating |
Free text; no controlled vocabulary, because national ones disagree. |
pets |
allowed, none, cats-only, dogs-only, ask. |
accessibility |
step_free, lift, wet_room and similar, as a list. |
features |
Free-text list. Deliberately unconstrained: this is where everything a controlled vocabulary would lose goes. |
energy |
rating and scheme (EPC, HES, NABERS). The scheme is required alongside the rating, because a bare "C" means nothing without it. |
All are optional. Absent means unstated, exactly as in the parent, with bedrooms: 0 noted above as the meaningful exception.
Tenure
For a sale, subject.property.tenure MAY be freehold, leasehold, commonhold, share-of-freehold or other, with lease_years_remaining where it applies. A leasehold with eighty years left is a materially different asset from the same flat freehold, and a listing that omits this is omitting the thing a buyer most needs.
What this profile does not do
- No valuation. A listing says the asking price. What the thing is worth is somebody else's claim, and OpenOntology is where a claim with a source belongs.
- No condition or survey. "Needs work" is
descriptiontext, not a field, because every attempt to enumerate condition ends up either useless or misleading. - No floorplan geometry. A floorplan is media with
kind: "floorplan". Describing rooms as geometry is a different specification and a much larger one. - No address normalisation. The parent's
locationcarries what the seller stated, at the precision the seller chose. Normalising it is a consumer's job.
Market context is not a listing
Aggregate market data — median sale price for a ZIP, days on market, inventory — is not an OpenListing document and MUST NOT be published as one. It describes a market, not a thing on offer, and it has no offer and no seller. Directories that carry both keep them apart.