CMAs
How Run a CMA chooses comparable sales, what each dollar adjustment is based on, how the value conclusion is reached, the daily allowance, and what a CMA from this API is not.
Run a CMA turns an address, MLS number or propertyId into a
comparative market analysis built from MLS records: the subject's profile, sold comps with dollar
adjustments, the active, pending and expired competition, market conditions, a value conclusion and a
seller net sheet. Everything is computed; nothing is written by an AI. Each analysis is stored and can be
read again for 90 days.
Not an appraisal. A CMA is a pricing tool. The adjustments are rule-based estimates with published
defaults, the comps are chosen by a formula, and the facts come from MLS records that can be incomplete or
stale. Check the subject's facts (and send overrides for anything you know better), look at the comps,
and use your judgment before putting a number in front of a seller.
Where the subject can be
Anywhere your brokerage's MLSs cover. The brokerage is the limiter: a subject in an MLS you're not on
returns 404 not_found. The subject doesn't need to be your listing, and it doesn't need to be on the
market: any address with an MLS record, including an old sale, works.
Homes with no MLS history (US only, in the states your MLSs cover). When an address has no MLS record, or you send
subject.publicRecordId, the home's county public record stands in: its building facts and location
become the subject (subject.resolvedBy is publicRecord, subject.propertyId is null and
subject.recordId names the record), and comps come from whichever of your MLSs has the most sales of
that kind of home within a mile over the past year. The county's facts can be out of date or missing (it
rarely knows about a remodel), so check them and send overrides; subject.profile.sources marks each
fact publicRecord, override or mls. Such a CMA has no photos and no listing link for the subject.
How comps are chosen
- Same MLS, same property type as the subject (house, townhouse or condo), not rentals.
- Within the radius (default 0.75 miles), sold within the window (default 6 months), and within the size band (default ±20% of living area).
- The same home listed in two overlapping MLSs counts once (the most recent sale wins).
- Each sale is scored on distance, size difference, age difference, bedroom and bathroom difference and
how long ago it sold; the best
maxComps(default 6) are kept. - If fewer than
minComps(default 3) distinct homes are found, the search widens to 1 mile, then 2 miles, then 12 months, andsearch.widenedsays what happened.
Adjustments and their defaults
Each comp is adjusted to what it would have sold for as the subject. Sale prices are first taken net of
seller concessions. Percentages are of the comps' median net sale price; every adjustment in the response
carries its basis.
| Factor | Default |
|---|---|
| Time | The market's monthly price-per-sq-ft movement × months since the sale, clipped to ±1% per month. No time adjustment in thin markets (fewer than 6 months with 3+ sales, or fewer than 30 sales in the year) |
| Living area | Each sq ft of difference at 50% of the area's median price per sq ft |
| Full bath / half bath | 2.5% / 1.25% per bath |
| Bedroom | 1.5%, at most one bedroom (living area already explains most of it) |
| Garage space | 1.5% per space |
| Lot size | 10% of the median price per acre per acre of difference, capped at ±5% |
| Pool | 3% |
| Basement | Finished 3%, unfinished 1% |
| View | 2% |
| Condition | 3% per step between poor, fair, average, good and excellent |
| Updates | Roof 1%, HVAC 1%, kitchen 3%, bathrooms 1.5%, flooring 1%, windows 1% |
| Age | 0.25% per year of difference, capped at ±5% |
Override any of them per request with options.adjustmentRates (livingAreaShare, fullBathPct,
halfBathPct, bedroomPct, garageSpacePct, lotShare, lotCapPct, poolPct, basementFinishedPct,
basementUnfinishedPct, viewPct, conditionStepPct, agePerYearPct, ageCapPct, timeCapPctPerMonth,
grossFlagPct, updatePct.{roof,hvac,kitchen,bathrooms,flooring,windows}). Facts that are unknown on
either side produce no adjustment. A comp whose gross adjustment is above 25% is flagged
high_gross_adjustment and weighs half as much.
From comps to a value
- Each adjusted comp gets a weight that falls with its gross adjustment, its distance and its age, halved
when flagged.
indicatedValueis the weighted mean of the adjusted prices;rangeis the weighted 25th to 75th percentile, never narrower than ±2%. recommendedListPriceis the indicated value divided by the area's median list-to-sale ratio, nudged 1% up in a rising market or down in a declining one, rounded the way agents list.confidenceishighwith five or more similar, recent sales and small adjustments;mediumorlowas the comps get fewer, older, more spread out or more heavily adjusted.reasonssays why.marketdescribes the area within at least 1 mile over 12 months: monthly closings, medians, days on market, list-to-sale, absorption (actives now ÷ closings per month), a price trend and this month's share of the year's closings.market.ratescarries the current 30- and 15-year fixed national averages with their dates (see Mortgage rates); it isnullonly when the rate source is unreachable.netSheetis arithmetic on the list price you give (or the recommended one): commission, seller closing costs, mortgage payoff and other costs you supply.
Public records (US)
When public records are enabled for the deployment, a US subject is also matched to its county record
by street number and postal code: parcel number, the assessor's facts (beds, baths, living area, lot,
year built), the latest tax bill and assessed value, the last recorded sale, whether the owner lives
there, the open loans with an estimated remaining balance, and an automated valuation with
its range and confidence. Only loans still open count: one whose release was recorded, or that a later
refinance paid off, is left out even when the county record still lists it, and a newer refinance the
county record hasn't picked up yet is counted at its amortized balance. The record fills facts the MLS listing lacks (each fact's sources entry
says publicRecord when it did), the estimated loan balance becomes the net sheet's default
mortgagePayoff, and the automated value is returned as valuation.avm as a cross-check, never
blended into the comps-based conclusion. Names of owners, borrowers, buyers and sellers are never
returned. subject.publicRecordStatus says whether a record was found; Canadian homes are
unsupported.
Allowance
Your brokerage can run 200 CMAs per day by default (reset at midnight America/Chicago); every
response's meta.cmaQuota shows the limit, what's used and when it resets, and the
x-daily-cma-limit / x-daily-cma-remaining headers carry the same. Reading a stored CMA is free.
Over the allowance you get 429 daily_quota_exceeded with Retry-After. Your CSM can raise it for your
brokerage. Runs are lookup calls (20 per minute) and one runs at a time per brokerage; a second one
started while the first is running gets 429 rate_limited and a short Retry-After.
A recipe
curl -s -X POST "$B/cma" "${H[@]}" -H "Content-Type: application/json" -d '{
"subject": { "address": { "line1": "4182 E Saguaro Bloom Dr", "state": "AZ", "postalCode": "85718" } },
"overrides": { "condition": "good", "updates": ["kitchen"] },
"options": { "netSheet": { "mortgagePayoff": 312000 } }
}' | jq '{ value: .data.valuation.indicatedValue, range: .data.valuation.range, list: .data.valuation.recommendedListPrice,
confidence: .data.valuation.confidence, comps: [.data.soldComps[] | {mls: .property.mlsRecordId, sold: .salePrice, adjusted: .adjustedPrice, flags}],
proceeds: .data.netSheet.estimatedProceeds }'