# City Business License Leads — New Business Lead Feed API (`malonestar/city-business-license-leads`) Actor

New business-license lead feed across Chicago, LA, SF, NYC and Seattle. Normalizes municipal open data into one schema with new-vs-renewal and active-vs-dead licence flags, owner mailing address, NAICS, phone where published, plus per-city coverage and freshness on every row.

- **URL**: https://apify.com/malonestar/city-business-license-leads.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Lead generation, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.30 / 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

## City Business License Leads — New Business Lead Feed (API)

A **multi-city business-license lead feed**. This actor normalizes municipal
open data from Chicago, Los Angeles, San Francisco, New York and Seattle into
**one clean schema** with a **"new since date" delta**, a **new-vs-renewal**
classification, an **active-vs-dead licence** flag, **NAICS / industry tags**,
**owner mailing address** and **phone where the city publishes one** — purpose-built
for local-services lead generation. Sourced from official municipal open-data
portals (Socrata). **Keyless, no anti-bot, no scraping.**

### What changed in v1.1 (read this before you compare row counts)

v1.1 is a correctness pass. Every number below was measured live on 2026-08-01
against the cities' own APIs, and every one of these produced a plausible wrong
answer on a run that reported success:

- **Renewals were being sold as new businesses.** Chicago publishes an
  `application_type` column; v1.0 fetched it and threw it away. **915,200 of
  Chicago's 1,202,710 records (76.1%) are `RENEW`** — existing businesses renewing,
  not new ones. On the 1,000 most recent records by issue date it was **721 of
  1,000 (72.1%)**. Every row now carries `application_type` and `record_kind`, and
  `onlyNewLicenses` (**on by default**) filters renewals out server-side so you are
  not billed for them.
- **Dead licences were being sold as leads.** `license_status` was also fetched
  and discarded. **Only 43,581 of New York's 69,885 records (62.4%) are Active** —
  the rest are Expired, Surrendered, Failed to Renew, Revoked, Voided, Suspended,
  or explicitly **Out of Business**. Chicago carries 78,619 cancelled and 1,461
  revoked licences. Every row now carries `license_status` and
  `is_active_license`, and `onlyActiveLicenses` (**on by default**) excludes the
  dead ones server-side.
- **Los Angeles returned undated junk on a default run.** Socrata's
  `$order <col> DESC` defaults to **NULL FIRST**, and LA has **3,569 records with
  no `location_start_date`** — so a default LA page was filled with undated
  records and contained not one recent business. Ordering is now `NULL LAST`.
- **A dead city vanished silently.** v1.0 warned and continued, with nothing on
  any row saying a city was missing, so a five-city run where two cities 404'd
  produced a three-city dataset that looked complete. Every row now carries
  `city_query_status`, and a city that could not be measured emits an explicit
  row instead of disappearing.
- **New York is stale and nothing said so.** Its newest record is dated
  **2026-04-16** and **zero** records have been created since 2026-07-01. A
  "since last week" run legitimately returns nothing from New York — which is the
  city not publishing, not an absence of new businesses. `source_stale`,
  `source_max_date` and `source_age_days` now say which.
- **Contact fields were being fetched and thrown away.** Root cause: Socrata
  omits null columns from row JSON, so a schema read off a sample row
  under-reports the dataset. v1.1 reads each dataset's metadata instead. Recovered:
  **LA `mailing_address` (337,260 records, 53.4%)** and `dba_name` (201,059,
  31.8%); **SF `mailing_address_1` (163,876, 44.9%)**, `self_reported_naics_code`
  (127,705, 35.0%) and `lic_code_description`; New York's unit/suite and address
  quality.
- **Seattle dates were not normalized.** `license_start_date` is a TEXT column in
  `YYYYMMDD` form, and v1.0 emitted `"20261230"` while every other city emitted
  `"2026-12-30"`. Both the output and the `since` filter now handle it.

Set `onlyNewLicenses: false` and `onlyActiveLicenses: false` to restore the v1.0
firehose. Neither filter can ever drop a row from a city that does not publish the
underlying column — those rows come back tagged `record_kind: "unknown"` and
`is_active_license: null`.

