# HUD Section 8 Expiring Contracts Scraper - Multifamily & LIHTC (`groundplanes/hud-section8-expiring-contracts`) Actor

Scrape HUD-assisted multifamily properties with expiring Section 8 and PRAC contracts, unit counts, rent-to-FMR ratios, REAC scores, and management agents, plus Year-15 and full LIHTC property lists.

- **URL**: https://apify.com/groundplanes/hud-section8-expiring-contracts.md
- **Developed by:** [Groundplane Labs](https://apify.com/groundplanes) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 properties

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## HUD Section 8 Expiring Contracts Scraper - Multifamily & LIHTC

Scrape HUD-assisted multifamily properties with upcoming Section 8 HAP / PRAC contract expiration dates, unit counts, rent-to-Fair Market Rent (FMR) ratios, REAC physical inspection scores, troubled status codes, and management agent organizations, plus Low-Income Housing Tax Credit (`LIHTC_YEAR15` and `LIHTC_ALL`) properties, directly from the U.S. Department of Housing and Urban Development (HUD) ArcGIS Online FeatureServer services.

### What it does

Affordable housing investors, lenders, housing authorities, and property management firms track HUD Section 8 Housing Assistance Payments (HAP) and Section 202/811 PRAC contract expirations as well as LIHTC Year-15 compliance milestones. Raw HUD ArcGIS layers store up to two assistance contracts per property (`CONTRACT1` / `CONTRACT2`) with Oracle-style string dates (`DD-MON-YY`) and separate schemas for assisted multifamily properties vs. LIHTC tax-credit projects.

This Actor:

- Queries HUD's official `MULTIFAMILY_PROPERTIES_ASSISTED` and `LIHTC` ArcGIS FeatureServer layers with automatic pagination (`resultOffset` / `resultRecordCount=1000`) and service-directory discovery fallback if layer paths change.
- Expands `CONTRACT1` and `CONTRACT2` on each multifamily property into individual expiring-contract rows when `contractExpirationDate` falls within your `expiringWithinMonths` window, calculating `monthsUntilExpiration`.
- Supports `LIHTC_YEAR15` (filtering `YR_PIS` to your target Year-15 placed-in-service cohort, such as `[2011, 2012]`) and `LIHTC_ALL` modes.
- Keeps individual person contact fields (`MGMT_CONTACT_FULL_NAME` and LIHTC `CONTACT`/`CO_TEL`) turned off by default (`includeContacts: false`), while always including business-level data (`managementAgent` organization name and `onSitePhone`).

### Who it's for

- **Affordable housing acquisition & preservation teams** identifying Section 8 properties with contracts expiring in the next 12–36 months or LIHTC properties entering their Year-15 disposition/recapitalization window.
- **Multifamily debt & FHA/agency lenders** screening properties by unit count, REAC inspection score, and rent-to-FMR ratio.
- **Property management companies & housing compliance consultants** benchmarking management portfolios across states.
- **Housing policy researchers & municipal planners** monitoring expiring affordable housing stock by state and city.

### Input

All filters have sensible defaults that return expiring Section 8 / PRAC contracts in Arizona (`["AZ"]`) within 24 months in a few seconds.

- **`dataset`** *(string enum)*: `"SECTION8_CONTRACTS"` (default), `"LIHTC_YEAR15"`, or `"LIHTC_ALL"`.
- **`states`** *(array of strings)*: Two-letter US state or territory postal codes (e.g. `["AZ"]` or `["AZ", "NV", "CO"]`). Default: `["AZ"]`.
- **`expiringWithinMonths`** *(integer)*: (`SECTION8_CONTRACTS` only) Keep contracts expiring between today and this many months into the future. Default: `24`.
- **`lihtcPlacedInServiceYears`** *(array of integers)*: (`LIHTC_YEAR15` only) 4-digit placed-in-service years (`YR_PIS`) to filter. Default: the current Year-15 cohort (current year minus 15 and minus 14), computed at run time when empty.
- **`minUnits`** *(integer)*: Minimum total housing units at the property. Default: `0`.
- **`includeContacts`** *(boolean)*: Include named individual contact fields (`contactName`, `contactPhone`, `contactEmail`). Defaults to `false` for privacy compliance.
- **`maxItems`** *(integer)*: Maximum rows to return. Default: `200`.

#### Example input JSON

```json
{
  "dataset": "SECTION8_CONTRACTS",
  "states": ["AZ"],
  "expiringWithinMonths": 24,
  "lihtcPlacedInServiceYears": [2011, 2012],
  "minUnits": 0,
  "includeContacts": false,
  "maxItems": 200
}
```

#### Example run options (default max charge $50)

When starting a run via the Apify API or Console, set a default maximum charge of **$50** (`"maxTotalChargeUsd": 50`) in your run options:

```json
{
  "maxTotalChargeUsd": 50
}
```

### Output

- **Sorting:**
  - For `SECTION8_CONTRACTS`, results are sorted by `contractExpirationDate` **ascending** (soonest-expiring contracts first) so the most urgent contract renewals and preservation opportunities are never cut off by `maxItems`.
  - For `LIHTC_YEAR15` and `LIHTC_ALL`, results are sorted by `yearPlacedInService` **descending** (newest placed-in-service projects first).
- **Deduplication:** `propertyId + contractNumber` for `SECTION8_CONTRACTS`; `hudId` for `LIHTC_YEAR15` and `LIHTC_ALL`.

#### Output fields (`SECTION8_CONTRACTS`)

| Field | Type | Description |
|---|---|---|
| `propertyId` | `string` | HUD Multifamily Property ID (`PROPERTY_ID`). |
| `propertyName` | `string \| null` | Property name (`PROPERTY_NAME_TEXT`). |
| `street` | `string \| null` | Standardized street address. |
| `city` | `string \| null` | Property city. |
| `state` | `string \| null` | Two-letter state code. |
| `zip` | `string \| null` | 5-digit ZIP code. |
| `totalUnits` | `integer \| null` | Total units at the property. |
| `assistedUnits` | `integer \| null` | HUD-assisted units at the property. |
| `contractNumber` | `string` | Section 8 / PRAC contract number (`CONTRACT1` or `CONTRACT2`). |
| `programType` | `string \| null` | HUD assistance program type (e.g. `"202/8 NC"`, `"PRAC/202"`, `"LMSA"`). |
| `contractExpirationDate` | `string` | Contract expiration date (`YYYY-MM-DD`). |
| `monthsUntilExpiration` | `number` | Months remaining from today until `contractExpirationDate`. |
| `rentToFmrRatio` | `number \| null` | Contract rent-to-Fair Market Rent (FMR) percentage. |
| `reacScore` | `string \| null` | Most recent HUD REAC physical inspection score. |
| `troubledCode` | `string \| null` | HUD troubled status indicator (`"Y"`, `"N"`, `"P"`). |
| `managementAgent` | `string \| null` | Management agent organization name (`MGMT_AGENT_ORG_NAME`). |
| `onSitePhone` | `string \| null` | On-site property phone number (`PROPERTY_ON_SITE_PHONE_NUMBER`). |
| `latitude` | `number \| null` | Property latitude. |
| `longitude` | `number \| null` | Property longitude. |
| `dataset` | `string` | `"SECTION8_CONTRACTS"`. |
| `contactName` | `string \| null` | Management contact person name *(only when `includeContacts: true`)*. |
| `contactPhone` | `string \| null` | Management contact phone *(only when `includeContacts: true`)*. |
| `contactEmail` | `string \| null` | Management contact email *(only when `includeContacts: true`)*. |

#### Output fields (`LIHTC_YEAR15` / `LIHTC_ALL`)

| Field | Type | Description |
|---|---|---|
| `hudId` | `string` | HUD LIHTC Project ID (`HUD_ID`). |
| `projectName` | `string \| null` | LIHTC project name (`PROJECT`). |
| `street` | `string \| null` | Street address. |
| `city` | `string \| null` | City. |
| `state` | `string \| null` | State code. |
| `zip` | `string \| null` | ZIP code. |
| `company` | `string \| null` | Sponsor / contact company (`COMPANY`). |
| `totalUnits` | `integer \| null` | Total project units (`N_UNITS`). |
| `lowIncomeUnits` | `integer \| null` | Low-income units (`LI_UNITS`). |
| `yearPlacedInService` | `integer \| null` | 4-digit year placed in service (`YR_PIS`). |
| `yearAllocated` | `integer \| null` | 4-digit year tax credits were allocated (`YR_ALLOC`). |
| `creditType` | `string \| null` | Tax credit type description (`CREDIT`). |
| `nonProfitSponsor` | `boolean \| null` | Whether the project has a non-profit sponsor (`NON_PROF`). |
| `latitude` | `number \| null` | Latitude. |
| `longitude` | `number \| null` | Longitude. |
| `dataset` | `string` | `"LIHTC_YEAR15"` or `"LIHTC_ALL"`. |
| `contactName` | `string \| null` | Sponsor contact person name *(only when `includeContacts: true`)*. |
| `contactPhone` | `string \| null` | Sponsor contact phone *(only when `includeContacts: true`)*. |

#### Sample output row (synthetic example)

```json
{
  "propertyId": "800999001",
  "propertyName": "Example Desert Palms Senior Apartments",
  "street": "450 E Camelback Rd",
  "city": "Phoenix",
  "state": "AZ",
  "zip": "85012",
  "totalUnits": 80,
  "assistedUnits": 78,
  "contractNumber": "AZ99T801001",
  "programType": "202/8 NC",
  "contractExpirationDate": "2027-04-30",
  "monthsUntilExpiration": 5.9,
  "rentToFmrRatio": 91.5,
  "reacScore": "92",
  "troubledCode": "N",
  "managementAgent": "EXAMPLE SOUTHWEST HOUSING MANAGEMENT LLC",
  "onSitePhone": "602-000-0100",
  "latitude": 33.5092,
  "longitude": -112.0685,
  "dataset": "SECTION8_CONTRACTS"
}
```

### How pricing works

Pay per result: $3 per 1,000 results ($0.003 per row). You only pay for rows returned. The Actor checks your run's maximum spending limit before every dataset push (recommended default: `"maxTotalChargeUsd": 50`) and stops cleanly if that limit is reached.

### Compliance

When `includeContacts` is set to `true`, the Actor includes named management or sponsor contacts published in HUD's public datasets. By default, `includeContacts` is `false`.

- All data is published by the U.S. Department of Housing and Urban Development (HUD) Open Data portal.
- Buyers and users are responsible for complying with the Telephone Consumer Protection Act (TCPA), the National Do-Not-Call (DNC) Registry, the CAN-SPAM Act, and all applicable state privacy laws before any outreach.
- Do not use this data for consumer or individual targeting.

### Data source and terms

- **Official source:** [HUD Geospatial Storefront / ArcGIS Online Services](https://hudgis-hud.opendata.arcgis.com/) (`MULTIFAMILY_PROPERTIES_ASSISTED` and `LIHTC` FeatureServer layers).
- **Update frequency:** `MULTIFAMILY_PROPERTIES_ASSISTED` is updated regularly by HUD Office of Multifamily Housing; the `LIHTC` layer is an annual national database release (last data edition December 2024). The actor measures each layer's last source edit on every run: if the multifamily layer goes more than 120 days without an edit, or the LIHTC layer more than 800 days, the run ends PARTIAL and says the source data is stale, with the last edit date.
- **Disclaimer:** This Actor is an independent tool and is **not affiliated with or endorsed by the U.S. Department of Housing and Urban Development (HUD)**.

### Troubleshooting

- **Run status messages:** every run ends with `Complete:` or `PARTIAL:`. `Complete:` means all matched rows were emitted and the counts in the message (rows matched, records scanned) are exact. `PARTIAL:` names the concrete reason, for example rows skipped after failing validation, or the max charge limit being reached.
- **Zero matches:** if the HUD source is healthy but nothing matches your filters, the run succeeds with `Complete: 0 rows matched your filters` and suggests which filters to widen. A failed run instead means the source itself errored or its fields changed.
- **Canary input:** the smallest input that returns rows in under 30 seconds. Use it to check the Actor is healthy: `{"dataset": "SECTION8_CONTRACTS", "states": ["AZ"], "expiringWithinMonths": 24, "maxItems": 5}`.
- Found a problem or need a field? Open an issue on the Issues tab; we respond quickly.

### Limits and FAQ

- **Does HUD publish owner entity names or management emails in the LIHTC layer?**
  No. The `MULTIFAMILY_PROPERTIES_ASSISTED` layer provides the management agent organization (`MGMT_AGENT_ORG_NAME`) and optional management contact details, while the `LIHTC` layer provides `COMPANY` and optional `CONTACT`/`CO_TEL`. Owner LLC names are not present in these layers and are never invented.
- **Why can the same `propertyId` appear twice in `SECTION8_CONTRACTS`?**
  A single multifamily property can have two separate HUD assistance contracts (`CONTRACT1` and `CONTRACT2`). If both contracts expire within your `expiringWithinMonths` window, one row is emitted per contract (`propertyId + contractNumber`).

### Changelog

- **v0.1.3 (2026-11-01):** Fix round: unparseable expiration dates are now counted, feed the 20 percent fail rule, appear in the PARTIAL reason with a sample, and trigger a first-page value sentinel that fails loudly when no expiration date parses; LIHTC Year-15 default is computed from the current year; retries are 6 tries with jittered backoff 2 to 32 s capped at 120 s; 2-digit years are interpreted as 20YY; service-directory fallback requires an exact name match; title gains Scraper; pricing states the per-1K figure.
- **v0.1.2 (2026-10-31):** Red Team fix round: per-layer freshness measurement (a stale HUD layer now ends the run PARTIAL with the last source edit date instead of reporting Complete forever); zero-scanned runs reconciled against the layer's total record count so an empty or broken source cannot masquerade as a zero match; server-side paging order with unique tiebreaks plus count reconciliation during the scan; core-column sentinel extended to headline fields (assisted units, rent-to-FMR ratios, management agent, LIHTC units and credit type); expiration windows use the US Eastern date so same-day expirations are not dropped early; `includeContacts` parsed strictly (a string "false" can never enable contacts); retries honor Retry-After; matched counts distinguish pre-dedupe matches from unique emitted rows; default memory set to 512 MB based on observed runs.
- **v0.1.1 (2026-10-31):** Honest status contract (`Complete:` / `PARTIAL:` with matched and scanned counts), zero matches now succeed as `Complete: 0`, core-column sentinel and per-row validation added, store category corrected to Business only, default run options (`maxTotalChargeUsd: 50`) declared.
- **v0.1 (2026-10-31):** Initial release supporting `SECTION8_CONTRACTS`, `LIHTC_YEAR15`, and `LIHTC_ALL` modes across HUD ArcGIS FeatureServer layers.

# Actor input Schema

## `dataset` (type: `string`):

Dataset to query: SECTION8_CONTRACTS (HUD-assisted multifamily properties with expiring assistance contracts), LIHTC_YEAR15 (LIHTC properties placed in service in the Year-15 window), or LIHTC_ALL (all LIHTC properties).

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

For SECTION8_CONTRACTS mode: keep contracts (`EXPIRATION_DATE1` or `EXPIRATION_DATE2`) expiring between today and this many months into the future.

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

Defaults to false for privacy compliance because `MGMT_CONTACT_FULL_NAME` (Section 8) and `CONTACT` / `CO_TEL` (LIHTC) identify individual people. Management agent organization name and on-site property phone are business data and are always included.

## `lihtcPlacedInServiceYears` (type: `array`):

For LIHTC_YEAR15 mode: 4-digit placed-in-service years (`YR_PIS`) to target properties approaching the 15-year compliance period milestone. When empty, it defaults to the current Year-15 cohort (current year minus 15 and minus 14).

## `maxItems` (type: `integer`):

Maximum number of contract or property rows to return.

## `minUnits` (type: `integer`):

Minimum total unit count on the property (`TOTAL_UNIT_COUNT` for Section 8, `N_UNITS` for LIHTC).

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

Two-letter US state or territory postal codes to filter properties (e.g. \["AZ"] or \["AZ", "NV", "CO"]).

## Actor input object example

```json
{
  "dataset": "SECTION8_CONTRACTS",
  "expiringWithinMonths": 24,
  "includeContacts": false,
  "lihtcPlacedInServiceYears": [
    2011,
    2012
  ],
  "maxItems": 200,
  "minUnits": 0,
  "states": [
    "AZ"
  ]
}
```

# Actor output Schema

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

No description

# 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 = {
    "dataset": "SECTION8_CONTRACTS",
    "expiringWithinMonths": 24,
    "includeContacts": false,
    "lihtcPlacedInServiceYears": [
        2011,
        2012
    ],
    "maxItems": 200,
    "minUnits": 0,
    "states": [
        "AZ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("groundplanes/hud-section8-expiring-contracts").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 = {
    "dataset": "SECTION8_CONTRACTS",
    "expiringWithinMonths": 24,
    "includeContacts": False,
    "lihtcPlacedInServiceYears": [
        2011,
        2012,
    ],
    "maxItems": 200,
    "minUnits": 0,
    "states": ["AZ"],
}

# Run the Actor and wait for it to finish
run = client.actor("groundplanes/hud-section8-expiring-contracts").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 '{
  "dataset": "SECTION8_CONTRACTS",
  "expiringWithinMonths": 24,
  "includeContacts": false,
  "lihtcPlacedInServiceYears": [
    2011,
    2012
  ],
  "maxItems": 200,
  "minUnits": 0,
  "states": [
    "AZ"
  ]
}' |
apify call groundplanes/hud-section8-expiring-contracts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,groundplanes/hud-section8-expiring-contracts"
        }
    }
}
```

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/JIiUU1fhCeAfvTjkG/builds/BuzjPMcST26Y970oe/openapi.json
