# HUD Section 8 Contract Expiration Monitor + Owner Contacts (`malonestar/hud-section8-contract-expiration-monitor`) Actor

Every HUD project-based Section 8, 202 and 811 HAP contract with its true term end (not the funding increment), assisted units, rent-vs-FMR position, owner and management-agent contacts, and a new-since-last-run delta.

- **URL**: https://apify.com/malonestar/hud-section8-contract-expiration-monitor.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Real estate, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.60 / 1,000 contract rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## HUD Section 8 Contract Expiration Monitor + Owner Contacts

Every HUD project-based Section 8, 202 and 811 HAP contract in the United States, with
**the date the contract term actually ends**, assisted units, rent-vs-FMR position,
owner and management-agent contacts, and a new-since-last-run delta.

Sourced live on every run from HUD's two published Multifamily files — 24,315 contracts
covering 1,483,851 assisted units across 23,612 properties (measured 2026-08-17 on the
2026-08-11 vintage).

### Who it is for

- **Affordable-housing acquirers and preservation developers** — find the contracts that
  genuinely come up for renewal in your window, with the owner's name, mailing address,
  phone and email already on the row.
- **LIHTC syndicators and Year-15 buyers** — screen Section 202/811 and older Section 8
  stock by term end, rent position and Opportunity Zone status.
- **Lenders and mortgage bankers** — size the HAP-expiry exposure in a market before
  underwriting.
- **Housing policy researchers and state housing agencies** — build an honest expiring-
  contract pipeline instead of one inflated roughly four-fold.

### The thing this actor exists to get right

**HUD publishes two expiration dates and the obvious one is not the contract term end.**

| HUD column | What it actually is | 24-month screen (from 2026-08-18) |
|---|---|---|
| `tracs_overall_expiration_date` | **Contract term end** — the genuine renewal / opt-out decision point | **4,627 contracts / 126,711 units** |
| `tracs_current_expiration_date` | End of the **current funding increment** — routine annual funding | 18,939 contracts / 1,184,903 units |

That is **4.09x the contracts and 9.35x the assisted units** for the same window. The
evidence that the overall column is the term end: `tracs_effective_date` plus
`contract_term_months_qty` lands on it for **22,580 of 24,315 contracts (92.9%)** and on
the current column for only **8,483 (34.9%)**.

The clearest single case is **Starrett City in Brooklyn**, the largest project-based
Section 8 contract in the country at 3,569 assisted units. Its current funding increment
ends **2027-01-31**. Its contract term runs to **2041-01-31**. Screening on the
increment column tells an acquirer that a 3,569-unit HAP contract expires in five months
when it has fifteen years left.

This actor defaults to `expirationBasis: "overall"` and **ships both dates on every
row**, labelled — `contract_term_end_date` and `current_funding_increment_end_date` —
along with `expiration_dates_differ`, `expiration_gap_days` and
`term_end_reconciles_with_effective_plus_term` so you can see the reasoning rather than
trust it. Set `expirationBasis: "current"` if you specifically want the funding-increment
calendar; nothing is hidden from you either way.

### Example input

```json
{
  "states": ["CA"],
  "expirationBasis": "overall",
  "expiringWithinMonths": 36,
  "contractStatuses": ["Active"],
  "maxResults": 300
}
```

Other useful shapes:

```json
{ "contractNumbers": ["NY36M000277"] }
{ "states": ["TX", "OK"], "expiringFiscalYears": ["2027", "2028"], "minAssistedUnits": 50 }
{ "ownerSearch": "Fairstead", "expirationBasis": "either", "expiringWithinMonths": 60 }
{ "states": ["OH"], "deltaOnly": true }
```

### What it will refuse to do

This actor never returns a green empty run in place of an answer it could not compute.

- **No scope** (`{}`) — fails immediately rather than dumping and billing all 24,315
  contracts.
- **An unparseable filter** — a three-letter state code, a non-numeric property id, an
  unknown `expirationBasis` — fails and names the field. It is never silently dropped.
- **Either HUD file unreachable or unparseable** — fails. The properties file is where
  state, county, MSA, owner and management-agent contacts come from, so a run without it
  would publish nulls that read as verified absences.
- **HUD's data no longer matching the contract this actor was tested against** — fails,
  with the specific measurement that moved.

