# MSHA Mine Data Scraper - Violations, Inspections, Accidents (`parseforge/msha-mine-data-scraper`) Actor

Streams all 20 MSHA Open Government Data files, 17.8 million rows on US mines, violations, inspections, accidents, penalties, production and health samples.

- **URL**: https://apify.com/parseforge/msha-mine-data-scraper.md
- **Developed by:** [ParseForge](https://apify.com/parseforge) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.23 / 1,000 mine records

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?

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

[![ParseForge](https://raw.githubusercontent.com/ParseForge/apify-assets/main/banner.jpg)](https://apify.com/parseforge?fpr=vmoqkp)

### MSHA Mine Data Scraper - Violations, Inspections & Accidents

**Scrape MSHA mine data from all 20 Open Government Data files: 17,856,581 rows covering every US mine since 1970 and every violation, inspection and accident since 2000.** Every row is a flat record with the source's own field names mapped to readable keys, dates normalised to ISO, and no empty columns. No login, no API key, no FOIA request. Export to CSV, JSON, Excel, or XML.

MSHA publishes this data as 582 MB of ZIP archives that you are expected to download whole, unzip, and load into a database before you can ask a single question. This Actor streams the archives and stops reading the moment it has your rows, so a 10-row request against the 1.4 GB violations file transfers a few kilobytes instead of 120 MB, and filters by mine, state, date, penalty or injury run while the file streams.

| Who uses it | What they scrape MSHA data for |
|---|---|
| Mine safety consultants | Violation and inspection history for a client's mine ID before an audit |
| Insurance underwriters | Injury rates, penalty history and employment size to price a mining risk |
| Industrial sales teams | Active mines by state, commodity and employee count with operator contacts |
| Journalists and researchers | Fatality records, narratives and enforcement outcomes as primary sources |
| Legal and compliance teams | Contested citations, FMSHRC dockets and civil penalty decisions |

### What it does

This Actor reads MSHA's Open Government Data archives and returns each record as a flat row. Pick one dataset or all twenty in a single run:

- ⛏️ **Mines:** 91,979 mines since 1970 with status, type, controller, operator, commodity, SIC, employee count and coordinates.
- 🚨 **Enforcement:** 3,096,400 violations, 3,029,093 assessed penalties, 451,718 contested citations, 481,753 civil penalty dockets, 162,065 conferences and 3,861 imminent danger orders.
- 🔍 **Inspections:** 1,156,532 inspection events with activity, inspector count, on-site hours and the areas covered.
- 🩹 **Accidents:** 274,448 injury and illness reports from MSHA form 7000-1, including the full free-text narrative.
- 📊 **Production:** 5,062,796 employment and production rows for mine operators and contractors, quarterly and yearly.
- 🧪 **Health samples:** 3,784,443 coal dust, quartz, area, personal health and noise samples.
- 🔗 **Optional joins:** add mine master details, operator contact addresses or the latest production summary to any row that carries a mine ID.

Results export to CSV, JSON, Excel, or XML, or stream from the API.

### What you can do with MSHA data

**🏭 Build a targeted list of active mines.** Filter 91,979 mines down to the ones that matter: active status, a given state, sand and gravel or stone, above a minimum employee count. Tick the operator contact block and every row carries the business name, contact title and street address MSHA has on file.

**⚖️ Reconstruct a mine's enforcement history.** Feed a mine ID and select violations, assessed violations, contested violations and penalty dockets in one run. You get the citation, its gravity and negligence findings, the penalty proposed, what was actually paid, whether it was contested and how the judge ruled.

**📉 Study injuries and fatalities.** The accidents dataset carries the degree of injury, the classification, the equipment involved, the miner's experience, days lost, and the inspector's narrative. Filter to fatalities only and you have all 1,210 fatal records with their stories.

**🧭 Track an operator across its mines.** Controller and operator history maps 169,809 controller-to-operator associations with start and end dates, so you can follow who owned which mine and when, then join production and violations onto the same mine IDs.

### Why choose this scraper

| | What you get |
|---|---|
| Full coverage | All 20 live MSHA data files, 17,856,581 rows, 562 source columns, nothing summarised away |
| Streamed, not downloaded | Reads the ZIP as it arrives and stops early, so small requests stay small |
| Filters that run upstream of your dataset | Mine ID, state, date, penalty, injury, negligence and 16 more, applied while the file streams |
| Clean rows | ISO dates, real numbers, Yes/No booleans, `"N/A"` where MSHA withheld a value, and zero always-empty columns |
| Joins MSHA makes you do yourself | Mine master, operator address and production summary attached by mine ID |
| Priced per row | You pay for rows written, not for the 582 MB the source makes you fetch |

### How it compares

Four other MSHA Actors exist on the Apify Store, and each covers one slice: a mine lookup, an operator lead list, or the assessed violations file. None of them reads all twenty files, and none joins the mine master onto the enforcement rows. This Actor costs more per row than the cheapest of them, and what the difference buys is coverage and the joins: one run can return mines, their violations, their inspections, their accidents and their production, with state and status filters working even on the files that carry neither column.

| | This Actor | Typical single-file MSHA Actor | Downloading the files yourself |
|---|---|---|---|
| Datasets covered | 20 | 1 to 2 | 20 |
| Rows reachable | 17,856,581 | Under 3.1 million | 17,856,581 |
| Filters | 22, applied while streaming | Usually a mine ID | Whatever you write |
| Bytes moved for 10 rows | A few KB | Varies | 582 MB, then unzip |
| Mine master join | Built in | No | Write it yourself |
| Setup | None | None | Download, unzip, parse, load |

### What an MSHA mine record looks like

One real row from a verified run, unedited:

```json
{
  "dataset": "mines",
  "mineId": "0100003",
  "mineName": "O'Neal Quarry & Mill",
  "coalOrMetal": "M",
  "mineType": "Surface",
  "mineStatus": "Active",
  "mineStatusDate": "1979-01-22",
  "controllerId": "0041044",
  "controllerName": "Lhoist Group",
  "operatorId": "L13586",
  "operatorName": "Lhoist North America of Alabama, LLC",
  "state": "AL",
  "bureauOfMinesStateCode": "01",
  "countyFipsCode": "117",
  "countyName": "Shelby",
  "congressionalDistrict": "06",
  "companyType": "Other",
  "controllerBeginDate": "1989-07-01",
  "district": "M3",
  "officeCode": "M3661",
  "officeName": "Birmingham AL Field Office (A)",
  "assessmentControlNo": "000638053",
  "primarySicCode": "327400",
  "primarySicDescription": "Lime",
  "primarySicCodeShort": "3274",
  "primarySicSuffix": "00",
  "secondarySicCode": "142200",
  "secondarySicDescription": "Crushed, Broken Limestone NEC",
  "secondarySicCodeShort": "1422",
  "secondarySicSuffix": "00",
  "primaryCommodityCode": "6",
  "primaryCommodity": "Stone",
  "secondaryCommodityCode": "6",
  "secondaryCommodity": "Stone",
  "status103i": "Never Had 103I Status",
  "status103iDate": "N/A",
  "portableOperation": "No",
  "portableStateFipsCode": "N/A",
  "daysPerWeek": 7,
  "hoursPerShift": 8,
  "productionShiftsPerDay": 3,
  "maintenanceShiftsPerDay": 0,
  "employeeCount": 108,
  "part48Training": "Yes",
  "longitude": -86.758889,
  "latitude": 33.177778,
  "avgMineHeightInches": "N/A",
  "gasCategoryCode": "N/A",
  "methaneLiberationCfd": "N/A",
  "producingPitCount": "N/A",
  "nonProducingPitCount": "N/A",
  "tailingPondCount": 0,
  "pillarRecoveryUsed": "No",
  "highwallMinerUsed": "No",
  "multiplePits": "No",
  "minersRepresentative": "No",
  "safetyCommittee": "No",
  "milesFromOffice": 100,
  "directionsToMine": "Approx 7 miles south of the city of Alabaster on Hwy 31.",
  "nearestTown": "Calera",
  "mineDataRetrievalUrl": "https://www.msha.gov/mine-data-retrieval-system?mineid=0100003",
  "scrapedAt": "2026-08-27T16:20:57.704Z"
}
```

Rows from the other 19 datasets carry that dataset's own columns instead. Violations, inspections, accidents and the sample files each have their own shape, and the `dataset` key tells you which one you are looking at.

### Configure the run

Pick datasets, set `maxItems`, and add filters. Filters run as each record is read, so only matches reach your dataset, and a filter naming a column the selected dataset does not have is ignored for that dataset. The Input tab lists every parameter.

Active sand and gravel mines in Texas, with the operator's mailing address:

```json
{ "datasets": ["mines"], "states": ["TX"], "mineStatuses": ["Active"], "commodities": ["SandAndGravel"], "includeOperatorContact": true, "maxItems": 500 }
```

Everything MSHA has on one mine, across four enforcement datasets:

```json
{ "datasets": ["violations", "assessedViolations", "contestedViolations", "penaltyDockets"], "mineIds": ["0100003"], "maxItems": 2000 }
```

The most recent fatal accidents in West Virginia, with the mine master joined on:

```json
{ "datasets": ["accidents"], "states": ["WV"], "fatalitiesOnly": true, "sortBy": "newestFirst", "includeMineDetails": true, "maxItems": 200 }
```

### Pricing

Pay-per-event: **$0.007 per row**, dropping to $0.00623 at higher volume, plus a $0.02 run-start fee and $0.02 for each dataset file opened. The three optional joins add $0.003 per row enriched, and only when the join actually resolved. You pay only for rows written to your dataset.

| Rows collected | Approximate cost |
|---|---|
| 100 | $0.74 |
| 1,000 | $7.04 |
| 10,000 | $70.04 |

Those figures are for one dataset with no optional blocks. Each extra dataset in the same run adds $0.02. At higher monthly volume the per-row rate drops to $0.00623, so 10,000 rows runs about $62.34. New Apify accounts start with $5 in free credit.

### Free users

Free-plan runs return up to 10 rows as a preview. [Upgrade your Apify plan](https://console.apify.com/sign-up?fpr=vmoqkp) to collect up to 1,000,000 rows per run.

### Run it

1. [Create a free Apify account with $5 in credit](https://console.apify.com/sign-up?fpr=vmoqkp).
2. Open the [MSHA Mine Data Scraper](https://apify.com/parseforge/msha-mine-data-scraper?fpr=vmoqkp).
3. Pick one or more `datasets`, set `maxItems`, add any filters, and click **Start**.
4. Export the results as CSV, Excel, JSON, or XML from the **Dataset** tab.

Run it programmatically through the [Apify API](https://docs.apify.com/api/v2) or the [ApifyClient](https://docs.apify.com/api/client/js) for JavaScript and Python.

### Use with AI agents (MCP)

Give an AI agent live access to MSHA data through the Model Context Protocol. Add the Actor to Claude, Cursor, or any MCP client:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=parseforge/msha-mine-data-scraper"
```

Then prompt it in plain language:

- *"List the active underground coal mines in West Virginia with more than 50 employees."*
- *"Pull every violation issued at mine ID 4600015 since 2024 and total the proposed penalties."*
- *"Find the most recent fatal mining accidents and summarise the narratives."*

### Troubleshooting

**No results at all.** Check that the filters you set exist on the dataset you picked. `mineIds` returns nothing on contractor production or conferences, because neither file carries a mine ID. `degreesOfInjury` only applies to accidents.

**Fewer rows than I asked for.** Either the filters matched fewer records than `maxItems`, or the run hit its time limit on a large file. Selecting several datasets splits `maxItems` between them unless you set `maxItemsPerDataset`. Free-plan runs stop at 10 rows.

**A sorted run returned fewer rows than an unsorted one.** Sorting has to read the whole file and hold the candidates, so it is capped by the run's memory and by a hard ceiling of 50,000 rows per dataset. The log says the cap it settled on. Narrow the filters, drop the optional joins, or use file order to get the full set.

**A field is empty.** MSHA leaves fields blank when it never recorded the value, and those arrive as `"N/A"`. Coordinates are present on 51.8% of mines and `sectionOfAct` on 0.6% of violations; that is the source, not the scraper. Columns that are empty in every single row upstream are dropped rather than shipped.

**The run is slow.** File order is the fast path and stops early. A sort, or a filter that matches rarely, forces a full read of files that reach 1.4 GB uncompressed: a full pass over all 3,096,400 violations takes a couple of minutes. Narrow the date range or the mine list to cut the work.

### FAQ

| Question | Answer |
|---|---|
| Do I need an MSHA account or API key? | No. These are public Open Government Data files served without any login or rate limit. |
| How fresh is the data? | MSHA refreshes the files every Friday afternoon. Each run reports the publication date it read. |
| Which datasets are included? | All 20 live ones: mines, violations, assessed and contested violations, inspections, accidents, penalty dockets, conferences, 107(a) orders, addresses, controller history, four production files and five sample files. |
| Can I get everything for one mine in a single run? | Yes. Select the datasets you want and set `mineIds`. Every dataset that carries a mine ID is filtered to it. |
| Can I filter violations by state? | Yes. The violations file has no state column, so the Actor resolves it through the mine master. That lookup is loaded once and is not billed. |
| What is the mine ID format? | Seven characters, zero padded, for example `0100003`. Shorter IDs are padded for you. |
| How many rows per run? | Free plan: 10. Paid: up to 1,000,000, bounded by what your filters match and the run's time limit. |
| Are the accident narratives included? | Yes, the full free-text narrative from MSHA form 7000-1 is in the `narrative` field of every accident row. |
| Why are some numeric fields `"N/A"`? | Because MSHA left them blank. The Actor never emits a literal `null`, so a withheld number arrives as `"N/A"` and a real one as a number. |
| Is this an official MSHA product? | No. It is unofficial and reads only publicly published MSHA data files. |

### Related actors

- [FMCSA Carrier Safety Scraper](https://apify.com/parseforge/fmcsa-carrier-safety-scraper?fpr=vmoqkp): federal motor carrier safety ratings and inspection history.
- [EPA ECHO Violations Scraper](https://apify.com/parseforge/epa-echo-violations-scraper?fpr=vmoqkp): environmental compliance and enforcement records for US facilities.
- [HHS OIG LEIE Exclusions Scraper](https://apify.com/parseforge/hhs-oig-leie-exclusions-scraper?fpr=vmoqkp): federal healthcare program exclusion lists.
- [SAM.gov Contract Opportunities Scraper](https://apify.com/parseforge/sam-gov-contracts-scraper?fpr=vmoqkp): US federal contract opportunities and awards.
- [Regulations.gov Scraper](https://apify.com/parseforge/regulations-gov-scraper?fpr=vmoqkp): federal dockets, rules and public comments.

Browse the full [ParseForge collection](https://apify.com/parseforge?fpr=vmoqkp) for more scrapers.

🆘 **Need help?** Email parseforge@protonmail.com with your run ID, your input, and what you expected.

⚠️ **Disclaimer.** This Actor is unofficial and is not affiliated with, endorsed by, or sponsored by the Mine Safety and Health Administration or the US Department of Labor. It collects only publicly published MSHA Open Government Data. You are responsible for using the data in compliance with applicable laws, including GDPR, CCPA, and PIPL. Accident records describe injuries to identifiable workers; do not use them to identify, profile, or target individuals.

# Actor input Schema

## `datasets` (type: `array`):

Which MSHA Open Government Data files to read. Each selected dataset is downloaded and streamed in turn, and every dataset bills its own event. Row counts are from the 2026-08-21 publication.

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

Free users: limited to 10 items (preview). Paid users: up to 1,000,000. This is the total across every selected dataset.

## `maxItemsPerDataset` (type: `integer`):

Cap for each selected dataset. Leave empty and the Max Items budget is split evenly across the datasets you selected.

## `mineIds` (type: `array`):

Seven digit MSHA mine IDs, for example 0100003. Shorter IDs are zero padded for you. Applies to every dataset that carries a mine ID; contractor production and conferences do not, so they return nothing when this is set.

## `companyIds` (type: `array`):

Controller, operator, contractor or violator IDs, for example 0041044 or L13586. A row matches when any of the dataset's company ID columns equals one of these.

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

Two letter state or territory. Datasets without a state column are matched through the mine master, which is loaded once and not billed.

## `coalOrMetal` (type: `string`):

Restrict to coal mines or to metal and nonmetal mines.

## `searchText` (type: `string`):

Case insensitive substring matched against the dataset's name columns: mine, operator, controller, contractor and violator names, plus the accident narrative. Datasets with no name column match their identifier columns instead.

## `dateFrom` (type: `string`):

Inclusive lower bound on the dataset's main date, in YYYY-MM-DD. On the four employment and production files, which only carry a calendar year, the year part is compared instead.

## `dateTo` (type: `string`):

Inclusive upper bound on the dataset's main date, in YYYY-MM-DD.

## `mineStatuses` (type: `array`):

Current mine status. Applies to the mines, addresses and controller history datasets directly, and to the rest through the mine master.

## `mineTypes` (type: `array`):

Underground, surface or facility.

## `commodities` (type: `array`):

Primary canvass commodity from the mine master.

## `subunits` (type: `array`):

Location or operation within the mine. Applies to accidents and to the four employment and production datasets.

## `documentTypes` (type: `array`):

Citation, order, safeguard or notice. Applies to the violations, assessed violations, contested violations and penalty docket datasets.

## `significantAndSubstantial` (type: `string`):

S\&S designation on the citation or order.

## `negligenceLevels` (type: `array`):

Inspector's negligence finding.

## `likelihoodLevels` (type: `array`):

Gravity likelihood recorded on the citation.

## `injuryIllnessLevels` (type: `array`):

Gravity injury or illness that could reasonably be expected.

## `minPenaltyUsd` (type: `number`):

Keep only rows whose proposed penalty is at least this much. Decimals are allowed.

## `maxPenaltyUsd` (type: `number`):

Keep only rows whose proposed penalty is at most this much. Decimals are allowed.

## `degreesOfInjury` (type: `array`):

Degree of injury recorded on MSHA form 7000-1.

## `accidentClassifications` (type: `array`):

MSHA accident classification, the 29 values that appear in the file.

## `fatalitiesOnly` (type: `boolean`):

Shortcut for degree of injury FATALITY. There are 1,210 fatal records in the file.

## `activityContains` (type: `string`):

Case insensitive substring of the inspection activity, for example "regular" or "103(i)". The file uses 86 activity spellings, so a substring beats an exact list. Applies to the inspections dataset only.

## `sortBy` (type: `string`):

File order streams and stops early, which is by far the fastest. Any other order has to read the whole file before it can answer, so it costs the full download of every selected dataset and is capped at 50,000 rows per dataset.

## `includeMineDetails` (type: `boolean`):

Join the mine master onto every row that carries a mine ID, adding mine name, type, status, state, county, commodity, SIC, controller, operator, employee count and coordinates. Billed per row actually enriched. Skipped on the mines dataset, which already has these columns.

## `includeOperatorContact` (type: `boolean`):

Join the mine addresses of record, adding business name, contact title, street, PO box, city, state, ZIP and country. Billed per row actually enriched.

## `includeProductionSummary` (type: `boolean`):

Join the latest reported calendar year from the yearly employment file, summed across subunits: hours, coal production in short tons and average employees. Billed per row actually enriched.

## `proxyConfiguration` (type: `object`):

MSHA serves these files without any bot protection, so no proxy is needed. Turn one on only if your own network cannot reach arlweb.msha.gov.

## Actor input object example

```json
{
  "datasets": [
    "mines"
  ],
  "maxItems": 10,
  "coalOrMetal": "any",
  "significantAndSubstantial": "any",
  "fatalitiesOnly": false,
  "sortBy": "fileOrder",
  "includeMineDetails": false,
  "includeOperatorContact": false,
  "includeProductionSummary": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `csv` (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 = {
    "datasets": [
        "mines"
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("parseforge/msha-mine-data-scraper").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 = {
    "datasets": ["mines"],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("parseforge/msha-mine-data-scraper").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 '{
  "datasets": [
    "mines"
  ],
  "maxItems": 10
}' |
apify call parseforge/msha-mine-data-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,parseforge/msha-mine-data-scraper"
        }
    }
}

```

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/eSDHLaHSOhvwRsFRH/builds/iHTq7M5OYv7gG2fZi/openapi.json
