# Historical Topographic Map Chronology - ASTM Phase I ESA 8.3.4 (`malonestar/usgs-historical-topo-records-review`) Actor

Builds the full USGS historical topographic map chronology for any coordinate and runs an ASTM E1527-21 Sec. 8.3.4 historical records review: five-year interval coverage back to 1940, with data gaps explicitly identified. Every map year, scale, quadrangle and download URL. Keyless USGS data.

- **URL**: https://apify.com/malonestar/usgs-historical-topo-records-review.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Real estate, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 58.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.50 / 1,000 results

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

## Historical Topographic Map Chronology — ASTM Phase I ESA §8.3.4

**Give it a coordinate. Get back every USGS topographic map ever published over that
point, year by year — plus an ASTM E1527-21 §8.3.4 historical records review that shows
which five-year intervals back to 1940 are covered and which are gaps.**

Keyless USGS data (The National Map Access API). One billable row per property, no matter
how many maps cover it.

***

### Who this is for

- **Phase I ESA consultants / environmental professionals.** §8.3.4 requires historical
  use information reviewed at approximately five-year intervals back to 1940 (or the
  property's obvious first developed use, whichever is earlier), with data gaps
  identified. This actor does the topographic-map leg of that review and hands you the
  interval table, the gap list, and a direct download URL for every sheet.
- **EDR-report reviewers and anyone pricing one.** The same chronology a commercial
  historical package charges per-site for, as a structured row you can diff, join and
  archive.
- **Due-diligence and land teams** screening a portfolio: run 250 parcels in one call and
  sort by `astm_max_years_between_maps` to see which sites will need extra historical
  research before anyone drives out.
- **Title, mining, water-rights and litigation researchers** who need to know exactly
  which map editions exist for a location and where to download them.
- **AI agents.** Every field is described in the output schema, so an agent can chain this
  into a wider due-diligence workflow without guessing.

### What it actually does

1. Queries USGS **Historical Topographic Maps** (186,062 products nationally, ~1884 to the
   1990s) for the maps whose own footprint covers your point.
2. Queries the modern **US Topo** series (2009–present) so the chronology reaches the
   present instead of stopping in the 1990s. Optional, on by default, reported separately.
3. Rolls the products into a **year-by-year chronology**, de-duplicated into real map
   editions, with the largest-scale (most detailed) sheet identified per year.
4. Runs the **ASTM §8.3.4 interval review**: buckets the window into five-year intervals,
   marks each covered or uncovered, and reports the longest gap, the largest span between
   consecutive maps, and whether coverage reaches the review start year.

#### Worked example — Denver, CO (39.7392, −104.9903)

32 map products covering the point, which de-duplicate to **18 distinct editions across
17 years**: 1890, 1935, 1940, 1944, 1950, 1953, 1957, 1958, 1960, 1965, 1981, 1997 from the
historical collection, then 2010, 2013, 2016, 2019, 2022 from US Topo.

Reviewed 1940–2026 at five-year intervals: **10 of 18 intervals covered (55.6%)**, longest
uncovered stretch **1970–1979**, largest span between consecutive maps **16 years**
(1965→1981). Pre-1940 coverage exists back to **1890**. The most detailed sheet available
is 1:24,000.

That 1970s gap is the actionable output: it is where the consultant knows to pull aerials
or Sanborns rather than assume nothing happened.

### What this actor does NOT do — read this before you rely on it

- **It does not review aerial photography.** USDA NAIP returns `total: 0` for every point
  tested through the TNM Access API, so there is no keyless aerial leg here and this actor
  does not pretend otherwise. Every row carries
  `aerial_photography_status: "not_covered_by_this_actor"` — a status string, deliberately
  not a boolean, because `false` would be a claim about something never checked.
- **It is a screening tool, not a determination.** ASTM §8.3.4 expects the standard
  historical sources *as a set* — aerials, fire insurance maps, city directories, land
  title, zoning. An interval reported uncovered here is a candidate data gap **for the
  topographic-map source only** and may well be filled by another source. An environmental
  professional makes the determination; this gives them the map leg of the evidence.
- **`null` never means "no".** On this actor `null` means *not checked* and `false` means
  *checked and negative*. If a source did not answer, every ASTM verdict field is `null`,
  the `observed_*` fields still report what the run did see, and `astm_analysis_status`
  reads `incomplete_source_failure`. A "no data gap" answer computed while a source was
  down is the one wrong answer in this product that carries professional liability, so it
  is never emitted.
- **If the USGS source fails for every property, the run FAILS and bills nothing.** It does
  not emit a row per site saying "no maps found".

### Example input

```json
{
  "assets": [
    { "lat": 39.7392, "lon": -104.9903, "label": "Denver CO - downtown redevelopment parcel" },
    { "lat": 29.7604, "lon": -95.3698,  "label": "Houston TX - Ship Channel industrial site" },
    { "lat": 38.9296, "lon": -116.9312, "label": "Nye County NV - rural parcel" }
  ],
  "bufferMiles": 0,
  "includeUsTopo": true,
  "reviewStartYear": 1940,
  "intervalYears": 5,
  "maxMapsListed": 200
}
```

`bufferMiles: 0` asks USGS for the maps whose footprint **covers** the property — a true
point-in-quadrangle test, and the defensible answer for §8.3.4. A value above 0 widens the
search to any sheet intersecting a box around the point, which pulls in adjoining
quadrangles and changes what the coverage counts mean; the value used is reported on every
row in `buffer_miles` and `query_geometry`.

Set `reviewStartYear` earlier than 1940 when the property has an obvious first developed
use before then — §8.3.4 asks for 1940 *or* first developed use, whichever is earlier. Map
years found before the window are always reported anyway, in
`pre_review_window_map_years`.

### Input reference

| Input | Default | What it does |
|---|---|---|
| `assets` | *(required)* | Array of `{lat, lon, label}`. One billable row each. |
| `bufferMiles` | `0` | 0 = maps covering the point. Above 0 widens to an intersecting box. |
| `includeUsTopo` | `true` | Adds the modern 2009+ US Topo series. Does not change billing. |
| `reviewStartYear` | `1940` | First year of the ASTM review window. |
| `reviewEndYear` | current year | Last year of the review window. |
| `intervalYears` | `5` | Interval length. §8.3.4 calls for approximately five years. |
| `maxMapsListed` | `200` | Caps the per-row `maps` inventory. Counts and chronology are always complete. |
| `maxAssets` | `250` | Safety cap on sites per run. |
| `verifyUpstream` | `true` | Runs the live drift check before touching your sites. |
| `simulateOutage` | `none` | Diagnostic: forces a source lookup, or one upstream-verification probe, to fail so you can prove the failure paths yourself. |

### How it protects you from a silently wrong answer

USGS answers **HTTP 200 with `total: 0`, `items: []` and `errors: []`** when you send a
collection tag it does not recognise — byte-identical in shape to a genuine "no maps cover
this property". If USGS ever renamed the historical collection, a naive actor would
confidently report no historical coverage **for every property in the United States**, and
every run would be green.

So before a single billable row exists, each run:

- confirms USGS still publishes both collection tags in its own dataset registry;
- probes a **positive canary** in Denver that must return at least 10 products, at least 6
  distinct years, coverage back past 1910 and forward past 1980, with the map scale and
  quadrangle name still parseable from at least 90% of titles;
- probes a **US Topo canary** that must return a map published 2015 or later;
- probes a **negative control** in the mid-Pacific that must return **exactly zero** — the
  check the positive canary cannot do, because an unfiltered response would still clear a
  floor test;
- post-validates the query USGS echoes back (collection tag and point coordinates) *and*
  the rows it returned (every product's own bounding box must contain your point).

Every measured value is logged on every run, pass or fail. If any check **measures a
wrong answer**, the run fails with a message naming the cause, and nothing is emitted or
billed.

**A probe that could not be reached is not the same as a probe that failed.** Each probe
has its own error boundary and its own recorded outcome - `pass`, `fail` (it completed
and a band did not hold) or `unavailable` (it never answered: a gateway timeout, a
transport error). A `fail` on any probe stops the run. An `unavailable` stops the run
only when it hits a **load-bearing** check - the collection-tag registry, or every
positive canary - because without those nothing about the collection can be confirmed.
If only the corroborating negative control cannot be reached, the run proceeds and says
so on every row (`drift_gate_status: verified_degraded`), rather than reporting a USGS
outage as a finding about your property. The catastrophic case that control guards
against - a spatial filter dropped entirely - is caught independently by the positive
canary's 200-product ceiling, since an unfiltered response is the whole 186,062-product
national collection.

It also reads USGS's own `total` **first** and pages against it. The API silently clamps a
page request at 1000 results with a message that reads like success, so "I asked for 2000
and got 1000" looks complete unless you compare. A read that falls short is reported as
`partial`, never published as a whole answer.

### Output fields

One row per property. Every field below is emitted on every row; fields that could not be
determined are `null`, which means *not checked* — never *no*.

| Field | Type | Description |
|---|---|---|
| `label` | string | Your label for the property, echoed back so rows can be joined to your portfolio. |
| `latitude` | number | Latitude of the property as supplied, WGS84 decimal degrees. |
| `longitude` | number | Longitude of the property as supplied, WGS84 decimal degrees. |
| `checked_at` | string | UTC timestamp when this run queried USGS. |
| `buffer_miles` | number | Search buffer applied. 0 means a true point-in-quadrangle test. |
| `query_geometry` | string | point\_intersects when buffer\_miles is 0 (maps whose footprint covers the property), bbox\_intersects otherwise. |
| `query_bbox` | string | The exact bbox sent to the USGS TNM Access API, as minLon,minLat,maxLon,maxLat. |
| `aerial_photography_status` | string | Always not\_covered\_by\_this\_actor. This is a STATUS, deliberately not a boolean: reporting false would be a checked-and-negative claim about a source this run never looked at. |
| `aerial_photography_note` | string | Why aerial photography is out of scope and what that means for your ASTM 8.3.4 review. |
| `scope_note` | string | Plain statement that this is a screening tool covering the USGS topographic-map leg only. |
| `data_source` | string | Name of the upstream service. |
| `data_source_url` | string | Endpoint queried. |
| `drift_gate_status` | string | `verified` when every USGS verification probe completed and passed before your sites were touched. `verified_degraded` when the load-bearing probes passed but a corroborating probe could not be reached and was not run. `skipped` when `verifyUpstream` was off. A run whose load-bearing probes fail emits no rows at all. |
| `drift_negative_control_status` | string | `pass` when the mid-Pacific control returned exactly zero, proving USGS applied the spatial filter. `unavailable` means the probe was attempted and USGS never answered - it does NOT mean the control failed. `skipped` means it was not run. Deliberately not a boolean: not-checked and checked-and-negative are different facts. |
| `drift_probes_verified` | array | Names of the verification probes that completed and passed on this run. null when the check was skipped. |
| `drift_probes_unavailable` | array | Names of probes that were attempted but never answered. Empty array means every probe completed. null when the check was skipped. |
| `drift_gate_note` | string | Plain-language statement of exactly what was and was not confirmed about the USGS collection before your sites were screened. |
| `error` | string | Populated only when the property could not be reviewed at all. null on a successful row. |
| `historical_topo_status` | string | ok, partial, unavailable or not\_requested for the Historical Topographic Maps collection. unavailable means NOT CHECKED - it never means no maps exist. |
| `historical_topo_error` | string | Upstream error text when the historical collection did not answer. |
| `historical_topo_maps_total_reported` | integer | The `total` USGS itself reported for this query. Read first and paged against, because the API silently clamps a page request at 1000. |
| `historical_topo_maps_retrieved` | integer | How many historical products this run actually pulled down. |
| `historical_topo_complete` | boolean | true only when retrieved equals the total USGS reported. false means the chronology below is built on a partial read. |
| `historical_topo_incomplete_reason` | string | Why the historical read fell short of the reported total. |
| `historical_topo_echo_verified` | boolean | true when USGS echoed back the exact collection tag and point this run asked for. Guards against a filter that is accepted, echoed and never applied. |
| `historical_topo_rows_verified` | boolean | true when the returned products own bounding boxes actually contain the property. |
| `us_topo_status` | string | ok, partial, unavailable or not\_requested for the modern US Topo series (2009-present). |
| `us_topo_error` | string | Upstream error text when US Topo did not answer. |
| `us_topo_maps_total_reported` | integer | The `total` USGS reported for the US Topo query at this point. |
| `us_topo_maps_retrieved` | integer | How many US Topo products this run actually pulled down. |
| `us_topo_complete` | boolean | true only when retrieved equals the total USGS reported. |
| `us_topo_incomplete_reason` | string | Why the US Topo read fell short of the reported total. |
| `us_topo_echo_verified` | boolean | true when USGS echoed back the exact collection tag and point that was asked for. |
| `us_topo_rows_verified` | boolean | true when the returned US Topo products own bounding boxes contain the property. |
| `sources_requested` | array | Which USGS collections this run was asked to query. |
| `sources_ok` | array | Which collections actually answered for this property. |
| `sources_failed` | array | Which requested collections did not answer. Non-empty means every ASTM verdict field on this row is null. |
| `maps_total_products` | integer | Total USGS map PRODUCTS covering the property across all queried collections. USGS scans multiple printings of the same sheet, so this is larger than the number of distinct maps - nine separate 1965 Englewood CO products cover the Denver test point. |
| `maps_distinct_editions` | integer | Distinct collection + quadrangle + year + scale combinations. The de-duplicated count of actual map sheets. |
| `maps_distinct_years` | integer | Number of distinct years for which at least one map covers the property. This is the number an ASTM 8.3.4 interval review turns on. |
| `map_years` | array | Every distinct year with coverage, ascending. |
| `earliest_map_year` | integer | Oldest year with coverage, across all queried collections. |
| `latest_map_year` | integer | Most recent year with coverage, across all queried collections. |
| `coverage_span_years` | integer | Latest map year minus earliest map year. |
| `largest_scale_denominator_any` | integer | Smallest scale denominator found, i.e. the most detailed map covering the property. 24000 means a 1:24,000 quadrangle. |
| `quad_names` | array | Every USGS quadrangle name covering the property. More than one is normal - names change between editions and scales. |
| `state_codes` | array | State abbreviations parsed from the covering map titles. |
| `chronology` | array | One entry per map year: map\_year, product\_count, edition\_count, source\_datasets, quad\_names, scale\_denominators, map\_extents, and the largest-scale sheet for that year with its title, download URL and preview image URL. |
| `maps` | array | Full per-product inventory: source\_dataset, source\_id, map\_year, publication\_date, publication\_date\_raw, publication\_date\_is\_year\_only, title\_year, year\_cross\_check, title, quad\_name, state\_code, scale\_denominator, map\_extent, format, size\_bytes, download\_url, geotiff\_url, preview\_url, metadata\_url, vendor\_metadata\_url, last\_updated and the product bounding box. |
| `maps_listed` | integer | How many products the maps array actually contains, after the maxMapsListed cap. |
| `maps_truncated_in_row` | boolean | true when maps\_listed is fewer than maps\_total\_products. Counts and the chronology are always computed over the complete result regardless. |
| `year_cross_check_disagreements` | integer | How many products had a year in their title that disagreed with their publicationDate. Measured 0 across 6,079 live products; anything above 0 is a drift signal worth inspecting. |
| `astm_analysis_status` | string | complete when every requested source answered in full, otherwise incomplete\_source\_failure - in which case every astm\_\* verdict below is null. |
| `astm_analysis_note` | string | Why the analysis is incomplete, when it is. null on a complete run. |
| `astm_interval_years` | integer | Length of each coverage interval. ASTM E1527-21 Sec. 8.3.4 calls for approximately five years. |
| `astm_review_start_year` | integer | First year of the review window. 1940 is the standard baseline. |
| `astm_review_end_year` | integer | Last year of the review window. |
| `astm_intervals_total` | integer | How many intervals the review window contains. |
| `astm_intervals_covered` | integer | Intervals with at least one covering map. null when a source did not answer. |
| `astm_intervals_uncovered` | integer | Intervals with no covering map - candidate data gaps for this source. null when a source did not answer. |
| `astm_coverage_pct` | number | Covered intervals as a percentage of the window. null when a source did not answer. |
| `astm_uncovered_intervals` | array | Labels of the intervals with no covering topographic map. null when a source did not answer. |
| `astm_longest_uncovered_run_intervals` | integer | Longest stretch of consecutive uncovered intervals. null when a source did not answer. |
| `astm_longest_uncovered_span` | string | That stretch expressed as a year range. null when there is none, or when a source did not answer. |
| `astm_max_years_between_maps` | integer | Largest number of years between consecutive covering maps inside the window, including the run-up from the review start and the run-out to the review end. This is the number a reviewer quotes when justifying a data gap. null when a source did not answer. |
| `astm_data_gap_present` | boolean | true when at least one interval in the window has no covering topographic map. NULL means not determined - a requested source did not answer, and null is never the same as false. |
| `astm_back_to_review_start_satisfied` | boolean | true when the earliest covering map falls within one interval of the review start year. null when a source did not answer. |
| `astm_interval_detail` | array | One entry per interval: interval\_start\_year, interval\_end\_year, label, covered (null when undetermined), observed\_covered, map\_years, product\_count and source\_datasets. |
| `observed_intervals_covered` | integer | Intervals covered by what this run actually saw. Populated even when the analysis is incomplete, so evidence is never withheld - but it is not a verdict. |
| `observed_intervals_uncovered` | integer | Intervals not covered by what this run actually saw. Not a verdict when the analysis is incomplete. |
| `observed_uncovered_intervals` | array | Labels of intervals not covered by what this run saw. |
| `observed_map_years_in_window` | array | Map years found inside the review window. |
| `pre_review_window_map_years` | array | Map years found EARLIER than the review start year. Relevant because ASTM 8.3.4 asks for coverage back to first developed use when that predates 1940. |
| `pre_review_window_coverage` | boolean | true when at least one map predates the review start year. null when a source did not answer. |
| `earliest_map_year_any` | integer | Earliest covering map year including those before the review window. |
| `latest_map_year_any` | integer | Latest covering map year including those after the review window. |

#### Nested array shapes

`chronology[]` — one entry per map year:
`map_year`, `product_count`, `edition_count`, `source_datasets[]`, `quad_names[]`,
`scale_denominators[]`, `map_extents[]`, `largest_scale_denominator`,
`largest_scale_title`, `largest_scale_download_url`, `largest_scale_preview_url`.

`astm_interval_detail[]` — one entry per interval:
`interval_start_year`, `interval_end_year`, `label`, `covered` (`null` when undetermined),
`observed_covered`, `map_years[]`, `product_count`, `source_datasets[]`.

`maps[]` — one entry per map product:
`source_dataset`, `source_id`, `map_year`, `publication_date`, `publication_date_raw`,
`publication_date_is_year_only`, `title_year`, `year_cross_check`, `title`, `quad_name`,
`state_code`, `scale_denominator`, `map_extent`, `format`, `size_bytes`, `download_url`,
`geotiff_url`, `preview_url`, `metadata_url`, `vendor_metadata_url`, `last_updated`,
`bbox_min_lon`, `bbox_min_lat`, `bbox_max_lon`, `bbox_max_lat`.

#### Two notes on the data that will otherwise confuse you

**`publication_date` is `null` on historical maps and that is correct.** USGS stamps every
product in the historical collection `YYYY-01-01`. The year is real; the month and day are
placeholders. Publishing "January 1, 1965" would invent a printing date that does not
exist, so the year goes in `map_year`, the raw value stays in `publication_date_raw`, and
`publication_date` is only populated for US Topo, which carries a genuine full date.

**`maps_total_products` is roughly double `maps_distinct_editions`, by design.** USGS
scans multiple printings of the same sheet and publishes each as its own product — nine
separate 1965 Englewood, CO products cover the Denver test point. Counting products as
"maps covering this site" overstates the chronology about 2x. `maps_distinct_years` is the
number an ASTM interval review actually turns on.

### Use as an MCP tool

This actor is callable by AI agents through [mcp.apify.com](https://mcp.apify.com). Every
input and output field carries a description, so an agent can pass a coordinate and read
back `astm_data_gap_present`, `astm_uncovered_intervals` and `astm_max_years_between_maps`
without guessing at semantics — and can tell "not determined" from "no gap", because the
first is `null` and the second is `false`.

### Pricing

**$10 per 1,000 results** — one result per property, regardless of how many maps cover it
or how many collections are queried. A failed run emits nothing and bills nothing.

### Related actors (same Phase I buyer)

- **[EPA Contaminated Site Screener](https://apify.com/malonestar/epa-contaminated-site-screener)**
  — Superfund/SEMS, RCRA CORRACTS/TSD, USTs, ECHO enforcement and institutional controls at
  ASTM E1527-21 search distances. The recognized-environmental-conditions leg to this
  actor's historical-records leg.
- **[Site Due Diligence Bundle](https://apify.com/malonestar/site-due-diligence-bundle)**
  — 20 federal and state layers for one coordinate in one call, with a composite site score
  and a go/caution/no-go verdict gated so it can never read "proceed" while a layer is
  unavailable.
- **[State Tank & Spill Registry Screener](https://apify.com/malonestar/state-tank-spill-registry-screener)**
  — the UST/LUST and spill-registry component of an EDR-equivalent bundle.

### FAQ

**How do I get historical topographic maps for an address?**
Geocode it to latitude/longitude, pass it in `assets`, and every USGS quadrangle covering
that point comes back with its year, scale and a direct download URL for the GeoPDF and
GeoTIFF.

**Does this satisfy ASTM E1527-21 §8.3.4?**
It does the topographic-map portion and identifies the interval gaps. §8.3.4 expects the
standard historical sources as a set, so aerials, fire insurance maps, city directories
and land title still need reviewing separately. An environmental professional makes the
determination.

**Why is a recent interval showing as uncovered at a well-mapped site?**
US Topo revises a quadrangle roughly every three years and the current cycle may not have
reached yours yet. That is a genuine absence of a published map, not a failure — check
`us_topo_status`, which will read `ok`.

**Why does one point return 27 maps but only 12 years?**
USGS scans several printings of the same sheet. See `maps_distinct_editions` and
`maps_distinct_years`.

**What happens if USGS is down?**
The run fails with a message naming the cause, emits no rows, and bills nothing. It never
returns a row saying no maps were found.

**Does it cover Alaska, Hawaii and the territories?**
Yes — the historical collection spans 45+ states plus territories (Puerto Rico quadrangles
go back to 1947). Coverage depth varies by location, which is exactly what the interval
review measures.

**Is there an aerial photography option?**
No. NAIP returns zero results through this API, so there is no keyless aerial leg and this
actor does not claim one.

### Data source

[USGS The National Map Access API](https://tnmaccess.nationalmap.gov/api/v1) —
`Historical Topographic Maps` and `US Topo` collections. Public domain, no key required.
Source characteristics were measured live 2026-08-04; the capture scripts and the drift
baselines ship with the actor.

# Actor input Schema

## `assets` (type: `array`):

Properties to build a topographic map chronology for. Each item is an object: {"lat": \<number -90..90>, "lon": \<number -180..180>, "label": "<your name for the site>"}. Each site produces exactly ONE billable dataset row no matter how many maps cover it. An invalid coordinate produces an ERROR row and the run continues; if the USGS source fails for EVERY site the run is failed so nothing is billed.

## `bufferMiles` (type: `integer`):

0 (recommended) asks USGS for the maps whose own footprint COVERS the property - a true point-in-quadrangle test, which is the defensible answer for an ASTM 8.3.4 review. A value above 0 widens the search to any map sheet intersecting a box of that half-width around the point, which brings in adjoining quadrangles and CHANGES what the coverage counts mean. The value used is reported on every row in buffer\_miles and query\_geometry.

## `includeUsTopo` (type: `boolean`):

ON by default. Adds the current USGS US Topo series so the chronology reaches the present instead of stopping in the 1990s where the historical collection ends. Does NOT change your billing - still one row per site. Its own coverage is reported separately in the us\_topo\_\* columns, and if it fails the ASTM verdict fields go null rather than reporting a gap that US Topo might have filled.

## `reviewStartYear` (type: `integer`):

First year of the ASTM E1527-21 Sec. 8.3.4 review window. 1940 is the standard baseline; set it earlier if the property has an obvious first developed use before 1940, since the standard requires going back to whichever is earlier. Map years found BEFORE this year are still reported, in pre\_review\_window\_map\_years.

## `reviewEndYear` (type: `integer`):

Last year of the review window. Leave unset to use the current year, which is what a present-day Phase I reviews back from. Note that the most recent interval is often uncovered even at a well-mapped site: US Topo revises a quadrangle roughly every three years and the current cycle may not have reached it yet.

## `intervalYears` (type: `integer`):

Length of each coverage interval. ASTM E1527-21 Sec. 8.3.4 calls for approximately five-year intervals, which is the default. Buckets are anchored at the review start year.

## `maxMapsListed` (type: `integer`):

Caps the size of the per-row `maps` inventory array. The year-by-year `chronology` and every count are ALWAYS computed over the complete result regardless of this cap, and maps\_truncated\_in\_row tells you when the inventory array was trimmed. Set 0 to omit the inventory entirely and keep only the chronology.

## `maxAssets` (type: `integer`):

Safety cap on how many sites are reviewed in one run. Sites beyond the cap are skipped with a warning naming how many were left out; they are NOT in the dataset and are not billed.

## `verifyUpstream` (type: `boolean`):

ON by default. Before any of your sites are touched, the run confirms USGS still publishes the two collection tags, probes a positive canary in Denver that must return decades of maps back to 1890, probes a US Topo canary, and probes a mid-Pacific negative control that must return exactly zero. If USGS renamed the collection or stopped applying the spatial filter, every site would otherwise report 'no maps cover this property' on a green run. Costs four extra requests.

## `simulateOutage` (type: `string`):

Diagnostic seam for proving the failure paths without waiting for a real outage. 'none' is normal operation. The three source options force a site lookup to fail through the same code path a genuine outage takes. The two drift options force an upstream-verification probe to time out: the negative-control option must still SUCCEED with drift\_gate\_status=verified\_degraded, and the load-bearing option must FAIL, emitting nothing and billing nothing. Leave on 'none' for real work.

## Actor input object example

```json
{
  "assets": [
    {
      "lat": 39.7392,
      "lon": -104.9903,
      "label": "Denver CO - downtown redevelopment parcel"
    },
    {
      "lat": 29.7604,
      "lon": -95.3698,
      "label": "Houston TX - Ship Channel industrial site"
    },
    {
      "lat": 38.9296,
      "lon": -116.9312,
      "label": "Nye County NV - rural parcel (sparse coverage)"
    }
  ],
  "includeUsTopo": true,
  "reviewStartYear": 1940,
  "intervalYears": 5,
  "maxMapsListed": 200,
  "maxAssets": 250,
  "verifyUpstream": true,
  "simulateOutage": "none"
}
```

# 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 = {
    "assets": [
        {
            "lat": 39.7392,
            "lon": -104.9903,
            "label": "Denver CO - downtown redevelopment parcel"
        },
        {
            "lat": 29.7604,
            "lon": -95.3698,
            "label": "Houston TX - Ship Channel industrial site"
        },
        {
            "lat": 38.9296,
            "lon": -116.9312,
            "label": "Nye County NV - rural parcel (sparse coverage)"
        }
    ],
    "bufferMiles": 0,
    "includeUsTopo": true,
    "reviewStartYear": 1940,
    "intervalYears": 5,
    "maxMapsListed": 200,
    "maxAssets": 250,
    "verifyUpstream": true,
    "simulateOutage": "none"
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/usgs-historical-topo-records-review").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 = {
    "assets": [
        {
            "lat": 39.7392,
            "lon": -104.9903,
            "label": "Denver CO - downtown redevelopment parcel",
        },
        {
            "lat": 29.7604,
            "lon": -95.3698,
            "label": "Houston TX - Ship Channel industrial site",
        },
        {
            "lat": 38.9296,
            "lon": -116.9312,
            "label": "Nye County NV - rural parcel (sparse coverage)",
        },
    ],
    "bufferMiles": 0,
    "includeUsTopo": True,
    "reviewStartYear": 1940,
    "intervalYears": 5,
    "maxMapsListed": 200,
    "maxAssets": 250,
    "verifyUpstream": True,
    "simulateOutage": "none",
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/usgs-historical-topo-records-review").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 '{
  "assets": [
    {
      "lat": 39.7392,
      "lon": -104.9903,
      "label": "Denver CO - downtown redevelopment parcel"
    },
    {
      "lat": 29.7604,
      "lon": -95.3698,
      "label": "Houston TX - Ship Channel industrial site"
    },
    {
      "lat": 38.9296,
      "lon": -116.9312,
      "label": "Nye County NV - rural parcel (sparse coverage)"
    }
  ],
  "bufferMiles": 0,
  "includeUsTopo": true,
  "reviewStartYear": 1940,
  "intervalYears": 5,
  "maxMapsListed": 200,
  "maxAssets": 250,
  "verifyUpstream": true,
  "simulateOutage": "none"
}' |
apify call malonestar/usgs-historical-topo-records-review --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,malonestar/usgs-historical-topo-records-review"
        }
    }
}

```

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/b1GrNQGgbn28LFeqn/builds/34zh5gcD6Or2r6k4P/openapi.json