### Who it's for

- **Local-services lead gen** — insurance, POS/merchant services, payroll, business
  banking, and B2B SaaS that sell to **brand-new businesses**.
- **Sales & RevOps** teams building a fresh-leads pipeline of just-licensed companies.
- **Data / list providers** enriching company records with city, NAICS, mailing
  address and licence status.
- **AI agents** needing a "find newly licensed businesses in a city" tool.

### What it does

Pulls business licences from each selected city, filters to the ones **issued
after your `since` date**, classifies each as a new licence or a renewal, drops
the dead ones, and returns a normalized record per licence. Run it on a schedule
(e.g. daily with `since` = yesterday) and it becomes a **recurring new-business
alert** you can route straight into your CRM.

### Live cities

| City | Source dataset | Records | New vs renewal | Licence status | Phone | Mailing address | NAICS |
|------|----------------|---------|----------------|----------------|-------|-----------------|-------|
| Chicago | Business Licenses (`r5kz-chrr`) | 1,202,710 | **yes** | **yes** | no | no | no |
| Los Angeles | Listing of Active Businesses (`6rrh-rzua`) | 631,589 | no | no | no | **53.4%** | **yes** |
| San Francisco | Registered Business Locations (`g8m3-pdis`) | 364,774 | no | no | no | **44.9%** | **35.0%** |
| New York City | Issued Licenses / DCWP (`w7w3-xahh`) | 69,885 | no | **yes** | **72.5%** | no | no |
| Seattle | Active Business License Tax Certificate (`wnbq-64tb`) | 83,856 | no | no | **99.5%** | no | **yes** |

Percentages are the share of records that actually carry a value, measured live
2026-08-01. A blank cell means the city publishes no such column at all, so a
`null` in that field is "not published", not "none on file" — `publishes_phone`,
`publishes_license_status` and `publishes_application_type` state this on every row.

**Coming soon (scaffolded, NOT wired):** San Diego, Boston, Austin, Kansas City,
Baton Rouge. Requesting one returns an explicit `not_wired` row rather than
silently nothing.

### Example input