Every failure message ends "Nothing was emitted and nothing was billed."

A query that is well-formed, reaches HUD, and genuinely matches nothing returns **0 rows
and SUCCEEDS**. That is the answer.

### The live drift gate

Before a single row is buffered — and long before anything is pushed or billed — every
run re-measures HUD's files against the properties this actor was built on. All of these
are logged on every run, so a future tightening can be evidence-based:

| Check | Live value 2026-08-17 | Fails at |
|---|---|---|
| Contract row count | 24,315 | below 20,000 |
| Property row count | 23,612 | below 20,000 |
| Contract-to-property join coverage | 100.00% | below 95% |
| HUD's published fiscal year vs the one derived from the term-end date | 100.00% | below 99% |
| Term end later than funding-increment end | 63.18% | below 20% |
| Positive canary NY36M000277 (Starrett City) | 3,569 units, NY, term 2041-01-31 > increment 2027-01-31 | any mismatch |
| Negative control ZZ00X000000 | 0 matches | any match |
| Unrecognised status / doc type / program group | 0.00% | above 5% |
| File vintage age | 6 days | above 400 days |

The fifth row is the product premise: if HUD ever collapses the two expiration columns
into one meaning, "contract term end" stops being separable from "current funding
increment end" and this actor fails loudly rather than publishing the wrong pipeline.

### New-since-last-run delta

`deltaOnly: true` returns only contracts that are new, or whose term end, funding
increment end, assisted units or status moved since the previous run.

Two things this deliberately does **not** do:

- The baseline lives in a **named** key-value store, not the per-run default store. A
  per-run store would make every contract read "new" on every run, forever, and bill the
  full file as a delta.
- The baseline is always the **full national universe**, never the filtered subset. A
  California-scoped run cannot make the next Texas-scoped run report everything as new.

On the very first run there is no baseline, so `is_new_since_last_run` is **`null`, not
`true`** — "we have never seen this file" is not the same claim as "this contract is new".
`delta_status` reads `baseline_established`.

### Data quality decisions you can audit

Every one of these was measured on the live file, and each is visible on the row rather
than resolved silently.

- **`rent_to_FMR_ratio = 0` is a null sentinel**, carried by exactly the 334 contracts
  whose `rent_to_FMR_description` reads "Unknown". Reading those zeros as real would rank
  them as the deepest below-FMR contracts in America — the most attractive preservation
  targets — off data HUD never published. `rent_to_fmr_pct` is `null` there and
  `rent_to_fmr_basis` says `zero_sentinel_not_published`.
- **`msa_code = "9999"` is an undocumented second sentinel** on 6,489 of 23,612
  properties (27.5%), with a null MSA name, carried by properties plainly inside metro
  areas including Starrett City in Brooklyn. `"0000"` is HUD's documented "Not In Metro
  Area" (681 properties). Both are suppressed from `msa_code` and preserved in
  `msa_code_raw` with `msa_sentinel_meaning`.
- **State comes from the property record, never the contract number.** The first two
  characters of a contract number look like a state code but disagree with the property's
  own `state_code` on 229 of 24,315 contracts (0.94%) — the territories use a separate
  vocabulary (RQ for PR ×196, VQ for VI ×19, TQ for MP ×4, GQ for GU ×1) and nine
  contracts are genuinely cross-border (OR→WA ×6, MD→DC, LA→MS, RQ→VI). Both values ship,
  with `state_prefix_matches_property_state`.
- **A bedroom-size FMR of 0 alongside a non-zero unit count of that size is missing, not
  $0** (680 contracts).
- **A participant id of 0 is a null sentinel**, not participant zero (37 owner rows, 82
  agent rows).
- **HUD uses three different blank conventions in one pair of files** — whitespace
  padding on the property block, a single space on the owner/agent block, an empty string
  on `associated_financing_Number` and `ownership_effective_date`. All become `null`.
- **The properties sheet ends with three entirely blank rows** inside its declared range.
  They are dropped rather than emitted as three all-null properties.
- **Four contracts publish a funding increment ending after the contract term**, and one
  publishes no current expiration at all. Flagged via
  `expiration_dates_inconsistent`, never normalised away.
- **70 properties have contract assisted units exceeding HUD's own total unit count.**
  Flagged via `assisted_units_exceed_property_total`, never clamped.
