# Powersports and E-Mobility Safety Monitor (`repairiq/powersports-safety-monitor`) Actor

Match new CPSC, NHTSA, and Federal Register safety and regulatory events to the brands, product categories, and keywords a customer actually sells - with evidence, severity, and a human-review flag.

- **URL**: https://apify.com/repairiq/powersports-safety-monitor.md
- **Developed by:** [Ryan Carter](https://apify.com/repairiq) (community)
- **Categories:** Automation, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $100.00 / 1,000 matched safety events

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Powersports and E-Mobility Safety Monitor

Match public agency events against supplied brands, models, categories, and keywords.

Uses CPSC and Federal Register sources, with optional NHTSA vehicle lookups. Source outages fail the run. A match requires review; an empty result is not a safety clearance.

### Quick start

Paste this input into the Actor, or save it as `input.json` for the API example. Inline records with example.com URLs are fictional demonstration data. Form prefill values are examples, not defaults for empty API requests.

```json
{
  "offline": true,
  "brands": [
    "Sur-Ron",
    "Talaria",
    "Segway"
  ],
  "productCategories": [
    "electric-dirt-bike",
    "battery"
  ],
  "keywords": [
    "thermal runaway",
    "fire hazard"
  ],
  "onlyNewEvents": false,
  "minimumMatchConfidence": 0.5,
  "stateStoreName": "safety-event-state-offline",
  "events": [
    {
      "eventId": "CPSC:demo-24-001",
      "eventType": "recall",
      "sourceAgency": "CPSC",
      "publishedAt": "2026-07-20T00:00:00Z",
      "title": "Example Brand recalls electric dirt bikes due to fire hazard",
      "summary": "The lithium-ion battery pack can overheat and enter thermal runaway, posing fire and burn injury hazards. Sold at specialty powersports dealers.",
      "hazard": "Fire and burn hazard",
      "remedy": "Stop use and contact the manufacturer for a free replacement battery",
      "statedBrands": [
        "Sur Ron",
        "Example Brand LLC"
      ],
      "statedModels": [
        "Sur Ron Light Bee X",
        "Model A"
      ],
      "sourceUrl": "https://www.cpsc.gov/Recalls/example",
      "effectiveDate": null,
      "commentDeadline": null,
      "raw": {}
    },
    {
      "eventId": "FR:demo-2026-14000",
      "eventType": "rule",
      "sourceAgency": "FederalRegister",
      "publishedAt": "2026-07-22T00:00:00Z",
      "title": "Safety Standard for Micromobility Products With Lithium-Ion Batteries",
      "summary": "The Commission proposes a mandatory safety standard addressing thermal runaway in lithium-ion battery systems used in micromobility products.",
      "hazard": null,
      "remedy": null,
      "statedBrands": [],
      "statedModels": [],
      "sourceUrl": "https://www.federalregister.gov/documents/example",
      "effectiveDate": null,
      "commentDeadline": "2026-09-15",
      "raw": {}
    },
    {
      "eventId": "CPSC:demo-24-002",
      "eventType": "recall",
      "sourceAgency": "CPSC",
      "publishedAt": "2026-07-18T00:00:00Z",
      "title": "Unrelated brand recalls kitchen blenders due to laceration hazard",
      "summary": "The blade assembly can detach during use.",
      "hazard": "Laceration hazard",
      "remedy": "Contact the manufacturer for a replacement",
      "statedBrands": [
        "Unrelated Kitchen Co"
      ],
      "statedModels": [
        "KB-100"
      ],
      "sourceUrl": "https://www.cpsc.gov/Recalls/example-2",
      "effectiveDate": null,
      "commentDeadline": null,
      "raw": {}
    }
  ]
}
```

### Input and output

For NHTSA, `sourceCoverage` lists successfully queried vehicles, vehicles skipped by the source document cap, and whether results were truncated. A skipped vehicle receives a free `vehicle_not_checked` diagnostic with `SOURCE_EVENT_LIMIT`; an unsupported lookup uses `VEHICLE_NOT_IN_SOURCE_CATALOG`. Neither means recall-free. Increase the limit or split the vehicle list, and verify the exact year/make/model against NHTSA's catalog. Model-level matches do not establish whether a particular VIN is affected or whether its repair has been completed.

Brand matching accepts spacing and hyphenation variants such as Sur-Ron, Sur Ron, and SurRon, but requires the complete name rather than a substring of another brand. Severity rules recognize both singular and plural serious-injury wording. Matches still require review against the linked agency record.

Non-recall notices also need safety or equipment-standard context. A motorcycle mention in an administrative mileage-reimbursement notice does not produce a matched-event charge. Equipment notices such as motorcycle helmet labeling remain eligible.

The Input tab documents every supported option and default. For an offline demonstration, supply `events` and set `offline: true`. For live collection, set `offline: false` and supply matching criteria such as `brands`, `keywords`, or `vehicles`. This Actor does not accept `sourceDatasetId`. A run supports at most 10,000 supplied records. Split larger inputs into separate runs.

Returns shared signal envelopes: `entityId`, `entityType`, `signalType`, `severity`, `confidence`, `observedAt`, `sourceUrl`, `evidence`, and Actor-specific `payload`. Compatible envelopes are unwrapped on input; payload fields must still match this Actor’s expected input. A common envelope does not join or enrich separate observations automatically.

Confidence is a heuristic evidence/completeness indicator, not a calibrated probability. Inspect evidence and missing inputs before acting. Dataset exports support JSON and CSV; nested fields are available through the dataset API.

### Pricing and spending limits

Pay per event, with platform usage included. The Apify Store pricing panel shows the current prices.

- **$0.10 per matched safety event**. One new source-backed safety event matched above the requested confidence threshold and delivered. No-results and vehicle-not-checked diagnostics are excluded.
- **Startup: $0.005 per GB of allocated memory**, with a minimum of one startup event. The default memory allocation is 1 GB or less.

Diagnostics and included extra signals have no output-event charge; the startup charge still applies. Set the maximum run cost to control spending. If the remaining budget cannot cover the next event, the Actor stops and returns the results already completed. Check the run summary before assuming the entire input was processed. Fees from a separate upstream scraper are not included.

### Errors and repeat runs

Malformed or empty required input fails with an actionable error. Unsupported or insufficient records can produce free diagnostic signals or rejection counts in `OUTPUT`. Check the run status, summary, diagnostics, and billing record together; zero results does not establish that no opportunities or problems exist.

Use the same `stateStoreName` for sequential observations of the same population. Use a different name for independent workflows. Do not overlap runs sharing a state store. Only delivered listing/lot results advance their reporting snapshot; a budget cutoff defers remaining work. Repeated or interrupted runs can still produce duplicate outputs because dataset writes, charging and state updates are separate operations; deduplicate downstream by entity, signal type and observation time.

### Run it from the API

Start asynchronously to avoid request timeouts on larger inputs. Use the returned run ID to wait for completion, then download its dataset.

```bash
curl --request POST 'https://api.apify.com/v2/acts/repairiq~powersports-safety-monitor/runs' \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-binary @input.json
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const input = JSON.parse(await (await import('node:fs/promises')).readFile('input.json', 'utf8'));
const run = await client.actor('repairiq/powersports-safety-monitor').call(input);
if (run.status !== 'SUCCEEDED') throw new Error('Run failed; inspect its log before using partial results.');
const dataset = client.dataset(run.defaultDatasetId);
for (let offset = 0; ; ) {
    const { items } = await dataset.listItems({ offset, limit: 1000 });
    for (const item of items) console.log(item);
    if (items.length < 1000) break;
    offset += items.length;
}
```

### Limitations and support

Uses CPSC and Federal Register sources, with optional NHTSA vehicle lookups. Source outages fail the run. A match requires review; an empty result is not a safety clearance.

Use data you have permission to process, and respect source restrictions. No affiliation with the named software vendors, marketplaces, or agencies is implied. Report reproducible problems through the Actor Issues tab with a run ID and a minimal input; omit credentials and private customer data.

### Local development

Requires Node.js 22 or newer. Run `npm ci`, `npm run build`, and `npm test` from this Actor directory. Put local input in `storage/key_value_stores/default/INPUT.json`, then run `npm start`. The repository root’s `npm run verify` also checks schemas, shared copies and real entry-point smoke scenarios.

# Actor input Schema

## `brands` (type: `array`):

Brand or manufacturer names to watch. Punctuation and spacing variants are matched automatically.

## `productCategories` (type: `array`):

electric-motorcycle, electric-dirt-bike, dirt-bike, motorcycle, atv, side-by-side, snowmobile, battery, charger, helmet, youth-powersports.

## `keywords` (type: `array`):

Free-text terms searched in event titles and bodies, and used as Federal Register search terms.

## `vehicles` (type: `array`):

NHTSA recall lookup requires an explicit make, model, and year, and the model must match NHTSA's catalog name exactly - it answers anything else with an error rather than an empty list. NHTSA also only carries on-road vehicles: dirt bikes, ATVs and side-by-sides are covered by CPSC instead. Either way the vehicle is reported as `VEHICLE_NOT_IN_SOURCE_CATALOG`, never as 'no recalls'. Look up valid names at https://api.nhtsa.gov/products/vehicle/models?modelYear=2024\&make=ktm\&issueType=r

## `includeCpsc` (type: `boolean`):

CPSC is the agency for ATVs, side-by-sides, off-road motorcycles, e-bikes, batteries, and chargers.

## `includeNhtsa` (type: `boolean`):

Requires at least one vehicle make/model/year. NHTSA covers on-road vehicles only.

## `includeFederalRegister` (type: `boolean`):

Searched with your keywords and brands, then matched like any other event.

## `onlyNewEvents` (type: `boolean`):

Emit each matched event once, then remember it in the state store. Leave this off for one-off runs and demos: with it on, a repeat run over the same lookback window correctly returns nothing, which is hard to tell apart from a broken run. Turn it on for scheduled monitoring, where you only want to hear about what changed.

## `lookbackDays` (type: `integer`):

How far back to request events. Agency records are sometimes revised, so a window wider than the schedule interval is safer. Powersports recalls are infrequent: a 30-day window often has nothing in it at all.

## `maximumEventsPerSource` (type: `integer`):

Caps how many raw documents are pulled before matching.

## `minimumMatchConfidence` (type: `number`):

Category- or keyword-only matches score lower than brand matches. Lower this to trade precision for recall.

## `events` (type: `array`):

Pre-normalized events for testing or for a source this Actor does not yet cover.

## `offline` (type: `boolean`):

Skip every network source and match only supplied events. Used for deterministic tests.

## `requestTimeoutSeconds` (type: `integer`):

Per-request timeout for agency APIs. Failed sources fail the run rather than reporting zero events, so do not set this so low that a slow agency looks like an outage: CPSC and the Federal Register regularly take more than 20 seconds for a wide lookback.

## `stateStoreName` (type: `string`):

Named key-value store remembering which events have already been reported.

## Actor input object example

```json
{
  "brands": [
    "Sur-Ron",
    "Talaria",
    "Segway",
    "KTM",
    "BMW"
  ],
  "productCategories": [
    "electric-dirt-bike",
    "battery",
    "charger"
  ],
  "keywords": [
    "lithium-ion battery",
    "thermal runaway",
    "fire hazard",
    "off-road motorcycle"
  ],
  "vehicles": [
    {
      "make": "BMW",
      "model": "R 1250 GS",
      "year": 2023
    },
    {
      "make": "KTM",
      "model": "790 Adventure",
      "year": 2024
    }
  ],
  "includeCpsc": true,
  "includeNhtsa": true,
  "includeFederalRegister": true,
  "onlyNewEvents": true,
  "lookbackDays": 90,
  "maximumEventsPerSource": 200,
  "minimumMatchConfidence": 0.5,
  "events": [],
  "offline": false,
  "requestTimeoutSeconds": 45,
  "stateStoreName": "safety-event-state"
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

## `billing` (type: `string`):

Check whether the run stopped at its spending limit.

# 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 = {
    "brands": [
        "Sur-Ron",
        "Talaria",
        "Segway",
        "KTM",
        "BMW"
    ],
    "productCategories": [
        "electric-dirt-bike",
        "battery",
        "charger"
    ],
    "keywords": [
        "lithium-ion battery",
        "thermal runaway",
        "fire hazard",
        "off-road motorcycle"
    ],
    "vehicles": [
        {
            "make": "BMW",
            "model": "R 1250 GS",
            "year": 2023
        },
        {
            "make": "KTM",
            "model": "790 Adventure",
            "year": 2024
        }
    ],
    "includeNhtsa": true,
    "onlyNewEvents": false,
    "lookbackDays": 90
};

// Run the Actor and wait for it to finish
const run = await client.actor("repairiq/powersports-safety-monitor").call(input);

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "brands": [
        "Sur-Ron",
        "Talaria",
        "Segway",
        "KTM",
        "BMW",
    ],
    "productCategories": [
        "electric-dirt-bike",
        "battery",
        "charger",
    ],
    "keywords": [
        "lithium-ion battery",
        "thermal runaway",
        "fire hazard",
        "off-road motorcycle",
    ],
    "vehicles": [
        {
            "make": "BMW",
            "model": "R 1250 GS",
            "year": 2023,
        },
        {
            "make": "KTM",
            "model": "790 Adventure",
            "year": 2024,
        },
    ],
    "includeNhtsa": True,
    "onlyNewEvents": False,
    "lookbackDays": 90,
}

# Run the Actor and wait for it to finish
run = client.actor("repairiq/powersports-safety-monitor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "brands": [
    "Sur-Ron",
    "Talaria",
    "Segway",
    "KTM",
    "BMW"
  ],
  "productCategories": [
    "electric-dirt-bike",
    "battery",
    "charger"
  ],
  "keywords": [
    "lithium-ion battery",
    "thermal runaway",
    "fire hazard",
    "off-road motorcycle"
  ],
  "vehicles": [
    {
      "make": "BMW",
      "model": "R 1250 GS",
      "year": 2023
    },
    {
      "make": "KTM",
      "model": "790 Adventure",
      "year": 2024
    }
  ],
  "includeNhtsa": true,
  "onlyNewEvents": false,
  "lookbackDays": 90
}' |
apify call repairiq/powersports-safety-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,repairiq/powersports-safety-monitor"
        }
    }
}
```

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

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/FSim2SpFEEd2Cjulh/builds/3u0iupUEbAweKrYSk/openapi.json