```json
{
  "cities": ["chicago", "seattle"],
  "since": "2026-07-01",
  "onlyNewLicenses": true,
  "onlyActiveLicenses": true,
  "naics": "72",
  "maxResults": 1000
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `cities` | string\[] | all live | `chicago`, `los-angeles`, `san-francisco`, `new-york`, `seattle` |
| `since` | string | — | `YYYY-MM-DD`; only licences issued/started after this date |
| `name` | string | — | Business-name fragment, case-insensitive |
| `licenseType` | string | — | Licence category fragment (skipped where a city has no type column) |
| `naics` | string | — | NAICS prefix (LA, Seattle, and SF self-reported) |
| `onlyNewLicenses` | boolean | `true` | Exclude renewals where the city publishes an application type |
| `onlyActiveLicenses` | boolean | `true` | Exclude expired/revoked/void licences where the city publishes a status |
| `maxResults` | integer | 1000 | Per-city cap on rows returned |
| `socrataAppToken` | string (secret) | — | Optional free Socrata token to raise rate limits |

### Output fields

#### The lead

`city`, `business_name`, `dba`, `license_type`, `license_description`, `naics`,
`issue_date`, `start_date`, `expiration_date`, `end_date`, `address`, `city_name`,
`state`, `zip`, `ward_or_district`, `latitude`, `longitude`, `phone`, `record_id`,
`mailing_address`, `mailing_city`, `mailing_state`, `mailing_zip`, `unit_or_suite`,
`address_quality`, `neighborhood`, `police_district`, `community_area`,
`supervisor_district`, `business_corridor`, `ownership_type`, `source_url`,
`retrieved_at`.

#### Lead quality — is this actually a new, live business?

`application_type`, `record_kind` (`new_license` / `renewal` /
`change_of_location` / `other` / `unknown`), `license_status`,
`license_status_normalized`, `is_active_license`, `business_closed`,
`closed_reason`.

#### City-specific detail

`business_activity_id`, `license_code`, `application_created_date`,
`payment_date`, `status_change_date`, `conditional_approval`,
`special_service_area`, `parking_tax`, `transient_occupancy_tax`,
`account_number`, `site_number`, `bin`, `bbl`, `community_board`,
`census_tract`, `license_detail`, `location_description`.

#### Per-city data quality (on every row)

`source_city_slug`, `source_dataset`, `city_query_status`, `city_source_error`,
`records_available`, `records_fetched`, `is_complete`, `source_max_date`,
`source_age_days`, `source_stale`, `publishes_application_type`,
`publishes_license_status`, `publishes_phone`, `data_completeness_note`.

#### Reading `null` correctly

`null` means **not published by this city**. `false` means **published and
negative**. They are different claims:

- `record_kind: "unknown"` means the city publishes no new-vs-renewal signal. It
  is **not** a claim that the record is new. Only Chicago publishes one today.
- `is_active_license: null` means the city publishes no status (LA, SF, Seattle).
  `false` means the city says the licence is expired, revoked, surrendered or void.
- `business_closed: null` means no closure signal was published — never a verified
  "still trading".
- `phone: null` in Chicago, LA or SF means the dataset has **no phone column at
  all**. `publishes_phone` is `false` for those cities.
- `records_available` is the source's own count of records matching your filters.
  When `records_fetched` is lower, `is_complete` is `false` — raise `maxResults`.

Fields that populate only on certain inputs, with an input that populates each
(all verified live 2026-08-01):

| Field(s) | Populating input |
|---|---|
| `city_source_error`, `city_query_status` other than `ok` | `cities: ["atlantis", "boston", "chicago"]` — an unknown slug and a scaffolded one |
| `data_completeness_note` | any run where a city is partial, stale or empty — e.g. `cities: ["new-york"], since: "2026-07-01"` |
| `source_stale`, `source_age_days` | `cities: ["new-york"]` (true today; the dataset is frozen at 2026-04-16) |
| `unit_or_suite` (21%), `address_quality` (69%), `bin`, `bbl` (56%), `community_board`, `census_tract`, `license_detail` | `cities: ["new-york"], onlyActiveLicenses: false` — New York-only columns |
| `is_active_license: false`, `business_closed`, `closed_reason` | `cities: ["new-york"], onlyActiveLicenses: false` (4 of the 300 most recent) or `cities: ["san-francisco"], onlyActiveLicenses: false` |
| `end_date` | `cities: ["san-francisco"], onlyActiveLicenses: false` (2% of recent rows; 238,700 of 364,774 dataset-wide) |
| `record_kind: "renewal"`, `application_type` | `cities: ["chicago"], onlyNewLicenses: false` — 205 of the 300 most recent Chicago records |
| `status_change_date` | `cities: ["chicago"], onlyActiveLicenses: false` — populated on 80,094 of 1,202,710 Chicago records (6.7%), i.e. licences whose status has changed |
| `ownership_type` | `cities: ["seattle"]` |
| `location_description` | `cities: ["los-angeles"]` |
| `mailing_address` (80–100% of recent rows) | `cities: ["los-angeles", "san-francisco"]` |
| `phone` | `cities: ["seattle"]` (100% of recent rows) or `["new-york"]` (64%) |

### What happens when a source breaks

Before any row is written, each selected city is checked live: the dataset still
resolves and still holds at least a floor number of records; a **negative
control** (a business name that cannot exist) must match **exactly zero** records,
which catches Socrata silently ignoring the `$where`; an unfiltered read must
return a real record; and every column the mapper depends on must still be present
in the dataset's own metadata. A city that fails is excluded loudly and emits an
`unavailable` row. If **every** city fails, the run fails and **nothing is
billed**.

### Use as an MCP tool

This actor works as a clean, chainable **MCP tool** for AI agents via
[mcp.apify.com](https://mcp.apify.com). Every input and output field is described,
and `record_kind`, `is_active_license`, `city_query_status` and `source_stale` let
an agent tell a real lead from an unverified one before writing it into a CRM.

### FAQ

**Why did my row count drop after v1.1?** Because `onlyNewLicenses` and
`onlyActiveLicenses` default to on, and roughly three-quarters of the Chicago
dataset is renewals while about 38% of the New York dataset is dead licences. Set
both to `false` for the old behaviour.

**Why does New York return nothing for a recent `since` date?** The city's dataset
has not been updated since 2026-04-16. `source_stale`, `source_max_date` and
`source_age_days` tell you this on every row, and the log says it too.

**Which cities give me a phone number?** New York (72.5% of records) and Seattle
(99.5%). Chicago, LA and SF publish no phone column at all. LA and SF publish an
owner **mailing address** instead (53.4% and 44.9%).

**Is this a company database?** No — it is a licence feed. A licence is evidence a
business registered with a city; it is a screening and prospecting tool, not a
verified company record.

**How is it priced?** Pay-per-event: a small fee per dataset row. A failed run
emits no rows.

### Related actors

Pairs with the other lead-gen feeds in this catalogue — liquor-licence new
openings, childcare provider leads, realtor licence rosters and the city/state
business-registry monitors.

# Actor input Schema

## `cities` (type: `array`):

City slugs to pull business licenses from. Live and wired: chicago, los-angeles, san-francisco, new-york, seattle. Any other slug produces an explicit unknown\_city / not\_wired row rather than being silently dropped. Leave empty to query all live cities.

## `since` (type: `string`):

Only return licenses issued/started after this date, per each city's issue-date column. Use on a schedule to get a fresh new-business lead feed. Leave empty for the most recent records overall.

## `name` (type: `string`):

Optional. Case-insensitive fragment matched against each city's business-name column (e.g. 'salon', 'LLC', a specific company).

## `licenseType` (type: `string`):

Optional. Case-insensitive fragment matched against the per-city license-type column (e.g. 'restaurant', 'pawnbroker', 'peddler'). Skipped for cities that do not expose a type column.

## `naics` (type: `string`):

Optional. NAICS code prefix to filter by industry (e.g. '72' = accommodation & food services, '5419' = professional services). Applied only in cities that expose NAICS (Los Angeles, Seattle).

## `onlyNewLicenses` (type: `boolean`):

On by default, so the feed matches what this Actor is sold as. Chicago publishes an application type and 76.1% of its 1.2M records are RENEW - existing businesses renewing, not new ones. With this on, Chicago is filtered server-side to ISSUE and C\_LOC (a genuine new site) so you are not billed for renewals at all. Cities that publish no new-vs-renewal signal are NEVER dropped by this - their rows come back tagged record\_kind "unknown". Turn it off to get every record.

## `onlyActiveLicenses` (type: `boolean`):

On by default. Only 62.4% of the New York dataset is Active - the rest is Expired, Surrendered, Revoked, Voided, Suspended or explicitly Out of Business, and Chicago carries 78,619 cancelled plus 1,461 revoked licences. With this on those are excluded server-side. Cities that publish no status (LA, SF, Seattle) are NEVER dropped by this - their rows come back with is\_active\_license null. Turn it off to get every record.

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

Maximum number of license records to return per city (the actor paginates to reach this).

## `socrataAppToken` (type: `string`):

Optional free Socrata app token to raise API rate limits for large pulls across many cities.

## Actor input object example

```json
{
  "cities": [
    "chicago"
  ],
  "onlyNewLicenses": true,
  "onlyActiveLicenses": true,
  "maxResults": 50
}
```

# Actor output Schema

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

Normalized newly issued business-license records across US cities, in the default dataset.

# 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 = {
    "cities": [
        "chicago"
    ],
    "onlyNewLicenses": true,
    "onlyActiveLicenses": true,
    "maxResults": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/city-business-license-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 = {
    "cities": ["chicago"],
    "onlyNewLicenses": True,
    "onlyActiveLicenses": True,
    "maxResults": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/city-business-license-leads").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "cities": [
    "chicago"
  ],
  "onlyNewLicenses": true,
  "onlyActiveLicenses": true,
  "maxResults": 50
}' |
apify call malonestar/city-business-license-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=malonestar/city-business-license-leads",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/E3091lJSNlo34cozt/builds/KfWT1GUBGzqxmtFTW/openapi.json