- **The data vintage is read from the `Last-Modified` header on every run** and published
  on every row. HUD replaces these files in place, so the vintage cannot be inferred from
  the URL.

### Use as an MCP tool

This actor is callable by AI agents over Apify's hosted MCP endpoint. Add it to a client
config:

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": ["-y", "@apify/actors-mcp-server", "--actors", "malonestar/hud-section8-contract-expiration-monitor"],
      "env": { "APIFY_TOKEN": "your-apify-token" }
    }
  }
}
```

A worked agent prompt: *"List every HUD Section 8 contract in Ohio whose contract term
ends in federal fiscal year 2028, with at least 100 assisted units, and give me the
owner's email and phone for each."*

Billing is unchanged when called as a tool: one dataset row is one billable result, and a
run that cannot answer fails without billing.

### Related actors

- `hud-affordable-housing-explorer` — LIHTC, multifamily and public-housing inventory with
  Opportunity Zone overlay.
- `hud-qct-lihtc-boost-screener` — Qualified Census Tract and 130% basis-boost eligibility.
- `fema-nri-county-risk-profile` — county and tract natural-hazard risk for the same assets.

### Output fields

All 148 fields are documented here and declared in the dataset schema.
Every field is nullable unless it is structurally always present. `null` means **not
checked**; `false` means **checked and negative**. The two are never collapsed.

| Field | Type | Description |
|---|---|---|
| `contract_number` | string | null | HUD TRACS contract number, 11 characters, unique across all 24,315 contracts. The stable delta identity. |
| `property_id` | integer | null | HUD property id. Joins this contract to the assisted-properties file that supplies location, owner and management-agent contacts. |
| `property_name` | string | null | Property name as published on the contract record, trimmed of HUD's fixed-width padding. |
| `contract_status` | string | null | TRACS contract status. Live vocabulary: Active, Expired, Pending, Suspended, Terminated, Executed. |
| `contract_status_is_active` | boolean | null | True when contract\_status is Active. Null when HUD published no status. |
| `contract_status_vocabulary_known` | boolean | null | False when HUD published a status outside the vocabulary this actor was tested against, so a new status is visible rather than silently mis-bucketed. |
| `contract_doc_type_code` | string | null | Contract document type: HAP, PRAC, RAC, PAC or COOP. |
| `program_type_name` | string | null | Detailed HUD program type, for example 'Sec 8 NC', 'PRAC/811', 'RAD PH Conv'. |
| `program_type_group_code` | string | null | Program type group code, for example S8NC, PRAC, LMSA, 202, HFDA. |
| `program_type_group_name` | string | null | Human-readable program type group. |
| `is_hud_administered` | boolean | null | True when HUD administers the contract directly rather than a state or local contract administrator. |
| `is_old_acc` | boolean | null | True when the contract sits under an old Annual Contributions Contract. |
| `is_performance_based_acc` | boolean | null | True when the contract is administered under a Performance-Based Contract Administrator ACC. |
| `assisted_units` | integer | null | Number of units receiving assistance under this contract. Sums to 1,483,851 nationally. |
| `units_0br` | integer | null | Assisted efficiency/studio units on this contract. |
| `units_1br` | integer | null | Assisted one-bedroom units on this contract. |
| `units_2br` | integer | null | Assisted two-bedroom units on this contract. |
| `units_3br` | integer | null | Assisted three-bedroom units on this contract. |
| `units_4br` | integer | null | Assisted four-bedroom units on this contract. |
| `units_5plus_br` | integer | null | Assisted five-or-more-bedroom units on this contract. |
| `bedroom_counts_reconcile_with_assisted_units` | boolean | null | True when the six bedroom-size counts sum exactly to assisted\_units. Measured true on 24,236 of 24,236 contracts that publish bedroom detail. Null when HUD published no bedroom breakdown. |
| `expiration_basis_used` | string | null | Which date column actually satisfied the expiration filter on this row: 'overall' (contract term end) or 'current' (funding increment end). Under expirationBasis 'either' this tells you which one matched. |
| `expiration_date_used` | string | null | The date corresponding to expiration\_basis\_used. |
| `months_to_expiration_used` | integer | null | Whole months from the run date to expiration\_date\_used. |
| `contract_effective_date` | string | null | Contract effective date. |
| `contract_term_months` | integer | null | Contract term in months as published by HUD. |
| `contract_term_end_date` | string | null | THE CONTRACT TERM END, from HUD's tracs\_overall\_expiration\_date. This is the genuine renewal / opt-out decision point and reconciles with effective date + contract term on 92.9% of contracts. Use this, not the current funding increment end, to size a renewal pipeline. |
| `contract_term_end_fiscal_year` | integer | null | Federal fiscal year of the contract term end, as published by HUD. |
| `contract_term_end_fiscal_quarter` | string | null | Federal fiscal quarter (Q1 = Oct-Dec) of the contract term end, as published by HUD. |
| `contract_term_end_fiscal_year_derived` | integer | null | Federal fiscal year recomputed from contract\_term\_end\_date. Agrees with HUD's published value on 100% of rows; a divergence is a live drift signal and fails the run. |
| `current_funding_increment_end_date` | string | null | End of the CURRENT FUNDING INCREMENT, from HUD's tracs\_current\_expiration\_date. Earlier than the term end on 63.2% of contracts. A 24-month screen on this column returns 18,939 contracts / 1,184,903 units against 4,627 / 126,711 on the term-end column: 4.09x the contracts and 9.35x the units. Renewing an increment is routine; it is NOT the contract coming up for renewal. |
| `current_increment_end_fiscal_year` | integer | null | Federal fiscal year of the current funding increment end, derived. |
| `current_increment_end_fiscal_quarter` | string | null | Federal fiscal quarter of the current funding increment end, derived. |
| `tracs_overall_expiration_serial_raw` | integer | null | HUD's raw Excel date serial for tracs\_overall\_expiration\_date, kept for audit against the source file. |
| `tracs_current_expiration_serial_raw` | integer | null | HUD's raw Excel date serial for tracs\_current\_expiration\_date, kept for audit against the source file. |
| `expiration_dates_differ` | boolean | null | True when the contract term end and the current funding increment end are different dates. Null when either is missing. |
| `expiration_gap_days` | integer | null | Days from the current funding increment end to the contract term end. Negative on the four contracts where HUD publishes an increment ending after the term. |
| `expiration_dates_inconsistent` | boolean | null | True when HUD publishes a current increment end LATER than the contract term end (4 contracts nationally). Surfaced rather than silently normalised. |
| `term_end_reconciles_with_effective_plus_term` | boolean | null | True when contract\_effective\_date plus contract\_term\_months lands within three days of the contract term end. True on 22,580 of 24,315 contracts; this is the evidence that the overall column is the term end. |
| `days_to_term_end` | integer | null | Signed days from the run date to the contract term end. Negative when the term has already ended. |
| `days_to_current_increment_end` | integer | null | Signed days from the run date to the current funding increment end. |
| `term_end_is_past` | boolean | null | True when the contract term end is before the run date. |
| `current_increment_end_is_past` | boolean | null | True when the current funding increment end is before the run date. |
| `rent_to_fmr_pct` | number | null | Contract rent as a PERCENT of Fair Market Rent (HUD names the column a ratio but publishes a percent). NULL when HUD did not publish it, including the 334 contracts where the source carries a literal 0 alongside a description of 'Unknown' -- reading those zeros as real would rank them as the deepest below-FMR contracts in the country. |
| `rent_to_fmr_band` | string | null | HUD's own banding of rent against FMR, for example 'Below 80% FMR' or 'Over 160% FMR'. 'Unknown' means HUD published no rent position. |
| `rent_to_fmr_basis` | string | null | 'published' when HUD published a rent-to-FMR percent, 'zero\_sentinel\_not\_published' when the source carried the 0 sentinel, 'not\_published' otherwise. |
| `rent_above_fmr` | boolean | null | True when rent\_to\_fmr\_pct exceeds 100. NULL when HUD published no rent position -- never false on unpublished data. |
| `rent_at_or_below_80pct_fmr` | boolean | null | True when rent\_to\_fmr\_pct is 80 or less, the classic deep-discount preservation signal. NULL when unpublished. |
| `fmr_0br` | integer | null | Fair Market Rent for efficiency units on this contract. Null when HUD published 0 alongside a non-zero unit count of that size (a missing value, not a $0 rent). |
| `fmr_1br` | integer | null | Fair Market Rent for one-bedroom units. Null when missing rather than 0. |
| `fmr_2br` | integer | null | Fair Market Rent for two-bedroom units. Null when missing rather than 0. |
| `fmr_3br` | integer | null | Fair Market Rent for three-bedroom units. Null when missing rather than 0. |
| `fmr_4br` | integer | null | Fair Market Rent for four-bedroom units. Null when missing rather than 0. |
| `property_join_status` | string | null | 'matched' when the contract resolved to a property record, 'unmatched' when it did not. 100% matched on the 2026-08-11 vintage. On an unmatched row every location and contact field is null because it was NOT CHECKED, not because it is absent. |
| `hub_name` | string | null | HUD multifamily hub responsible for the property. |
| `servicing_site_name` | string | null | HUD servicing site (field office) for the property. |
| `property_address_line1` | string | null | Property street address. |
| `property_address_line2` | string | null | Property address line 2. Populated on 597 of 23,612 properties. |
| `property_city` | string | null | Property city. |
| `property_state_code` | string | null | Property state or territory code from HUD's property record. THIS is the authoritative state -- see state\_from\_contract\_number\_prefix. |
| `property_zip_code` | string | null | Property ZIP code. |
| `property_zip4_code` | string | null | Property ZIP+4 extension. Null on 3,127 properties. |
| `state_from_contract_number_prefix` | string | null | The first two characters of the contract number, which look like a state code. Published for transparency only. It disagrees with the property's own state on 229 of 24,315 contracts (0.94%): the territories use a separate vocabulary (RQ for PR, VQ for VI, TQ for MP, GQ for GU) and nine contracts are genuinely cross-border. Never filter on this. |
| `state_prefix_matches_property_state` | boolean | null | False when the contract-number prefix disagrees with the property's state code. |
| `county_code` | string | null | Three-digit county code within the state. |
| `county_name` | string | null | County name. |
| `msa_code` | string | null | HUD MSA code, or NULL when the source carried a sentinel. See msa\_code\_raw. |
| `msa_code_raw` | string | null | HUD's raw msa\_code including sentinels. '0000' (681 properties) is HUD's documented 'Not In Metro Area'. '9999' (6,489 properties, 27.5%) is an UNDOCUMENTED second sentinel with a null MSA name -- it is carried by properties plainly inside metro areas, including Starrett City in Brooklyn, so treating it as a real code invents a phantom metro larger than any real one. |
| `msa_name` | string | null | HUD MSA name. Null on 6,501 properties. |
| `msa_code_is_sentinel` | boolean | null | True when msa\_code\_raw is 0000 or 9999. |
| `msa_sentinel_meaning` | string | null | 'not\_in\_metro\_area' for 0000, 'metro\_not\_stated' for 9999, null for a real code. |
| `congressional_district_code` | string | null | Congressional district code as published. '00' appears on 883 properties and covers both at-large districts and unstated values, so it is published verbatim rather than interpreted. |
| `placed_base_city_name` | string | null | HUD 'placed base' city name. Populated on 15,244 properties. |
| `property_phone` | string | null | Property phone as published by HUD, in any of at least five formats. |
| `property_phone_digits` | string | null | Property phone reduced to ten digits for deterministic CRM import. |
| `property_total_units` | integer | null | Total units at the property, assisted and unassisted. |
| `assisted_unit_share_pct` | number | null | assisted\_units as a percent of property\_total\_units. |
| `assisted_units_exceed_property_total` | boolean | null | True when this contract's assisted units exceed HUD's published total unit count for the property -- a source inconsistency affecting 70 properties, surfaced rather than clamped. |
| `property_category` | string | null | HUD property category, for example '202/811' or 'Subsidized, No HUD Financing'. |
| `primary_financing_type` | string | null | Primary financing type. Populated on 11,501 properties. |
| `associated_financing_number` | string | null | Associated FHA financing number. Populated on 11,411 properties. |
| `ownership_effective_date` | string | null | Date the current ownership took effect, normalised from HUD's M/D/YYYY string. Null on 6,796 properties. |
| `is_insured` | boolean | null | True when the property carries FHA mortgage insurance. |
| `is_202_or_811` | boolean | null | True when the property is a Section 202 (elderly) or Section 811 (disabled) property. |
| `is_hud_held` | boolean | null | True when HUD holds the mortgage. |
| `is_hud_owned` | boolean | null | True when HUD owns the property. |
| `is_hospital` | boolean | null | True when the property is a hospital. |
| `is_nursing_home` | boolean | null | True when the property is a nursing home. |
| `is_board_and_care` | boolean | null | True when the property is a board-and-care facility. |
| `is_assisted_living` | boolean | null | True when the property is assisted living. |
| `is_refinanced` | boolean | null | True when the property has been refinanced. |
| `is_section_221d3` | boolean | null | True when financed under Section 221(d)(3). |
| `is_section_221d4` | boolean | null | True when financed under Section 221(d)(4). |
| `is_section_236` | boolean | null | True when financed under Section 236. |
| `is_non_insured` | boolean | null | True when the property carries no FHA insurance. |
| `is_bmir` | boolean | null | True when the property is Below Market Interest Rate. |
| `is_risk_sharing` | boolean | null | True when the property is under an FHA risk-sharing program. |
| `is_mip` | boolean | null | True when a mortgage insurance premium applies. |
| `is_co_insured` | boolean | null | True when the mortgage is co-insured. |
| `is_opportunity_zone` | boolean | null | True when the property sits in a Qualified Opportunity Zone (13,381 of 23,612 properties). |
| `owner_contact_status` | string | null | 'complete' when organisation/individual name, email, phone and address are all present; 'partial' when some are; 'absent' when none are; 'not\_checked' when the property record did not join; 'suppressed\_by\_input' when includeContacts was false. Never conflate absent with not checked. |
| `owner_participant_id` | integer | null | HUD participant id of the owner. Null when HUD published the 0 sentinel (37 properties). |
| `owner_company_type` | string | null | Owner company type: Profit Motivated, Non-Profit or Limited Dividend. |
| `owner_organization_name` | string | null | Owner organisation name. Populated on 23,550 of 23,612 properties. |
| `owner_individual_full_name` | string | null | Owner individual name where the owner is a natural person (84 properties). Null everywhere else -- HUD writes a single space, not a blank. |
| `owner_individual_title` | string | null | Title of the individual owner, where published. |
| `owner_address_line1` | string | null | Owner mailing address line 1. |
| `owner_address_line2` | string | null | Owner mailing address line 2. |
| `owner_city` | string | null | Owner mailing city. |
| `owner_state_code` | string | null | Owner mailing state. Often differs from the property state -- an absentee-ownership signal. |
| `owner_zip_code` | string | null | Owner mailing ZIP code. |
| `owner_zip4_code` | string | null | Owner mailing ZIP+4 extension. |
| `owner_phone` | string | null | Owner main phone as published, in any of at least five formats. |
| `owner_phone_digits` | string | null | Owner main phone reduced to ten digits. |
| `owner_fax` | string | null | Owner main fax as published. |
| `owner_email` | string | null | Owner contact email, lowercased. Populated on 23,459 of 23,612 properties (99.35%), all containing an @, across 11,151 distinct addresses. |
| `mgmt_agent_contact_status` | string | null | Same contract as owner\_contact\_status, for the management agent. |
| `mgmt_agent_participant_id` | integer | null | HUD participant id of the management agent. Null when HUD published the 0 sentinel (82 properties). |
| `mgmt_agent_company_type` | string | null | Management agent company type. |
| `mgmt_agent_org_name` | string | null | Management agent organisation name. Populated on 23,478 properties. |
| `mgmt_agent_individual_full_name` | string | null | Management agent individual name where the agent is a natural person (100 properties). |
| `mgmt_agent_individual_title` | string | null | Title of the individual management agent. |
| `mgmt_agent_address_line1` | string | null | Management agent mailing address line 1. |
| `mgmt_agent_address_line2` | string | null | Management agent mailing address line 2. |
| `mgmt_agent_city` | string | null | Management agent mailing city. |
| `mgmt_agent_state_code` | string | null | Management agent mailing state. |
| `mgmt_agent_zip_code` | string | null | Management agent mailing ZIP code. |
| `mgmt_agent_zip4_code` | string | null | Management agent mailing ZIP+4 extension. |
| `mgmt_agent_phone` | string | null | Management agent main phone as published. |
| `mgmt_agent_phone_digits` | string | null | Management agent main phone reduced to ten digits. |
| `mgmt_agent_fax` | string | null | Management agent main fax as published. |
| `mgmt_agent_email` | string | null | Management agent contact email, lowercased. Populated on 23,426 properties across 10,374 distinct addresses. |
| `owner_is_own_management_agent` | boolean | null | True when the owner and management agent share a HUD participant id -- self-managed. Null when either id is the 0 sentinel. |
| `delta_status` | string | null | 'baseline\_established' on the first run or when the baseline could not be read, 'new' when the contract number is absent from the baseline, 'changed' when term end, increment end, assisted units or status moved, 'unchanged' otherwise. |
| `is_new_since_last_run` | boolean | null | True only when this contract number was genuinely absent from the previous full-universe baseline. NULL -- never true -- when there is no usable baseline, because 'we have never seen this file' is not the same claim as 'this contract is new'. |
| `delta_changed_fields` | array | null | Names of the fields that moved since the baseline. Empty array when unchanged, null when there is no baseline. |
| `delta_previous_values` | object | null | Previous values of the changed fields. Null when nothing changed or there is no baseline. |
| `baseline_built_at` | string | null | When the baseline this run compared against was written. Null on a baseline-establishing run. |
| `baseline_contract_count` | integer | null | How many contracts the baseline held. The baseline is always the FULL national universe, never the filtered subset, so a state-scoped run cannot make the next run report everything as new. |
| `contracts_data_vintage` | string | null | Last-Modified date of HUD's contract workbook, read fresh on every run. HUD replaces these files in place, so the vintage cannot be assumed from the URL. |
| `properties_data_vintage` | string | null | Last-Modified date of HUD's assisted-properties workbook. |
| `contracts_source_status` | string | null | 'ok' when HUD's contract file was retrieved and parsed. A run reaching a row at all means this is 'ok'; the field exists so the provenance travels with the data. |
| `properties_source_status` | string | null | 'ok' when HUD's assisted-properties file was retrieved and parsed. The run fails rather than emitting rows with unverified nulls in the contact fields. |
| `retrieved_at` | string | null | Date this run executed, in UTC. |
| `matched_rows_total` | integer | null | How many contracts matched the query BEFORE maxResults was applied, so a capped answer is never mistaken for a complete one. |
| `results_truncated` | boolean | null | True when matched\_rows\_total exceeded maxResults and this dataset is a partial answer. |
| `max_results_applied` | integer | null | The maxResults cap this run used. |
| `expiration_basis_requested` | string | null | The expirationBasis this run was asked for. |

### Source

- `https://www.hud.gov/sites/dfiles/Housing/documents/MF-Assistance-Sec8-Contracts1.xlsx`
  (24,315 contracts x 31 columns)
