# Check a property's history

import { Card } from "zudoku/ui/Card";
import { Calculator, RotateCcw, CircleAlert } from "zudoku/icons";

Read one property's recorded sales, detected flips, ownership, permits and listing changes.

## Choose the property

Send exactly one of `address` or `property_id` to `POST /v1/history`.
Both or neither returns 400 before charging.
Member presence counts: an empty `address` alongside `property_id` still means both.

An address resolves the property using the same matching, ambiguity and coverage rules as underwriting.
A `property_id` reads the known property directly, without address matching.
Take that identifier from an underwritten subject or comparable property; single-property underwriting accepts an address or property identifier.

Check `subject_property` to confirm the returned house.
For identifier lookup, `address_quality.code` is `identified-by-property-id`.
The house number and ZIP code were not compared on that path.

No other inputs are accepted.
Underwriting options `condition`, `rehab_budget`, `target_margin` and `overrides` are rejected.

## Send the request

Save your [API key](/definitions), the credential identifying you, in `INVESTORLIFT_API_KEY`.
Set your client timeout to at least 30 seconds.
These examples use the address input.

<CodeTabs syncKey="request-language" hideIcon>

```bash title="cURL"
curl https://api.investorliftdata.com/v1/history \
  --request POST --max-time 30 \
  --header "Authorization: Bearer $INVESTORLIFT_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"address": "1026 N Beckley Ave, Dallas, TX 75203"}'
```

```javascript title="JavaScript"
const response = await fetch("https://api.investorliftdata.com/v1/history", {
  method: "POST",
  signal: AbortSignal.timeout(30_000),
  headers: {
    Authorization: `Bearer ${process.env.INVESTORLIFT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"address": "1026 N Beckley Ave, Dallas, TX 75203"}),
});
console.log(await response.json());
```

```python title="Python"
import os
import requests

