# Code Violation Leads (Accela Code Enforcement Cases) (`finaldynamics/code-violation-leads`) Actor

Code violation leads: code enforcement cases from any Accela Citizen Access city or county with case type, status, address and date. Distressed property lists.

- **URL**: https://apify.com/finaldynamics/code-violation-leads.md
- **Developed by:** [Final Dynamics](https://apify.com/finaldynamics) (community)
- **Categories:** Lead generation, Real estate
- **Stats:** 2 total users, 1 monthly users, 66.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 enforcement case records

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

## Code Violation Leads (Accela Code Enforcement Cases)

Pull code violation leads and code enforcement cases from any city or county that runs Accela
Citizen Access: property registrations, complaints, violation notices, stop-work orders and the
other records a code enforcement office opens. Give it an agency and a date range; get one row per
case with the type, status, address and a link to the public record.

A code violation is often the first public sign of a distressed or neglected property. Real estate
investors, wholesalers, property managers and contractors use these rows as leads weeks before the
same address shows up in foreclosure or tax lists.

### What you get

One row per case. Every field is flat, so the dataset drops straight into Google Sheets, Airtable or
a CRM.

| Field                                                                     | Example                                                |
| ------------------------------------------------------------------------- | ------------------------------------------------------ |
| `permitNumber`                                                            | `VR-2026-00483` (the case number)                      |
| `temporaryNumber`                                                         | `26TMP-050333` if the case was still being filed       |
| `permitType`                                                              | `Property Registration`, `Zoning Complaint`            |
| `status`                                                                  | As published; often empty while a case is being opened |
| `appliedDate`                                                             | `2026-09-14` (date opened or last updated)             |
| `addressFull`, `street`, `addressCity`, `addressState`, `zip`, `parcelId` | `1014 ADA AVE NW, ATLANTA GA 30318`                    |
| `description`, `shortNotes`                                               | The case description and notes columns                 |
| `detailUrl`                                                               | Direct link to the public case record, when one exists |
| `recordKind`                                                              | `enforcement_case`                                     |
| `agencyKey`, `agencyName`, `city`, `state`, `module`                      | Which portal the row came from                         |
| `isNewSinceLastRun`, `previousStatus`, `statusChanged`                    | Set by the incremental and monitoring modes            |
| `scrapedAt`                                                               | UTC timestamp                                          |

Anything the portal does not publish is `null`. Owner names are off by default because owners are
usually private individuals; turn on `includeOwnerName` together with `includeDetails` if you need
them and your use allows it.

### How to use it

Pick an agency, set a date range, run.

```json
{
  "agency": "ATLANTA_GA",
  "module": "Enforcement",
  "dateFrom": "2026-09-08",
  "dateTo": "2026-09-15",
  "maxResults": 200
}
```

Both dates are inclusive. `maxResults` caps the rows you pay for.

The actor reads the selected module's **Record Type** list and uses `permitTypeContains` to choose
matching case types for server-side searches. It scans the full date window and filters rows locally
when the agency publishes no list, nothing matches, or more than 25 types match; the run log
explains the fallback. Leaving both filters empty includes all types.

To choose types yourself, set `recordTypes` to names or raw values from that menu, for example
`["Code Complaint"]` in Atlanta. Up to 25 explicit types are accepted. An unknown name stops the run
with examples from the available list; if the list is unavailable, clear `recordTypes` to use the
full scan. `permitTypeContains` still filters the returned rows when `recordTypes` is set.

`maxPages` defaults to 60 result pages across all type searches, including searches with no matches.
At the limit the run stops and tells you to raise `maxPages`, narrow `lastDays` or
`dateFrom`/`dateTo`, or select fewer `recordTypes`. Plain runs write rows page by page, preserving
completed pages if a later request fails or the run is killed. With `includeDetails`, rows wait
until the run-wide detail failure check passes. Persistent status-monitor runs keep all-or-nothing
delivery: an incomplete run writes no rows and does not seed or advance its baseline. Both
`maxPages` and `statusMonitorMaxPages` apply to monitors across all searches; the lower limit wins.
An incomplete run never advances the `sinceLastRun` watermark.

Atlanta's saved Enforcement form publishes **Code Complaint** and **Property Registration** in that
menu. Selection uses those case types from the Enforcement module, not Building permit types. Other
agencies may publish a different list or none at all.

**Recent cases on a schedule:** set `lastDays: 14` to search the last 14 calendar days ending today
(UTC), including today. The date window moves automatically on every run, overriding `dateFrom` and
`dateTo`, so saved tasks stay current. Leave `lastDays` at `0` to use fixed dates.

**Complaints and violation notices only, skipping routine registrations:**

```json
{
  "agency": "ATLANTA_GA",
  "module": "Enforcement",
  "dateFrom": "2026-09-01",
  "dateTo": "2026-09-15",
  "permitTypeContains": ["complaint", "violation"]
}
```

**Parcel numbers and permanent case numbers:** set `includeDetails: true` to read each case's public
detail page. Enforcement detail pages carry less than permit pages do, so start without it.

**Only cases you have not seen before:** create a key-value store in Apify Console, select it as
`stateStoreId`, set `sinceLastRun: true`, and reuse the same run configuration (a saved task). Each
complete run remembers the newest applied date it delivered and the next run emits only cases with a
newer applied date. A run that hits a cap or an error never moves that marker, so nothing is
skipped.

**Status changes on a schedule:** create a key-value store in Apify Console, select it as
`statusStateStoreId`, and schedule the task. The first complete run stores every case's status;
later runs emit only cases whose status changed, with `previousStatus` filled in. Give each schedule
its own store.

**Any other Accela portal:** set `agency` to the portal's agency code and `portalUrl` to its base
URL, for example `https://aca-prod.accela.com/CITYNAME/`. Not every agency publishes an Enforcement
module; when one does not, the run says so.

### Quick start

No code needed; about two minutes from input to spreadsheet. For a longer walkthrough with example
output, read the guide
[Build a code violation research spreadsheet](https://finaldynamics.com/code-violation-guide.html).

1. Open the example "Code violation leads, Atlanta, last 14 days" on this actor's page, or paste one
   of the inputs above into the input form.
2. Click **Start** and wait until the run shows **Succeeded**.
3. Open the **Output** tab to see the cases as a table.
4. Click **Export**, choose CSV or Excel, and download the file.
5. In Google Sheets, choose **File > Import > Upload** and select the file.

### Automate it

**A weekly lead pull:** keep `lastDays` in the task (for example 7 or 14) so the date window moves
with every run. Click **Save as a new task**, then add a schedule for that task under **Schedules**
in Apify Console (for example every Monday at 07:00). To get only cases you have not received
before, turn on `sinceLastRun` and select a key-value store as `stateStoreId`.

**Into Google Sheets with n8n:** add the **Apify** node with the **On new Apify Event** trigger,
choose your task and the succeeded event, then add an **Apify** node with **Get Dataset Items**
using the run's `defaultDatasetId`, and finish with a **Google Sheets** node that appends the rows.
Every scheduled run then adds its cases to the same sheet. Make and Zapier have Apify integrations
that work the same way.

### Pricing

Pay per event. There are no platform usage charges on top.

| Event                    |  Price | When                                                    |
| ------------------------ | -----: | ------------------------------------------------------- |
| `actor-start`            |  $0.01 | Once per run                                            |
| `permit-record`          | $0.006 | Per case row delivered                                  |
| `contractor-detail-page` | $0.004 | Per case page fetched, only when `includeDetails` is on |

Examples: 200 cases cost $1.21. A weekly pull of 50 new cases is $0.31. A run that finds nothing
costs $0.01. Set a maximum charge on the run for a hard ceiling; the actor stops cleanly at the cap.

### Coverage

The same 24 bundled portals as the Accela Building Permit Scraper by the same publisher (Atlanta,
Hillsborough County, Tampa, Sacramento, Milwaukee, Reno, Hartford and others), plus any Accela
Citizen Access portal you supply with `portalUrl`. Whether an agency exposes an Enforcement module
is up to that agency; Atlanta does. Try one short date range first on a new agency.

This listing runs the same code as the Accela Building Permit Scraper. Set `module` to `Building` or
`Planning` here and you get permits or planning cases with the same fields.

### Good to know

- If a case detail page remains unavailable after retries, its grid row is retained with
  `detailFetchFailed: true`, null detail-only fields and no detail-page charge, unless more than 20%
  of attempted detail pages or more than 25 pages fail, which stops the run before row delivery.
- Public records only. The actor reads the same pages a visitor sees, honours the portal's robots
  rules, and never logs in, solves CAPTCHAs or uses proxies. A block is a failed run, never an empty
  success.
- Requests are polite: at most two at a time per portal, with retries and backoff on errors.
- The date column is what the agency publishes. For Atlanta it is the case's most recent activity
  date, so a case can reappear with a newer date.
- Cases still being filed show a temporary number and no detail link; the permanent number appears
  once the agency publishes the record.
- The actor is not affiliated with Accela or with any listed agency. Public records can be
  incomplete or corrected later.

### FAQ

**How do I get a code violation list for my city?**

If the city runs Accela Citizen Access with an Enforcement module (Atlanta does), enter its agency
key and `lastDays`. One short run shows whether cases come back.

**Are code violations good leads for real estate investors?**

A violation often marks a neglected or vacant property whose owner may want to sell. The list gives
you the address and case details; owner names and phone numbers are not part of it.

**Can I export code violations to Excel?**

Yes. Open the Output tab after the run and export CSV or Excel, or send each scheduled run to Google
Sheets as described above.

### Related actors

- [Accela Building Permit Scraper](https://apify.com/finaldynamics/accela-permit-scraper): building
  permits with contractor details from Accela cities and counties.
- [Florida Lis Pendens, Pre-Foreclosure & Deed Leads](https://apify.com/finaldynamics/florida-lis-pendens-leads):
  lis pendens and deeds from five Florida county clerks.
- [Florida Vacant Land & Absentee Owner Leads](https://apify.com/finaldynamics/florida-vacant-land-leads):
  vacant parcels and out-of-state owners in every Florida county.

Questions or a portal that misbehaves? Open an issue on the actor page and include the agency and
date range.

# Actor input Schema

## `agency` (type: `string`):

Accela agency key. See agencies.json for 24 verified portal URLs.

## `portalUrl` (type: `string`):

HTTPS base URL ending with the agency key and a slash. Bundled deployment permits aca-prod.accela.com.

## `module` (type: `string`):

Citizen Access module using General Search: Building, Enforcement or Planning. Known names are trimmed and normalized; other module names retain their casing. Availability varies by agency; unsupported modules fail clearly.

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

Inclusive applied date, YYYY-MM-DD.

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

Inclusive applied date, YYYY-MM-DD.

## `lastDays` (type: `integer`):

Search the last N calendar days ending today, instead of fixed dateFrom and dateTo. Leave at 0 to use the fixed dates.

## `recordTypes` (type: `array`):

Names or values from the selected module's Record Type list, for example "Code Complaint" in Atlanta. The portal searches only those types. At most 25 explicit types are allowed; unknown names stop the run with examples of available types. Leave empty to select types automatically using permitTypeContains. If no list is published, no types match, or more than 25 match, the actor scans the date window and filters rows locally.

## `permitTypeContains` (type: `array`):

Case-insensitive substring matching; any listed string may match. Empty includes all types. With recordTypes empty, matching types from the portal list are searched directly (up to 25). If automatic selection is unavailable, finds no matches, or exceeds 25 matches, the date window is scanned. This filter still applies to returned rows, including when recordTypes is set.

## `minValuation` (type: `number`):

Zero disables filtering. A positive value excludes unknown valuations; enable includeDetails to read valuation when published.

## `maxResults` (type: `integer`):

The most rows one run delivers. When a run stops at this limit, the "only new permits" memory is not updated, so nothing is skipped next time.

## `maxPages` (type: `integer`):

Ceiling on result pages read across all record-type searches (default 60). At the limit the run stops and names the input to change. Narrow the dates, select recordTypes, or raise maxPages. Plain runs retain delivered rows; detail runs deliver only after the failure threshold check. Incomplete status-monitor runs deliver no rows or baseline changes. sinceLastRun is not advanced on an incomplete run.

## `includeDetails` (type: `boolean`):

Fetch public detail links for contractor, parcel, dates and valuation. Owner names additionally require includeOwnerName. Each fetched detail page costs $0.004, even if a valuation filter later excludes the record.

## `includeOwnerName` (type: `boolean`):

Include the property owner's name from the permit detail page. Off by default because owners are usually private individuals.

## `sinceLastRun` (type: `boolean`):

Deliver only permits applied for after the newest one a previous run delivered. Needs a key-value store selected below as stateStoreId; give each schedule its own. A run that stops early does not move the marker forward.

## `stateStoreId` (type: `string`):

Key-value store that remembers the newest applied date delivered by sinceLastRun runs. Create one in Apify Console and select it here; each schedule should have its own.

## `statusBaseline` (type: `array`):

Optional list of permit numbers and statuses from an earlier run, to compare against. Permit numbers match without regard to case.

## `statusChangesOnly` (type: `boolean`):

Deliver only permits from the list above whose status is different now. Needs a non-empty list.

## `statusStateStoreId` (type: `string`):

A key-value store the actor uses to remember every permit's status between scheduled runs. The first complete run delivers everything and saves the statuses; later runs deliver only permits whose status changed. Use one store per schedule and let runs finish one at a time.

## `statusMonitorMaxPages` (type: `integer`):

Safety limit on search result pages when watching status changes. If the limit is reached, the run stops and the saved statuses stay as they were. Counts across all record-type searches; maxPages also applies, and the lower limit wins.

## `statusMonitorMaxRecords` (type: `integer`):

Safety limit on permits read when watching status changes. If the limit is reached, the run stops and the saved statuses stay as they were.

## Actor input object example

```json
{
  "agency": "ATLANTA_GA",
  "portalUrl": "https://aca-prod.accela.com/ATLANTA_GA/",
  "module": "Enforcement",
  "dateFrom": "2026-09-01",
  "dateTo": "2026-09-14",
  "lastDays": 0,
  "recordTypes": [],
  "permitTypeContains": [],
  "minValuation": 0,
  "maxResults": 200,
  "maxPages": 60,
  "includeDetails": false,
  "includeOwnerName": false,
  "sinceLastRun": false,
  "statusBaseline": [],
  "statusChangesOnly": false,
  "statusMonitorMaxPages": 100,
  "statusMonitorMaxRecords": 5000
}
```

# Actor output Schema

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

Crawled data stored in default dataset

## `files` (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 = {
    "agency": "ATLANTA_GA",
    "portalUrl": "https://aca-prod.accela.com/ATLANTA_GA/",
    "module": "Enforcement",
    "dateFrom": "2026-09-01",
    "dateTo": "2026-09-14",
    "lastDays": 0,
    "recordTypes": [],
    "permitTypeContains": [],
    "minValuation": 0,
    "maxResults": 200,
    "maxPages": 60,
    "includeDetails": false,
    "includeOwnerName": false,
    "sinceLastRun": false,
    "statusBaseline": [],
    "statusChangesOnly": false,
    "statusMonitorMaxPages": 100,
    "statusMonitorMaxRecords": 5000
};

// Run the Actor and wait for it to finish
const run = await client.actor("finaldynamics/code-violation-leads").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 = {
    "agency": "ATLANTA_GA",
    "portalUrl": "https://aca-prod.accela.com/ATLANTA_GA/",
    "module": "Enforcement",
    "dateFrom": "2026-09-01",
    "dateTo": "2026-09-14",
    "lastDays": 0,
    "recordTypes": [],
    "permitTypeContains": [],
    "minValuation": 0,
    "maxResults": 200,
    "maxPages": 60,
    "includeDetails": False,
    "includeOwnerName": False,
    "sinceLastRun": False,
    "statusBaseline": [],
    "statusChangesOnly": False,
    "statusMonitorMaxPages": 100,
    "statusMonitorMaxRecords": 5000,
}

# Run the Actor and wait for it to finish
run = client.actor("finaldynamics/code-violation-leads").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 '{
  "agency": "ATLANTA_GA",
  "portalUrl": "https://aca-prod.accela.com/ATLANTA_GA/",
  "module": "Enforcement",
  "dateFrom": "2026-09-01",
  "dateTo": "2026-09-14",
  "lastDays": 0,
  "recordTypes": [],
  "permitTypeContains": [],
  "minValuation": 0,
  "maxResults": 200,
  "maxPages": 60,
  "includeDetails": false,
  "includeOwnerName": false,
  "sinceLastRun": false,
  "statusBaseline": [],
  "statusChangesOnly": false,
  "statusMonitorMaxPages": 100,
  "statusMonitorMaxRecords": 5000
}' |
apify call finaldynamics/code-violation-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,finaldynamics/code-violation-leads"
        }
    }
}
```

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/u0gV8YBiFTHYpp5W9/builds/myfCiyhVwLxoHsdDT/openapi.json