- `https://www.hud.gov/sites/dfiles/Housing/documents/MF-Properties-with-Assistance-Sec8-Contracts1.xlsx`
  (23,612 properties x 74 columns)

Both are public, keyless, and joined on `property_id`. Vintage 2026-08-11 as measured
2026-08-17; HUD republishes roughly monthly and replaces the files in place.

### FAQ

**When does a HUD Section 8 contract actually expire?**  On its contract term end
(`contract_term_end_date`). The current funding increment end is a routine annual
funding action, not the renewal decision.

**Can I get the owner's email and phone?**  Yes. Owner email is populated on 23,459 of
23,612 properties (99.35%) and the management agent email on 23,426 (99.21%), across
11,151 and 10,374 distinct addresses respectively. Owner mailing address, phone and
company type ship too, plus `owner_contact_status` so a partial record is visible.

**How do I find expiring Section 8 contracts in my state?**  `{"states":["CA"],
"expiringWithinMonths":36,"contractStatuses":["Active"]}`.

**How do I monitor for new or amended contracts?**  Schedule the actor and set
`deltaOnly: true`. The first run establishes a full-universe baseline and reports
`baseline_established`; subsequent runs report only what moved.

**Which contracts have rents below FMR?**  Filter on `rent_at_or_below_80pct_fmr` or
sort by `rent_to_fmr_pct`. Rows where HUD published no rent position carry `null`, not
`0`, so they cannot masquerade as deep discounts.

