# Contractor Activity Signals (`engineertsaistudio/contractor-activity-signals`) Actor

Turns public building-permit activity into decision-ready contractor change signals: first-seen contractors, new projects for known contractors, repeat activity, and optional watchlist matches. Not a raw permit feed -- a signal layer for deciding who needs a review, not another scraper.

- **URL**: https://apify.com/engineertsaistudio/contractor-activity-signals.md
- **Developed by:** [Studio EngineerTsai](https://apify.com/engineertsaistudio) (community)
- **Categories:** Real estate, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 contractor activity signals

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?

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

## Contractor Activity Signals

A raw permit row isn't a decision. **A contractor starting new work is** —
that's the moment vendor/compliance review actually matters.

This Actor turns public building-permit activity into decision-ready
events: a contractor you've never seen before, an existing contractor
starting a new project, repeat activity in a short window, or activity
matching a watchlist you supply.

It is **not** another permit scraper — [that already exists as a separate,
free primitive](https://apify.com/engineertsaistudio/county-permit-contractor-normalizer).
This Actor is the signal layer built on top of it.

### What you get

One signal per real event:

```json
{
  "signal_type": "FIRST_SEEN_CONTRACTOR",
  "contractor": "ELEC ELECTRIC",
  "permit_id": "PDS2023-RESALT-015757",
  "current_activity": "Existing Residential Main Electrical Service Upgrade - 200A",
  "first_seen": true,
  "changed_state": "NEW",
  "source": "https://data.sandiegocounty.gov/resource/dyzh-7eat.json",
  "observed_at": "2026-08-30T10:00:44Z",
  "review_reason": "contractor not seen in the prior baseline window -- first real activity observed",
  "insurance_status": "NOT_OBSERVED"
}
```

Real output, from a real run against San Diego County's public data:
303 permits in the current window, 140 distinct contractors, 473 signals,
71 of them genuinely first-seen.

### Signal types

| Signal | Meaning |
|---|---|
| `FIRST_SEEN_CONTRACTOR` | This contractor didn't appear in the baseline window |
| `NEW_PROJECT_FOR_CONTRACTOR` | A contractor already active in the baseline has a new permit |
| `ACTIVITY_CHANGE` | This contractor has more than one permit in the current window |
| `WATCHLIST_CONTRACTOR_ACTIVITY` | The permit matches a name you're watching for |

### What this Actor does NOT claim

**`insurance_status` is always `"NOT_OBSERVED"`.** This Actor has no
certificate-of-insurance data source. A signal means "this activity may be
worth a compliance review," never "this contractor is compliant" or
"non-compliant." Don't use it as a substitute for actually checking.

### Input

| Field | Required | Description |
|---|---|---|
| `socrataHost` / `resourceId` | no (defaults to San Diego County) | Any Socrata county/city portal |
| `currentWindowDays` | no (default 14) | What counts as "current" activity |
| `baselineWindowDays` | no (default 90) | Comparison window immediately before "current" |
| `watchlist` | no | Contractor name fragments to flag specifically |

Windows are relative to the **dataset's own most recent record**, not
today's date — this works correctly whether your source portal updates
daily or is a static archive.

# Actor input Schema

## `socrataHost` (type: `string`):

The county/city open-data portal hostname, e.g. data.sandiegocounty.gov.

## `resourceId` (type: `string`):

The dataset's resource id from its Socrata URL, e.g. dyzh-7eat.

## `dateField` (type: `string`):

The dataset's own column name for the permit issue date.

## `currentWindowDays` (type: `integer`):

How many days, counted back from the dataset's own most recent record, count as 'current' activity.

## `baselineWindowDays` (type: `integer`):

How many days immediately before the current window count as the comparison baseline.

## `watchlist` (type: `array`):

Optional list of contractor name fragments to flag with WATCHLIST\_CONTRACTOR\_ACTIVITY (case-insensitive substring match).

## `fetchLimit` (type: `integer`):

How many raw permit rows to pull before splitting into baseline/current windows.

## Actor input object example

```json
{
  "socrataHost": "data.sandiegocounty.gov",
  "resourceId": "dyzh-7eat",
  "dateField": "issued_date",
  "currentWindowDays": 14,
  "baselineWindowDays": 90,
  "watchlist": [],
  "fetchLimit": 2000
}
```

# 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 = {
    "socrataHost": "data.sandiegocounty.gov",
    "resourceId": "dyzh-7eat",
    "dateField": "issued_date"
};

// Run the Actor and wait for it to finish
const run = await client.actor("engineertsaistudio/contractor-activity-signals").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 = {
    "socrataHost": "data.sandiegocounty.gov",
    "resourceId": "dyzh-7eat",
    "dateField": "issued_date",
}

# Run the Actor and wait for it to finish
run = client.actor("engineertsaistudio/contractor-activity-signals").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 '{
  "socrataHost": "data.sandiegocounty.gov",
  "resourceId": "dyzh-7eat",
  "dateField": "issued_date"
}' |
apify call engineertsaistudio/contractor-activity-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,engineertsaistudio/contractor-activity-signals"
        }
    }
}

```

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/ybTaGCOmVkp5QqxRm/builds/DD8fnDpCmz3ATmZSB/openapi.json
