Reading a score
A Scored Call returns two kinds of number: what the pricing model estimates, and what the recorded comparable sales imply. They are different evidence, they move for different reasons, and reading them as one number is the mistake this page exists to prevent. The member-by-member schema, with the full description of every field, is in the API reference — this page is about what the numbers are and are not.
Two numbers, two sources
pricing_model_arv_estimateis read from the pricing model's own value distribution. It does not move when comparable sales are added or removed.comp_bandis derived from the comparable sales themselves, size-normalised to the property where their square footage allows it. It is the number that does move with the comps.
If you display both, expect exactly one of them to change when the comparable evidence changes. That is not a bug in either number; it is what each one is.
When the model's estimate is withheld
model_estimate_available is always present, alongside subject_property. When it is false, the
model's estimate and its distribution are withheld from the response: they were
checked against the comparable sales and did not agree with them, and a confidently
wrong valuation is worse than none. The Comp Band and the comparable sales are
unaffected.
A response with model_estimate_available: false is a charged 200. The Comp Band
it ships with is a valuation — derived from recorded sales, size-normalised, stated
with its evidence — so the call did what a Scored Call is for. One Credit was spent,
exactly as for any other Scored Call.
subject_property — which house these numbers are about
Every scored answer names the property it resolved to and the facts the valuation ran on. A confident number about the wrong house looks exactly like a confident number about the right one, so read this block before you act on the rest of the response.
This block is additive: an integration that ignores it is completely unaffected. Nothing else in the response changed when it was added.
It carries four things.
1. Which property. property_id is the property data service's identifier for the
house that was scored, and address is that record's address as the county stores
it — not the address you submitted. Echoing your own text back would prove nothing;
the resolved record is the only thing that shows which house was priced. The stored
city is lower-cased by the property data service, and it is published unchanged rather
than re-cased into something the county does not hold. A component the record does not
carry is left out, and address is omitted altogether when the record carries none of
them — property_id is the identifier that is always present.
2. The seven facts. Under facts:
| Fact | What it is |
|---|---|
square_feet | Finished living area, in square feet. |
bedrooms | Bedroom count. |
bathrooms | The bathroom count as the property data service calculates it, published as stored. It can be fractional — 2.5 is ordinary here, not an edge case. |
partial_bathrooms | The partial-bathroom count the record stores separately. The word partial is the record's own term and does not necessarily mean a half bath. |
year_built | The year the structure was built. |
lot_size_square_feet | Lot size, in square feet — never acres. |
property_type | The property type in the county record's own wording. |
bathrooms and partial_bathrooms are two separate stored figures, and this service
does not add the two together. Each is published exactly as the property data service
holds it. We do not claim what the calculated bathroom count already includes, so do not
read the two as a sum — a record reporting 2.5 and 1 is not a house with three and a
half bathrooms.
Each one is a { value, source } pair, and source is one of exactly two values:
county-record, meaning the property data service's stored value for this property, or
caller-stated, meaning a value you supplied in its place using the overrides request
option described below. A fact you did not supply reads county-record, and
partial_bathrooms reads county-record on every answer, because it cannot be overridden.
A fact that is missing means the county record does not carry it. That is not an
error, and it is not "not applicable": the fact is left out of facts entirely rather
than returned as null, 0 or an empty string, because the valuation ran without it
too. Absence is information — it tells you the number you are reading was produced
without that fact. A recorded living area of zero is read as no living area at all — zero square feet
is not a building — so that property is scored without square footage and the block
says so by leaving square_feet out.
3. How far the address was confirmed. address_quality carries a code you can
branch on and a statement you can show someone. It is not a confidence score and
not a probability. Getting an answer at all already required your house number
to match exactly one complete county record; this reports whether the ZIP code
agreed as well.
| Code | What it means | What it does not mean |
|---|---|---|
zip-confirmed | Your address resolved to a readable ZIP code, and the matched county record sits in the same one. | Not a guarantee the record is correct or current — only that two specific checks agreed. |
zip-unconfirmed | Either no readable ZIP code was available for your address, or the county record does not carry that same ZIP code — a different one, or none at all. | Not a failed match. The house number still matched exactly one complete county record. |
The ZIP code being compared is the one your address resolved to, which is not always one you typed — a query with no ZIP code in it often still resolves to one.
4. Whether the property is outside the model's range. outside_model_range is
always present, flagged or not, and carries flagged plus a reasons list that is
empty when nothing applies. It is a caution about the estimate, not a claim about the
house: when it is flagged, read the numbers in this answer with extra scrutiny.
| Reason | What it means |
|---|---|
large-lot | The lot is larger than the sizes the pricing model is calibrated over. |
large-structure | The finished living area is larger than the sizes the model is calibrated over. |
property-type | The property type the valuation ran on — the county record's, or yours if you overrode it — is one the model does not cover well: multifamily, commercial, mixed-use or industrial. |
A property can carry more than one reason. A fact neither the county record nor your
overrides supplies cannot raise its own reason: an unknown lot size is not a large lot.
The size thresholds are policy figures, calibrated by judgment and intended to be tuned.
These reasons are read from the facts the valuation actually ran on, so a fact you
stated yourself can raise one — state a five-acre lot and you will see large-lot. That
is deliberate: the caution is about the estimate you are being handed, and that estimate
was computed from your values.
overrides — pricing the property as you describe it
A county record is not always right, and it is often not current. When a seller tells you
the basement is finished, the addition is done, or the lot is bigger than the record says,
send overrides and the property is priced as you describe it — and the answer says
plainly which facts were your claim and which were the county's.
It is additive. An integration that does not send overrides is completely unaffected:
same request on the wire, same answer, same labels, same cost. Sending it never changes
what a call costs — it is a request option, not a second property, so a call with a
what-if is one Credit exactly as it was before.
The six facts you can override
| Fact | Units and rules |
|---|---|
square_feet | Finished living area, in square feet. Must be a number above zero. |
bedrooms | Bedroom count. A number, zero or above — 0 is accepted, because a studio genuinely has none. |
bathrooms | Bathroom count, as the property data service counts them. A number, zero or above, and may be fractional — 2.5 is ordinary. |
year_built | The year the structure was built. A number above zero. |
lot_size_square_feet | Lot size, in square feet — never acres. A number above zero. |
property_type | The property type, as free text. Must not be blank or only spaces; surrounding spaces are trimmed, exactly as the county record's own value is, so the trimmed value is what appears in facts. |
Code
The coordinates are never overridable, and that is a rule
A what-if changes the house's facts, never which house. latitude and longitude
are not on the list above and never will be, so there is no way to ask this API for a
valuation of one property under another property's address. property_id and the address
in subject_property always describe the house the county record matched.
partial_bathrooms is not overridable either, so its source reads county-record
on every answer. And note that overriding bathrooms leaves partial_bathrooms at the
county's figure — this service still does not add the two together, so do not read
your own stated bathroom count plus a county partial count as one total.
What caller-stated means
Every fact in subject_property.facts carries a source. A fact you supplied through
overrides reads caller-stated; everything else reads county-record.
A value you supply reads caller-stated even when it matches the county record's own
figure. The label says where the number came from, not whether it changed — so a claim
can never be mistaken for a record, and a record is never quietly relabelled as a claim.
Anything we cannot accept is refused before you are charged
overrides is checked before any Credit is spent, so a rejected what-if costs nothing.
These are all refused with a 400:
- any key that is not one of the six above — including
latitude,longitude,partial_bathrooms,address, or a misspelling. Unrecognised keys are refused, never silently ignored, so a typo can never leave you believing a claim was priced when it was not; - a value of the wrong type — a string where a number belongs, an object, an array,
true; nullfor any fact. An override may state a value; it can never erase one;- a square footage, lot size or year at or below zero, a negative bedroom or bathroom
count, or a blank
property_type; overrides: {}. An empty what-if states no claim, and its answer would be indistinguishable from a call that sent nooverridesat all.
Everything above is refused identically by POST /v1/validate, which costs nothing and
makes no charge of any kind — so you can check a body before you spend a Credit on it.
The required facts are still required
A valuation needs the property's coordinates, its lot size and its property type. If the property still lacks one of those after your values are applied, the call is refused and the Credit is refunded, exactly as an ordinary request would be. There is no "score it anyway": a confident number built on facts the model never received is the one thing this API will not sell you.
The useful consequence runs the other way too. lot_size_square_feet and property_type
are both required and overridable, so a value you supply can make a property scorable
that the county record alone could not score — and on that answer, the fact that rescued
it reads caller-stated.
Pricing on your own numbers
The repair budget and the target margin behind a score are ours by default. Both can be yours instead. Both inputs are additive: an integration that sends neither is completely unaffected, and every response looks exactly as it did before they existed — plus the two echoes described below.
rehab_budget — your repair cost, in whole dollars
Send rehab_budget and the repaired value and the maximum allowable offer are built from
your figure instead of the bracket condition maps to. Accepted range: 0 to 10,000,000,
whole dollars only — 48000, never 48000.50.
- It overrides
conditionentirely. Sending both is legal and documented, and the budget wins every time. You never have to pick the bracket that comes closest to a number you already know. - Zero is a real answer. A house you have walked and found needs no work is
rehab_budget: 0, and it prices exactly asturn_keydoes. condition: tear_downis still refused with a 400, even with a repair budget. A repair budget does not answer what a tear-down needs, which is a land value — and there is no land-value estimate behind these numbers. Analyze lot value separately.
target_margin — the margin your buyer needs, as a fraction
Send target_margin and the maximum allowable offer is solved at your margin. 0.2
means twenty percent. Accepted range: 0.01 to 0.9. The default is 0.2.
Both are echoed back, on every scored answer
rehab_budget and target_margin in the response are the numbers the arithmetic
actually used — yours when you sent them, ours when you did not. They are present
either way, deliberately: otherwise "what margin is this price built on?" would be
unanswerable on exactly the calls where you assumed the default.
Neither input changes what a call costs. They are request options, not properties: one property is one Credit whether you send both, one or neither.
max_allowable_offer — the ceiling, and the one flag to read beside it
max_allowable_offer is the highest purchase price at which a cash flip buyer on
standard financing still clears the target margin. It is the algebraic inverse of a flip
pro forma rather than a price quantile, so a seller's suggested price and a buyer's
underwriting agree by construction.
This is the investor's ceiling — your offer to the seller is this minus your fee.
It is not a prediction of what the property will trade at, and it is not an offer to make as written. It is the number above which the deal stops working at the margin you asked for.
Code
| Member | What it is |
|---|---|
amount | The price, in whole dollars, rounded to the nearest thousand. |
clamped | true when no purchase price at all clears the target margin on this deal, at this repair budget. |
clamped is the no-price-works alarm, and it is the flag to branch on. When it is
true the amount beside it is not an offer and must not be presented as one — a
zero-dollar price is not a price. It is computed from the raw solve before any rounding,
so a deal that solves to a small negative number is still reported as clamped rather
than rounded quietly to zero. Read it on every answer before you act on the amount.
Both members are present on every answer that carries a price, and the block is absent whenever the Comp Band is absent.
max_allowable_offeris the name on every path that returns a price. There is one scoring vocabulary and one version of it, and no second shape to choose between; see the Versioning page.
cost_stack — the deal behind the price
cost_stack breaks the maximum allowable offer into the line items a flip buyer would
actually carry. It is present on exactly the answers that carry a price.
| Line | What it is |
|---|---|
down_payment | The cash the buyer puts into the purchase. |
loan | The borrowed balance — the maximum allowable offer less the down payment. |
interest | Interest on that loan over the hold period below. |
points | Lending points charged on the loan. |
hold_days | How long the deal is modelled as being held. It grows with the repair budget, because a heavier renovation takes longer. |
closing_costs | Closing costs, charged on both the purchase and the resale. |
target_profit | The profit the offer was solved to leave on the table — your target_margin of the rehab-adjusted ARV, taken before that ARV was rounded for publication. Multiply the published rehab_adjusted_arv by target_margin yourself and you will land close to this figure but not exactly on it; the difference is the ARV rounding, and the price was solved against the unrounded value. |
These are whole dollars, and down_payment plus loan is the maximum allowable offer
exactly. Rounding is applied once, above both the price and these lines, so those two
columns always add back to the headline — on every answer, including the ones where no
price could be solved.
When a price was solved, the whole stack reconciles. On an answer where
max_allowable_offer.clamped is false, add down_payment, loan, rehab_budget,
interest, points, closing_costs and target_profit and you land back at
rehab_adjusted_arv. The gap is rounding and nothing else — the price to the nearest
thousand, rehab_adjusted_arv to the nearest thousand, and each remaining line to the
nearest dollar — so it stays a little over a thousand dollars across the repair budgets
this API brackets. It grows with a very large repair budget, because a longer hold puts
more financing cost on each rounded dollar of price.
When clamped is true, there is nothing to reconcile. No price in range cleared
your target margin, so the published price is a floor rather than a solution. The lines
still describe that published price honestly, but they will not add back to
rehab_adjusted_arv, and the difference can be large. Read clamped first on those
answers — it is what tells you the stack describes a deal nobody can do.
Every figure here follows from the financing assumptions behind the maximum allowable offer, which are policy figures calibrated by judgment and intended to be tuned — not a quote from any lender, and not fitted to a data set.
as_is_similar_band — what the comparable sales carry, per square foot
comp_band is a range of total dollars for this property. as_is_similar_band is a
different cut of the same evidence: a range of dollars per square foot, taken over the
comparable sales listed in comps.
It feeds nothing. No other number in the response is computed from it — not the Comp Band, not the rehab-adjusted value, not the maximum allowable offer, not the check that withholds a model estimate. It is evidence you can read beside those numbers, and that is all it is.
Code
The three filters
A comparable sale qualifies when all three hold. Each bound is a setting of this service, and the values it currently ships with are:
| Filter | What it means | Shipped value | Where it comes from |
|---|---|---|---|
| Size | The sale's square footage is within this fraction of this property's square footage | ±15% | Fixed by the requirement this block implements |
| Age | The sale's build year is within this many years of this property's | ±20 years | Fixed by the requirement |
| Recency | The sale was recorded within this many days before the request | 365 days | This service's own choice, not fixed by the requirement |
Both the size and the age fences are inclusive — a sale exactly at the bound is inside
it. A sale whose recorded date is missing, or is not a plain YYYY-MM-DD date we can read,
does not qualify: it is not treated as recent, and it is not treated as old.
If this property's own square footage or build year is missing from the county record, there is nothing for the size or age fence to measure against, so no sale qualifies — and you get the fallback below. Nothing is substituted for the missing figure.
The fallback is a stated outcome, not an edge case
Thin filters are ordinary. When fewer than 3 sales qualify (again, a setting of this service), the block does not go quiet and does not pretend:
basisflips fromsimilar-salestoall-comparable-sales;- the three filters are dropped, and the range is recomputed over every sale in
compsthat carries a usable sale price and square footage; statementchanges to say plainly that too few similar sales were found;qualifying_salesstill reports how many passed the FILTER — so on the fallback it is a number below 3. That is the point. It is not the size of the comparable list.
How to tell which one you got: read basis. Do not parse statement — it is written
for a person to read and its wording may change. basis is a closed set of exactly two
values and it is what to branch on.
The fallback stays a price-per-square-foot range. It never becomes the dollar
comp_band; those are two different units and they are never published under one name.
When the block is absent
If no comparable sale carries both a usable sale price and a usable square footage, the
member is absent — not present-and-empty, and not null. No member inside it is ever
null. The block is present on answers where the model estimate was withheld, for the same
reason comp_band and comps are: it is computed from the recorded sales, not from the
model.
What each comparable sale carries
Every entry in comps carries the sale's address, date, price, size, beds, baths and build
year — and six further facts.
| Field | What it is |
|---|---|
property_id | The property data service's identifier for that property. It is what a property-history call takes — see below. |
similarity_rank | This sale's position in the comps list, starting at 1. |
price_per_square_foot | The sale's price divided by its square footage, in dollars. |
distance_miles | Straight-line distance from this property to that one, in miles, to two decimals. |
lotsize | The lot size recorded for that property. The unit is not stated — see below. |
property_type | The property type recorded for that property, republished word for word. |
What similarity_rank actually is
It is a position, not a score. Rank 1 is the sale the scoring service returned first;
rank 2 is the one it returned second. The comps array has always been "in the order the
scoring service ranked them", and this field simply makes that order readable without
counting array positions.
It is not a similarity percentage, and rank 1 is not a claim that the sale is the most comparable one. That service does not publish how it ordered them, so neither do we.
Ranks are computed after this property's own prior sale is dropped and after the list is capped, so they always run 1, 2, 3… with no gaps.
lotsize — why it has no unit, and no _square_feet in its name
The lot size for a comparable is republished under the field name the upstream data carries,
and this service has not established what unit that figure is in. Naming it
lot_size_square_feet would assert something nobody here has measured, so it is not named
that. Compare lot sizes against each other within one answer; do not convert them against a
unit you have assumed. (subject_property.facts.lot_size_square_feet is in square feet —
that one is established, and it is a different field from a different source.)
Missing values are left out, never sent as null
A sale missing a coordinate carries no distance_miles. One missing square footage carries
no price_per_square_foot. One whose price was recorded as a nominal amount — a quitclaim or a
transfer between relatives — carries neither sale_price nor price_per_square_foot, and
contributes to neither the range nor the qualifying count. There is no zero-filled lot size,
no "unknown" property type and no distance of 0 standing in for "we don't know".
property_id — the key to a property’s history
Every comparable carries a property_id, the same kind of identifier
subject_property.property_id carries for the property you asked about. It is the input a
property-history call takes — the request that returns the recorded
transaction story behind a comparable: what it last sold for, what kind of buyer bought it —
an individual, a company or a trust — and whether it was a flip. It reports the kind of
buyer, never a name.
That call is the History Call. property_id is what you hand it, so it is worth
keeping alongside any comparable you want to follow up on.
What is still never published
The comparables' latitude and longitude are not published, and publishing a distance does not change that — those coordinates identify someone else's parcel to the metre. The scoring service's internal similarity score is not published either: it is a number we cannot explain, and a number you cannot interpret is worse than no number.
What is verified, and what is not
Stated plainly, because the distinction matters.
Verified:
- The derived outputs are internally consistent and deterministic: the same response yields the same derived numbers, exactly.
- The maximum allowable offer is the algebraic inverse of a flip pro forma, and the round-trip between them holds to the cent — a seller's suggested price and a buyer's underwriting agree by construction.
- The check that withholds a model estimate disagreeing with the comparable sales
(the
model_estimate_available: falsepath above) is exercised and tested.
Not verified:
- There is no published back-test of predicted price against realized sale price. Not for the model's estimate, and not for the derived maximum allowable offer. If accuracy matters to your use case, budget for your own back-test.
- The target margin and the financing figures behind
max_allowable_offerare policy figures calibrated by judgment and intended to be tuned. They are not fitted to any data set.
Accuracy caveats that survive every release
- Residential only. Large multifamily and commercial properties are outside what the model covers, and will diverge rather than error.
- Tear-downs are unsupported. There is no land-value estimate; analyze lot value separately.
- Repair estimates are screening ranges, not bids. Small homes skew high per square foot, and heavy-rehab figures are the least reliable in the set.
- The band is only as good as the comps. Show
effective_sample_sizealongside any band you publish: a low value means the number rests on thin evidence and should be read with caution. - Sparse property data yields confident-looking numbers. A value computed from three missing inputs still renders with the same number of digits. The evidence members exist so you can tell the difference — use them.