**Does this cover Section 202 and 811?**  Yes: 5,687 PRAC 202/811 contracts, 3,894
Section 202, 953 811 PRA Demo and 121 PAC 202/811, alongside the Section 8 stock.

# Actor input Schema

## `states` (type: `array`):

Two-letter state or territory codes, matched against the HUD PROPERTY record (CA, NY, TX, PR, VI, GU, MP, DC). Do not use the first two characters of the contract number: they disagree with the property's own state on 229 of 24,315 contracts.

## `expirationBasis` (type: `string`):

HUD publishes two different expiration dates. 'overall' is the CONTRACT TERM END - the genuine renewal / opt-out decision point, and the default. 'current' is the end of the current funding increment, which is earlier on 63.2% of contracts: a 24-month screen on it returns 18,939 contracts / 1,184,903 units against 4,627 / 126,711 on the term end. Starrett City's increment ends 2027-01-31 while its contract runs to 2041.

## `expiringWithinMonths` (type: `integer`):

Return contracts whose chosen expiration date falls between today and N months from today. Leave blank to apply no window. Not defaulted: an unrequested window would silently narrow every query.

## `expiringFiscalYears` (type: `array`):

Federal fiscal years (beginning 1 October) in which the chosen expiration date falls, for example \["2027","2028"]. Combined with the month window as an OR.