response = requests.post(
    "https://api.investorliftdata.com/v1/history",
    headers={
        "Authorization": f"Bearer {os.environ['INVESTORLIFT_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={"address": "1026 N Beckley Ave, Dallas, TX 75203"},
    timeout=30,
)
print(response.json())
```

</CodeTabs>

## Charges and empty records

This is a [charged call](/definitions), an API request with a price.
Each successful history call is charged the price on its [reference page](/api/history) and in the [per-call price list](/pricing/per-call-prices).

:::warning
A property with nothing on file is a charged answer.
When available county records contain no transactions, the call returns 200 with an empty `transactions` array.
Its `records_statement` says the records were checked and nothing was on file.

Unread or incomplete transaction records are a failure, not an empty history.
The call returns a 5xx and the charge is returned to your balance.
:::

## Read the answer

<details>
<summary>Show the full response (159 lines)</summary>

```json title="Full response" operation="history"
{
  "flips": [],
  "permits": {
    "hvac": {
      "work_status": null,
      "most_recent_date": null,
      "most_recent_job_value": null
    },
    "roof": {
      "work_status": null,
      "most_recent_date": null,
      "most_recent_job_value": null
    },
    "permits": [
      {
        "tags": [
          "tags_electrical",
          "tags_solar"
        ],
        "file_date": "2023-10-05",
        "job_value": 16000,
        "final_date": "2025-04-26",
        "issue_date": "2023-10-05",
        "work_status": "completed",
        "construction_duration": 569
      },
      {
        "tags": [
          "tags_electrical"
        ],
        "file_date": null,
        "job_value": null,
        "final_date": null,
        "issue_date": "2023-04-21",
        "work_status": "open",
        "construction_duration": null
      },
      {
        "tags": [
          "tags_plumbing"
        ],
        "file_date": null,
        "job_value": null,
        "final_date": null,
        "issue_date": "2016-03-08",
        "work_status": "completed",
        "construction_duration": null
      }
    ],
    "plumbing": {
      "work_status": "completed",
      "most_recent_date": "2016-03-08",
      "most_recent_job_value": null
    },
    "statement": "Building-permit records were retrieved for this property. The permits below are the most recent recorded work in each trade, together with any permit still open and any demolition. Each category is a conclusion derived from the county record, not a statement made by the county.",
    "demolition": {
      "work_status": null,
      "most_recent_date": null,
      "most_recent_job_value": null
    },
    "electrical": {
      "work_status": "completed",
      "most_recent_date": "2023-10-05",
      "most_recent_job_value": 16000
    },
    "water_heater": {
      "work_status": null,
      "most_recent_date": null,
      "most_recent_job_value": null
    },
    "coverage_known": true,
    "has_open_permit": true
  },
  "summary": {
    "market_state": "off-market",
    "flip_detected": false,
    "open_permit_work": true,
    "long_tenured_owner": true,
    "demolition_recorded": false,
    "asking_price_reduced": null,
    "last_recorded_sale_kind": "family-transfer",
    "recorded_loan_available": false,
    "last_recorded_buyer_type": "individual",
    "permit_records_available": true,
    "has_recorded_transactions": true
  },
  "transactions": [
    {
      "kind": "family-transfer",
      "reo_sale": null,
      "sale_date": "2008-03-29",
      "buyer_type": "individual",
      "sale_price": 0,
      "foreclosure": null,
      "inter_family": true,
      "price_is_estimated": null
    }
  ],
  "market_status": {
    "state": "off-market",
    "sold_date": null,
    "sold_price": null,
    "price_reduced": null,
    "days_on_market": null,
    "list_price_low": null,
    "rental_listing": false,
    "list_price_high": null,
    "failed_listing_date": "2023-08-09",
    "list_price_low_date": null,
    "list_price_high_date": null,
    "original_listing_date": null
  },
  "seller_position": {
    "basis": "No recorded loan is available for this property, so no payoff estimate and no equity estimate can be made. Nothing is inferred from that absence: it does not mean the property is owned free and clear, and it does not mean the last purchase was made in cash. What this section does report is how long the current owner has held title, taken from the chain of title above.",
    "days_owned": 6783,
    "owned_since": "2008-03-29",
    "recorded_loan_available": false
  },
  "subject_property": {
    "facts": {
      "bathrooms": {
        "value": 2,
        "source": "county-record"
      },
      "year_built": {
        "value": 1935,
        "source": "county-record"
      },
      "square_feet": {
        "value": 2047,
        "source": "county-record"
      },
      "property_type": {
        "value": "Single Family",
        "source": "county-record"
      },
      "lot_size_square_feet": {
        "value": 7100,
        "source": "county-record"
      }
    },
    "address": {
      "zip": "75203",
      "city": "Dallas",
      "state": "TX",
      "street": "1026 N Beckley Ave"
    },
    "property_id": "00000000000000000000000000000001",
    "address_quality": {
      "code": "zip-confirmed",
      "statement": "The house number matched exactly one county record, and that record sits in the same ZIP code your address resolved to. Check the address and property facts in this block before relying on these numbers."
    },
    "outside_model_range": {
      "flagged": false,
      "reasons": []
    }
  },
  "records_statement": "The recorded sale and deed records for this property were checked. The transactions below are what is on file, newest first. Each category and each buyer type is a conclusion derived from the county record, not a statement made by the county."
}
```

</details>

The response contains `subject_property`, `records_statement`, `transactions`, `flips`, `seller_position`, `permits`, `market_status` and `summary` only.
The [reference](/api/history) describes each member.

## Transactions

`transactions` lists recorded events newest first.
A missing date or price is `null` and does not remove the event.
Events without `sale_date` appear last; they are not identified as the oldest.

The `kind` is `standard-sale`, `foreclosure`, `bank-repo`, `family-transfer`, `new-construction` or `unknown`.
The `buyer_type` is `individual`, `company`, `trust` or `unknown`.
These categories are derived conclusions from documents, not facts asserted by the county.

Treat `unknown` as not established.
A nominal-price transfer is never a `standard-sale`.
`price_is_estimated: true` means the record's own estimate, not recorded consideration.

## Flips

`flips` lists buy/sell pairs with **540 days or fewer** between recorded sale dates.
The boundary is inclusive: a hold of exactly 540 days is a flip.
Both legs need real market prices; family transfers are excluded.

`days_held` can be zero for a same-day close.
`percentage_gain` can be negative; `0.4195` means a 41.95% gain.

`seller_posture` is `professional`, `private` or `unknown`.
A company or trust is professional; an individual is private.
An unrecorded seller is `unknown`, not automatically private.

## Ownership and loans

`seller_position.owned_since` uses the newest dated transaction.
`seller_position.days_owned` is whole days since that date.
Unestablished ownership length or a future sale date gives `null`, not a negative duration.

`recorded_loan_available` is `false`: no per-property loan, mortgage or lien record is available here.
There is no payoff or equity estimate.
Missing loan records do not establish free-and-clear ownership or a cash purchase.

An estimate from a loan recorded at the last sale would miss later refinances.
It would need a range, not a single figure.
Ownership length comes from the transaction chapter.

## Permits

Read `coverage_known`, not the array length.
It is `true` only when at least one permit record was returned.
Empty records cannot distinguish no permits, unavailable county records or a nonexistent property.

Unavailable or failed permit lookups return an empty `permits` array, `coverage_known: false` and a plain `statement`.
The status facts are `null`; these records never establish that the property has no permits.
A failed permit lookup leaves the other history chapters intact, and the successful call is still charged.

The list selects the most recent permit in each category, plus every open permit and every demolition.
Categories are `roof`, `hvac`, `water_heater`, `electrical`, `plumbing` and `demolition`.
Each category has `most_recent_date`, `most_recent_job_value` and `work_status`; missing values are `null`.

Each permit has recorded dates, `job_value`, `tags` and `construction_duration`.
Missing dates or values are `null`; tags are fixed categories, not county free text.
`construction_duration` has no established unit; do not assume days or weeks.

`work_status` is `completed`, `inactive`, `open`, `in-review` or `unknown`.
Unknown status does not mean completed work.
`has_open_permit` is `true` for open or in-review work.

It is `false` only when records were retrieved and every status was readable.
Unavailable records or any unreadable status give `null` when no open work is established.

No permit number or reference identifier is returned, because either could retrieve a public record naming the applicant.
No permit count is returned: the underlying count is a page size, not a total.

## Listing changes

`market_status` describes the current listing.
A rental is not a sale listing: `rental_listing` marks it, and sale-specific dates, prices and days on market are withheld.
Unknown status remains `unknown`; a cancelled listing is not reported as `failed`.

`price_reduced` is `true` only when the low asking price was recorded after the high price.
A low price recorded first means the price went up.
If either price or either date is missing, the result is `null`.

## Summary

`summary` is computed from the chapters, not a second read of the records.
Its members are booleans, fixed categories or `null`, with no free text.
Map these values to your own fields or decision rules.

A value not established by its chapter is `null` or `unknown`, never `false`.
`false` means checked and no.
Unavailable permit records leave `open_permit_work` and `demolition_recorded` as `null`.

`long_tenured_owner` uses the configured ownership-duration threshold.
It is `null` when ownership length cannot be established.

## Names and identifiers

This history response omits buyer, seller and owner names, their mailing addresses and recorder document numbers.
It publishes derived categories instead.

## Errors and safe retries

The [Trace ID](/definitions), the identifier for a call, is in every response's `trace-id` header.
Quote it when contacting support.

`property-not-found` returns 422 when no record matches the identifier; the charge is returned to your balance.
History never returns `unscorable-address`, because it does not underwrite the property.

Send an `Idempotency-Key` to replay a stored successful answer without another charge.
Refunded calls or calls without a stored answer run again; the replay window also matters.
[Retry safely](/production/retry-safely) explains each case.

Reusing a stored history key and body for `/v1/predict` is a conflict, not a replay.
The endpoints answer different questions.

## Next steps

<div className="docs-next-steps">

<Card className="docs-next-card"><a href="/scoring/score-a-property"><Calculator className="docs-next-icon" size={18} strokeWidth={1.5} aria-hidden="true" focusable="false" /><strong>Score a property</strong><span className="docs-next-description">Read its valuation and supporting sales.</span><span className="docs-next-arrow" aria-hidden="true">→</span></a></Card>

<Card className="docs-next-card"><a href="/production/retry-safely"><RotateCcw className="docs-next-icon" size={18} strokeWidth={1.5} aria-hidden="true" focusable="false" /><strong>Retry safely</strong><span className="docs-next-description">Handle stored answers and retries.</span><span className="docs-next-arrow" aria-hidden="true">→</span></a></Card>

<Card className="docs-next-card"><a href="/production/handle-errors"><CircleAlert className="docs-next-icon" size={18} strokeWidth={1.5} aria-hidden="true" focusable="false" /><strong>Handle errors</strong><span className="docs-next-description">Read failures and choose your next step.</span><span className="docs-next-arrow" aria-hidden="true">→</span></a></Card>

</div>
