Menu · Guides
Guides

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.

View as Markdown

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

  1. Same MLS, same property type as the subject (house, townhouse or condo), not rentals.
  2. Within the radius (default 0.75 miles), sold within the window (default 6 months), and within the size band (default ±20% of living area).
  3. The same home listed in two overlapping MLSs counts once (the most recent sale wins).
  4. 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.
  5. If fewer than minComps (default 3) distinct homes are found, the search widens to 1 mile, then 2 miles, then 12 months, and search.widened says 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. indicatedValue is the weighted mean of the adjusted prices; range is the weighted 25th to 75th percentile, never narrower than ±2%.
  • recommendedListPrice is 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.
  • confidence is high with five or more similar, recent sales and small adjustments; medium or low as the comps get fewer, older, more spread out or more heavily adjusted. reasons says why.
  • market describes 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.rates carries the current 30- and 15-year fixed national averages with their dates (see Mortgage rates); it is null only when the rate source is unreachable.
  • netSheet is 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 }'