## `programGroups` (type: `array`):

Program type group codes or detailed program names. Groups: PRAC, 202, S8NC, LMSA, HFDA, 515, S8 RAD Conv, PRAD, S8SR, PD, PRES, PAC, Pension Fund. Leave empty for all.

## `contractStatuses` (type: `array`):

TRACS statuses to keep: Active, Expired, Pending, Suspended, Terminated, Executed. Leave empty for all statuses - not defaulted to Active, because a hidden status filter is a hidden answer.

## `minAssistedUnits` (type: `integer`):

Only return contracts with at least this many assisted units. Leave blank for no minimum.

## `contractNumbers` (type: `array`):

Look up specific 11-character HUD TRACS contract numbers, for example \["NY36M000277"].

## `propertyIds` (type: `array`):

Look up every contract on specific HUD property ids, for example \["800015895"].

## `ownerSearch` (type: `string`):

Case-insensitive substring matched against the owner organisation, owner individual, management agent organisation and management agent individual names.

## `deltaOnly` (type: `boolean`):

Return only contracts that are new or whose term end, funding increment end, assisted units or status moved since the previous run's full-universe baseline. On the very first run there is no baseline, so this returns nothing rather than billing the whole file as new.

## `includeContacts` (type: `boolean`):

Include owner and management-agent name, address, phone and email. Turn off to emit the same row shape with those values nulled and a status of 'suppressed\_by\_input'.

