HUD QCT LIHTC Boost Screener — 130% Basis by Coordinate
Pricing
from $3.30 / 1,000 results
HUD QCT LIHTC Boost Screener — 130% Basis by Coordinate
Qualified Census Tract (QCT) lookup for any coordinate — instantly flag the LIHTC 130% eligible-basis boost per site. Batch lat/lon points against HUD's official QCT layer; each returns in_qct, the 11-digit tract GEOID, state, county FIPS, tract, QCT vintage, and boost-eligible flag. MCP-ready.
Pricing
from $3.30 / 1,000 results
Rating
0.0
(0)
Developer
Kyle Maloney
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
4 hours ago
Last modified
Categories
Share
HUD QCT + DDA Screener - LIHTC 130% Basis Boost
Batch-screen coordinates against the two HUD designations that grant the 130% eligible-basis boost for Low-Income Housing Tax Credit deals under IRC Sec. 42(d)(5)(B): a Qualified Census Tract (QCT) and a Difficult Development Area (DDA). One clean row per site, with the designation vintage stamped on every row and a year-over-year comparison so you can see when a tract has been added or dropped.
Keyless. Three HUD.Official.Content ArcGIS services, queried live on every run:
| Source | Live layer name | Designation | Live count (2026-10-24) |
|---|---|---|---|
| QCT, current | Qualified_Census_Tracts | FY2027 | 14,539 |
| QCT, earlier vintage | QUALIFIED_CENSUS_TRACTS_2025 | FY2025 | 15,727 |
| DDA, current | Difficult_Development_Areas | FY2027 | 3,000 |
The two "current" service URLs still contain
_2026. HUD overwrote them with the FY2027 designations on 2026-10-23 and stripped the year from the layer names, so neither the URL nor the layer name states the vintage any more — see §1b.
What changed in v1.1 (2026-08-01), and why it matters to a tax position
v1.1 is a hardening release. Two of the changes fix answers that were materially wrong, both reproduced live. No output field was removed or renamed and the price is unchanged.
1. v1.0 was screening a superseded designation
v1.0 queried only HUD's 2025 QCT service while HUD publishes a 2026 one, and it
stamped a hard-coded qct_year: "FY2025" constant that could never notice a new
year had been published. QCT designations churn materially - Colorado went from 258
QCTs in 2025 to 224 in 2026: 49 tracts dropped, 15 added.
Worked example, in the prefill: Grand Junction, CO tract 08077000200
(39.07095, -108.55684) is a QCT in 2025 and not a QCT in 2026. v1.0 reported
lihtc_130_basis_boost_eligible: true. v1.1 reports false, with
in_qct_prior_year: true and qct_designation_change: "dropped_since_prior_year" so
the prior-year answer is still there if you need it.
qct_year, qct_prior_year and dda_year are established at runtime, never from
a constant, and a vintage that cannot be established fails the run rather than guessing.
1b. v1.2 — HUD now republishes these services in place, so no URL or layer name is evidence of the vintage
On 2026-10-23 HUD published the FY2027 designations (effective 2027-01-01) by overwriting the FY2026 feature services and stripping the year from both layer names:
| before | after | |
|---|---|---|
| QCT layer | QUALIFIED_CENSUS_TRACTS_2026 | Qualified_Census_Tracts |
| DDA layer | Difficult_Development_Areas2026 | Difficult_Development_Areas |
The service URLs still say _2026, the ArcGIS item titles still say 2026, and the
item description still says 2025 — all three are now stale. Only the data moved. Taking
the year from the URL would stamp FY2026 on FY2027 answers, which is the v1.0 defect
re-committed behind a passing drift check.
So v1.2 identifies the vintage from the data: the designated tract set is the
designation, and a name can be renamed. The live counts are matched against HUD's own
published designation files (huduser.gov/portal/datasets/qct/QCT<YYYY>CSV.zip),
measured 2026-10-24:
| vintage | tracts | CO | NY | TX |
|---|---|---|---|---|
| FY2025 | 15,727 | 258 | 1,043 | 1,401 |
| FY2026 | 14,496 | 224 | 1,026 | 1,326 |
| FY2027 | 14,539 | 217 | 1,039 | 1,341 |
The live "_2026" service returns 14,539 / 217 / 1,039 / 1,341 — FY2027, exactly
— and its Colorado GEOID set matches HUD's FY2027 list 217 of 217 with zero difference
in either direction. Every row now carries qct_year_basis / dda_year_basis
saying how the year was established: layer_name (HUD published it — strongest),
data_fingerprint (HUD stripped it, so the designated-feature counts identified it), or
layer_name_and_data_fingerprint (both, in agreement). If the two disagree, or if
the fingerprint matches no known vintage, the run fails — so when FY2028 lands a
human updates the table deliberately instead of a guessed year shipping on a tax screen.
One consequence worth knowing: the FY2026 designation is no longer published keyless
anywhere. HUD overwrote it, so the earlier-vintage comparison is now FY2025 and
spans two years. qct_designation_change says "this year" / "prior year", so it is
emitted only when the two vintages are consecutive; across a gap it is null and
qct_designation_change_note says why, with qct_comparison_years naming the two
vintages actually compared. in_qct and in_qct_prior_year remain true statements
about their own stated vintage, and the boost verdict never depended on the comparison.
2. v1.0 asserted "no boost" without ever checking DDA
lihtc_130_basis_boost_eligible mirrored in_qct alone. But the same 130% boost
applies inside a Difficult Development Area, and the old code's claim that DDA "is not
a point-queryable FeatureServer" is simply wrong - HUD publishes it as polygons.
Worked example, and it was the Actor's own control coordinate: Beverly Hills, CA
(34.073, -118.400) sits inside the Small-Area DDA "Los Angeles-Long
Beach-Glendale, CA HUD Metro FMR Area", ZCTA 90210. v1.0 reported
lihtc_130_basis_boost_eligible: false. v1.1 reports true with
basis_boost_basis: "dda".
3. A failed lookup can no longer read as "not eligible"
v1.0 initialised in_qct and lihtc_130_basis_boost_eligible to false in the base
row. Every derived flag now starts null: null means not checked, false means
checked and negative. Each row carries qct_status, qct_prior_status and
dda_status, the boost flag is gated so it can never read false while a contributing
source is unavailable, and if every source fails for every asset the run fails and
bills nothing. The billing gate runs before any row is pushed.
4. Live drift assertions run before any billable row
Both QCT layers store Web Mercator, so a dropped inSR=4326 makes the service answer
HTTP 200 with zero features - a false clear. Every run now proves: each live layer
name is still recognisably the right layer; the designation vintage can be established
from the layer name or the designated-feature fingerprint, and the two agree where both
are available; the "current" layer is not older than the earlier one; national
feature counts sit inside a measured band; DDA_TYPE contains only the closed
vocabulary SA / NM; a positive QCT canary (South Bronx NY must resolve to tract
36005007900); a positive DDA canary (Beverly Hills must resolve to a Los Angeles DDA);
and a negative control (an open-Pacific point must return exactly 0 from all three
layers).
Who this is for
- LIHTC developers and syndicators pre-qualifying a pipeline of sites for the 130% eligible-basis boost before ordering an appraisal or a market study.
- Affordable-housing consultants and tax counsel who need the vintage on the record, because allocation and placed-in-service timing determine which year's designation applies.
- State housing finance agencies and CDFIs batch-scoring applications.
- Real-estate and site-selection teams layering the boost onto other constraints.
- AI agents doing site due diligence - see the MCP note below.
Example input
{"assets": [{ "lat": 40.815, "lon": -73.904, "label": "South Bronx, NY (Qualified Census Tract)" },{ "lat": 34.073, "lon": -118.4, "label": "Beverly Hills, CA (Small Area DDA - boost via DDA, not QCT)" },{ "lat": 39.07095, "lon": -108.55684, "label": "Grand Junction, CO (QCT in the prior year, not the current one)" },{ "lat": 39.0, "lon": -101.5, "label": "Rural Kansas (genuine clear - neither QCT nor DDA)" }],"maxAssets": 500}
That is the prefilled input. Measured against live HUD on 2026-10-24 it returns four
rows, with qct_year: FY2027 (basis data_fingerprint) and qct_prior_year: FY2025:
| label | boost eligible | basis | in_qct (FY2027) | earlier (FY2025) | change | DDA |
|---|---|---|---|---|---|---|
| South Bronx, NY | true | qct | true (36005007900) | true (36005007900) | null (2-year span) | - |
| Beverly Hills, CA | true | dda | false | false | null (2-year span) | Los Angeles-Long Beach-Glendale, CA HUD Metro FMR Area (SA, ZCTA 90210) |
| Grand Junction, CO | false | neither_qct_nor_dda | false | true (08077000200) | null (2-year span) | - |
| Rural Kansas | false | neither_qct_nor_dda | false | false | null (2-year span) | - |
Rows two and three are exactly the answers v1.0 got wrong. qct_designation_change is
null on all four because HUD overwrote the FY2026 service with FY2027, so the only
earlier vintage it still publishes keyless is FY2025 — two years back, not one. Each row
carries qct_comparison_years: "FY2027 vs FY2025" and a
qct_designation_change_note saying so. The boost verdict is unaffected: it never
depended on the comparison.
Input fields
| Field | Type | Required | Description |
|---|---|---|---|
assets | array | yes | Points to screen. Each item: lat, lon (WGS84 decimal degrees), optional label. One billable row per asset. |
maxAssets | integer | no | Safety cap on how many assets are screened. Default 500, max 5,000. |
Omitting assets returns HTTP 400 at run creation - the run never starts and
nothing is billed.
Output fields
One row per screened asset. Every one of the 45 declared fields is listed here.
null on a flag always means the backing source did not answer, never the answer is no.
| Field | Type | Description |
|---|---|---|
asset_label | string | null |
asset_lat | number | null |
asset_lon | number | null |
in_qct | boolean | null |
tract_geoid | string | null |
state | string | null |
county_fips | string | null |
tract | string | null |
qct_year | string | null |
qct_year_basis | string | null |
lihtc_130_basis_boost_eligible | boolean | null |
error | string | null |
source_url | string | null |
checked_at | string | null |
qct_status | string | null |
qct_error | string | null |
qct_prior_status | string | null |
qct_prior_error | string | null |
dda_status | string | null |
dda_error | string | null |
sources_ok_count | integer | null |
sources_total_count | integer | null |
partial_result | boolean | null |
qct_layer_name | string | null |
qct_prior_year | string | null |
qct_prior_year_basis | string | null |
qct_prior_layer_name | string | null |
dda_year | string | null |
dda_year_basis | string | null |
dda_layer_name | string | null |
in_qct_prior_year | boolean | null |
tract_geoid_prior_year | string | null |
qct_designation_change | string | null |
qct_comparison_years | string | null |
qct_designation_change_note | string | null |
state_fips | string | null |
tract_name | string | null |
in_dda | boolean | null |
dda_count | integer | null |
dda_type | string | null |
dda_type_label | string | null |
dda_name | string | null |
dda_code | string | null |
dda_zcta5 | string | null |
basis_boost_basis | string | null |
basis_boost_statutory_citation | string | null |
disclaimer | string | null |
source_url_qct_prior | string | null |
source_url_dda | string | null |
actor_version | string | null |
Fields that are legitimately null on a healthy run
A dead-column audit over the FULL dataset of a live run with the schema prefill
(measured 2026-10-24) leaves exactly five fields null: qct_error,
qct_prior_error, dda_error, error and qct_designation_change. The first four
being null is good news - it means every source answered; each is pinned by a
fault-injection fixture in offline.test.js and populates on any run during an upstream
outage. qct_designation_change is null for the structural reason above (HUD's two
keyless QCT vintages are currently two years apart, so the adjacent-year vocabulary
would be a false claim); qct_designation_change_note carries the reason on every row,
and the field populates again the moment HUD publishes two consecutive vintages.
dda_* detail fields are null when the site is not in a DDA, and tract_geoid is null
when it is not in a current-year QCT; the prefill deliberately includes one of each so
every column populates on an auto-QA run.
Use from an AI agent: quickstart
The smallest input that returns a real answer. Verified live on 2026-10-05: the run SUCCEEDED and returned 1 row in about 25 seconds.
{"assets": [{"lat": 40.815,"lon": -73.904,"label": "South Bronx NY"}]}
Call it through the hosted MCP server (https://mcp.apify.com?tools=malonestar/hud-qct-lihtc-boost-screener) or straight over HTTP:
POST https://api.apify.com/v2/acts/malonestar~hud-qct-lihtc-boost-screener/run-sync-get-dataset-itemsAuthorization: Bearer YOUR_APIFY_TOKENContent-Type: application/json
with the JSON above as the body. The response is a JSON array of dataset rows.
What comes back: exactly one row per site. Abbreviated first row from that run (the full row has 50 fields, all described in the output schema):
{"asset_label": "South Bronx NY","in_qct": true,"tract_geoid": "36005007900","qct_year": "FY2027","qct_year_basis": "data_fingerprint","in_dda": false,"dda_year": "FY2027","lihtc_130_basis_boost_eligible": true,"basis_boost_basis": "qct","qct_status": "ok","dda_status": "ok"}
Read before you conclude anything: qct_status and dda_status (ok means verified; anything else means unanswered, and the eligibility field is then not a negative). And read qct_year / dda_year — the designation changes every year and the service URL does not tell you which one you got.
Input notes for agents
assetsis an array of{ "lat": number, "lon": number, "label": "optional" }.- The boost can come from a QCT or a DDA;
basis_boost_basissays which.
Cost: $0.0060 per row at the Free-tier list price (paid plans get a discount), so the example above costs about $0.006. A run that cannot answer fails and bills no rows.
Use as an MCP tool
This Actor is available to AI agents through mcp.apify.com. It is a good chainable
tool because it takes plain coordinates and returns a flat, self-describing row:
lihtc_130_basis_boost_eligible for the yes/no, basis_boost_basis for which
designation carried it, qct_year / dda_year for the vintage, and qct_status /
dda_status so an agent can tell a verified negative from an unanswered one.
Pricing
$6 per 1,000 results (pay-per-event, one Result event per screened asset). Graduated discounts apply on paid Apify plans. There are no other charge events.
Billing honesty: if every asset fails against every source the run fails and emits nothing, so you are not billed for an outage.
FAQ
Does lihtc_130_basis_boost_eligible: false mean there is definitely no boost?
No. It means the site is in neither a current-year QCT nor a current-year DDA. State
allocating agencies may still grant a discretionary basis boost under
IRC Sec. 42(d)(5)(B)(v) for buildings that need it for financial feasibility, and that
is a QAP decision this screen cannot see.
Which year applies to my deal? That depends on allocation and placed-in-service
timing, and it is a question for your tax counsel. This Actor gives you both vintages it
can reach - in_qct with qct_year, and in_qct_prior_year with qct_prior_year -
plus qct_comparison_years naming the pair, and qct_designation_change when the two
are consecutive years.
Why does a site show in_qct: false but in_qct_prior_year: true? Because HUD
re-designates QCTs annually against updated income and poverty data. Colorado went 258
QCTs in 2025 -> 224 in 2026 -> 217 in 2027.
Why is qct_designation_change null right now? Because HUD published FY2027 by
overwriting its FY2026 service on 2026-10-23, so the only earlier vintage it still
publishes keyless is FY2025. That is a two-year span, and this field's vocabulary says
"this year" / "prior year" - reporting dropped_since_prior_year across the gap would
assert a change in FY2026, a year the run never looked at. qct_designation_change_note
says so on every row, and the field starts reporting again as soon as HUD publishes two
consecutive vintages. If you need the FY2026 designation, HUD still publishes it as a
file at huduser.gov/portal/datasets/qct/QCT2026CSV.zip.
Can I trust qct_year? It is established from the data, not from the URL or the
layer name, and qct_year_basis tells you how. HUD republishes these services in place
and has stripped the year from the layer names, so a URL containing _2026 is not
evidence of anything. If the vintage cannot be established, or the layer name and the
data disagree about it, the run fails rather than emitting a guessed year.
What is the difference between an SA and an NM DDA? SA is a Small-Area
(metropolitan) DDA, designated by ZIP Code Tabulation Area - dda_zcta5 is populated.
NM is a Non-Metropolitan DDA, designated by county - dda_zcta5 is null there, which
is the source's own convention and not a missing value.
Why did my run fail with "LIVE DRIFT CHECK FAILED"? Because HUD's services no
longer match what this Actor was verified against - a renamed or re-vintaged layer, a
new DDA_TYPE code, or a canary that stopped returning known-good data. A loud failure
is the correct answer: the alternative is a plausible tax screen computed from a source
that has silently changed. Nothing is billed on a failed run.
Is this tax advice? No. It is an informational screen against public HUD data.
Confirm against HUD's official QCT/DDA datasets at
huduser.gov/portal/datasets/qct.html and with your tax-credit counsel.
Related Actors
- HUD Affordable Housing Explorer - LIHTC, multifamily and public-housing inventory, plus Opportunity Zone point-in-polygon.
- CDFI NMTC Low-Income & Energy Bonus Screener - the NMTC and IRA energy-community bonus-credit designations for the same coordinate.
- FEMA NRI County Risk Profile and Site Due Diligence Bundle - the physical constraints that sit alongside the financial ones.
Informational screening tool built on public HUD data. Not tax advice, and not a HUD
or IRS determination. Designations are re-published annually - always check the
qct_year and dda_year on the row. Confirm with HUD and your tax-credit counsel
before relying on the 130% basis boost.