## `maxResults` (type: `integer`):

Caps how many matching contracts are emitted and billed. Every row carries matched\_rows\_total and results\_truncated, so a capped answer is never mistaken for a complete one.

## `simulateDrift` (type: `boolean`):

Diagnostic only. When true, the live drift gate is forced to fail so the fail-loudly path can be verified on demand: the run FAILS, emits zero rows and bills nothing. It can only make the gate stricter, never looser. Leave false for normal use.

## Actor input object example

```json
{
  "states": [
    "CA"
  ],
  "expirationBasis": "overall",
  "expiringWithinMonths": 36,
  "contractStatuses": [
    "Active"
  ],
  "deltaOnly": false,
  "includeContacts": true,
  "maxResults": 300,
  "simulateDrift": false
}
```

# Actor output Schema

## `results` (type: `string`):

The default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "states": [
        "CA"
    ],
    "expiringWithinMonths": 36,
    "contractStatuses": [
        "Active"
    ],
    "maxResults": 300
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/hud-section8-contract-expiration-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "states": ["CA"],
    "expiringWithinMonths": 36,
    "contractStatuses": ["Active"],
    "maxResults": 300,
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/hud-section8-contract-expiration-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "states": [
    "CA"
  ],
  "expiringWithinMonths": 36,
  "contractStatuses": [
    "Active"
  ],
  "maxResults": 300
}' |
apify call malonestar/hud-section8-contract-expiration-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,malonestar/hud-section8-contract-expiration-monitor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/UNTARQhOY62AiT7aT/builds/nIzLe5LdOUIHwgwG8/openapi.json
