# TTB Distilled Spirits Plant Permits Scraper (`automation-lab/ttb-distilled-spirits-plant-permittees`) Actor

Download and filter the official TTB distilled spirits plant permittee roster for supplier verification and compliance research.

- **URL**: https://apify.com/automation-lab/ttb-distilled-spirits-plant-permittees.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.32 / 1,000 item extracteds

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

## TTB Distilled Spirits Plant Permits Scraper

Download and filter the official US Alcohol and Tobacco Tax and Trade Bureau (TTB) spirits producers and bottlers roster. The Actor turns the public **TTB distilled spirits plant permits** file into typed dataset rows for supplier verification, compliance research, and repeatable roster refreshes.

The output includes each DSP permit number, legal owner and operating names, premises address, county, industry type, new-permit flag, and regulator provenance. No TTB account or browser automation is required.

### What can you do with this Actor?

- Search the national TTB spirits producer roster by permit, business name, trade name, or address text.
- Export all permittees for one or more states, cities, or counties.
- Resolve an exact list of DSP permit numbers for supplier onboarding.
- Extract records that TTB marks as new since its previous publication.
- Schedule recurring runs and compare datasets in your own database or automation.
- Send normalized results to spreadsheets, BI tools, CRMs, or compliance systems.

This Actor covers the official **Spirits Producers and Bottlers List**. It does not claim to return wine, beer, importer, wholesaler, COLA, application, enforcement, or private account records.

### Who is it for?

**Compliance teams** can create a repeatable regulator-sourced permit roster for screening and periodic review.

**Distillers, distributors, and procurement teams** can confirm that a prospective supplier appears in the published TTB spirits list.

**Researchers and analysts** can segment distilled spirits plants by geography, legal owner, operating name, or publication marker.

**Data engineers** can replace manual CSV downloads with a scheduled API workflow and a stable output schema.

### Why use this TTB permit scraper?

TTB publishes a useful CSV, but a raw file still leaves filtering, parsing, limits, provenance, and integrations to you. This Actor:

1. discovers the current official CSV from TTB's permittee page;
2. validates the source hostname, response shape, and expected columns;
3. parses quoted CSV values safely;
4. applies all filters before charging or saving records;
5. emits normalized JSON with a combined address and source timestamps.

If TTB changes the file schema or returns an unexpected page, the run fails clearly instead of silently producing misleading empty data.

### Data extracted

| Field | Description |
| --- | --- |
| `permitNumber` | Published TTB permit identifier |
| `ownerName` | Legal owner name |
| `operatingName` | Operating or trade name, when published |
| `street` | Premises street address |
| `city` | Premises city |
| `state` | Two-letter state or territory code |
| `zipCode` | Premises ZIP code |
| `county` | Premises county, when published |
| `industryType` | TTB industry classification |
| `isNewPermit` | Whether the source marks the record as new |
| `fullAddress` | Combined premises address for convenient export |
| `sourceUrl` | Exact official CSV URL used by the run |
| `sourcePageUrl` | Official TTB permittee-list page |
| `fetchedAt` | UTC retrieval timestamp |

Blank values from the regulator are preserved as empty strings. The Actor does not infer missing trade names, counties, or permit status.

### Getting started

1. Open the Actor in Apify Console.
2. Enter a broad `query`, exact geography filters, permit numbers, or enable `newPermitsOnly`.
3. Set `maxItems` to control the maximum number of accepted records.
4. Click **Start**.
5. Review the **Dataset** tab or export results as JSON, CSV, Excel, XML, or RSS.
6. Save the run as an Apify Task if you want a stable recurring workflow.

A useful first input is:

```json
{
  "states": ["KY"],
  "maxItems": 50
}
```

### Input parameters

| Input | Type | Default | Behavior |
| --- | --- | --- | --- |
| `query` | string | empty | Case-insensitive substring search across identity, address, and industry fields |
| `permitNumbers` | string\[] | all | Exact permit-number match |
| `states` | string\[] | all | Exact two-letter state or territory match |
| `counties` | string\[] | all | Exact county-name match, case-insensitive |
| `cities` | string\[] | all | Exact city-name match, case-insensitive |
| `industryTypes` | string\[] | all | Exact TTB industry-type match |
| `newPermitsOnly` | boolean | false | Keep only rows whose source flag is `1` |
| `maxItems` | integer | 100 | Save between 1 and 10,000 accepted rows |

Filters combine with **AND** logic. Values within one array use **OR** logic. For example, `states: ["KY", "TN"]` plus `query: "BOURBON"` returns rows in either state that also contain the search text.

### Filter examples

Search names and addresses for bourbon-related text:

```json
{
  "query": "BOURBON",
  "maxItems": 25
}
```

Look up exact permits:

```json
{
  "permitNumbers": ["KY-S-10", "AK-S-15000"],
  "maxItems": 10
}
```

Build a geographic supplier list:

```json
{
  "states": ["KY"],
  "counties": ["NELSON"],
  "maxItems": 500
}
```

Find records marked new in TTB's latest published roster:

```json
{
  "newPermitsOnly": true,
  "maxItems": 100
}
```

### Output example

A dataset item follows this shape:

```json
{
  "permitNumber": "KY-S-20016",
  "ownerName": "EXAMPLE DISTILLING COMPANY LLC",
  "operatingName": "EXAMPLE SPIRITS",
  "street": "100 DISTILLERY WAY",
  "city": "BARDSTOWN",
  "state": "KY",
  "zipCode": "40004",
  "county": "NELSON",
  "industryType": "Distilled Spirits Plant",
  "isNewPermit": false,
  "fullAddress": "100 DISTILLERY WAY, BARDSTOWN, KY, 40004",
  "sourceUrl": "https://www.ttb.gov/system/files/2025-04/FRL_Spirits_Producers_and_Bottlers_List.csv",
  "sourcePageUrl": "https://www.ttb.gov/public-information/foia/list-of-permittees",
  "fetchedAt": "2026-09-12T06:00:00.000Z"
}
```

The example anonymizes business identity while preserving the real output types. Actual runs return the public values supplied by TTB.

### How much does it cost to export TTB spirits permittees?

Pay-per-event pricing has a one-time **$0.00005 run start** and an item fee based on your Apify plan:

| Tier | Price per saved permittee |
| --- | ---: |
| Free | $0.00253 |
| Bronze | $0.00220 |
| Silver | $0.001716 |
| Gold | $0.00132 |
| Platinum | $0.00132 |
| Diamond | $0.00132 |

At the Bronze rate, 25 saved permittees cost about **$0.05505**, 100 cost about **$0.22005**, and 1,000 cost about **$2.20005**, including the start event. Failed, filtered-out, and duplicate rows do not incur the item event.

Apify platform compute is handled by the Actor's pricing model. Check the live pricing panel before a large run because platform pricing can change.

### Scheduling compliance refreshes

Create an Apify Task with a stable filter, then add a weekly or monthly schedule. Each run receives the latest roster URL advertised by the official TTB page and stamps every row with `fetchedAt` and `sourceUrl`.

For change monitoring, retain datasets in your destination and compare records by `permitNumber`. The `isNewPermit` field reflects TTB's source marker; it is not a complete historical change log. Your own snapshots are the durable way to detect changed names or addresses.

### Integrations and exports

Use Apify integrations to send results to:

- Google Sheets for a review queue;
- Make or Zapier for supplier onboarding;
- webhooks for downstream validation;
- cloud storage or a data warehouse for snapshots;
- Python, JavaScript, or any HTTP client through the Apify API.

The stable deduplication key is `permitNumber`. Keep `sourceUrl` and `fetchedAt` when audit provenance matters.

### Run with the API using cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~ttb-distilled-spirits-plant-permittees/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"states":["KY"],"maxItems":50}'
```

For synchronous dataset output, use the `run-sync-get-dataset-items` endpoint when the requested volume fits your client timeout.

### Run with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/ttb-distilled-spirits-plant-permittees').call({
  query: 'BOURBON',
  maxItems: 25,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/ttb-distilled-spirits-plant-permittees').call(
    run_input={'newPermitsOnly': True, 'maxItems': 100}
)
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use through MCP

Add the Apify MCP server to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/ttb-distilled-spirits-plant-permittees"
```

For **Claude Desktop**, add this JSON under `mcpServers` in the desktop configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/ttb-distilled-spirits-plant-permittees"
    }
  }
}
```

For **Cursor**, open MCP settings and add the same `apify` server URL. For **VS Code**, add the URL as an HTTP MCP server in your workspace or user MCP configuration.

Example prompts:

- “Export 50 Kentucky TTB distilled spirits plant permittees.”
- “Find current TTB spirits roster records containing bourbon.”
- “Return permits marked new in the latest TTB producer list.”

### Reliability and source behavior

The Actor fetches two lightweight official resources: the TTB permittee index and its current spirits CSV. Transient timeouts, 429 responses, and temporary server errors receive bounded retries. It does not use proxies or browser automation.

The run intentionally fails when:

- TTB returns a non-success status after retries;
- the page or CSV has an unexpected content shape;
- required columns disappear or unknown columns appear;
- a CSV row has the wrong number of fields;
- input values violate documented limits.

A valid query with no matches succeeds with an empty dataset.

### Limits and interpretation

- Coverage is limited to the current public TTB spirits producers and bottlers file.
- TTB controls publication timing, field contents, and the meaning of its new-permit flag.
- Presence in the list is not legal advice and does not prove present good standing for every purpose.
- The roster does not provide a complete permit history, revocation history, application status, contacts, or ownership graph.
- Exact geography filters depend on regulator spelling. Use `query` when you need broader text matching.
- `maxItems` limits accepted rows, not rows scanned in the downloaded source.

### Responsible use

The Actor processes a public US government roster. Use results for lawful compliance, research, procurement, and data-quality purposes. Verify consequential decisions against the linked official TTB source and applicable rules. Do not present an Actor result as legal advice or as a substitute for regulator confirmation.

Respect Apify's terms, TTB's website policies, and any laws governing your downstream use, retention, matching, or redistribution of records.

### Troubleshooting

**Why did my run return zero records?**

Filters combine with AND logic. Remove filters one at a time, verify two-letter state codes, and check regulator spelling for cities and counties. A valid no-match input produces an empty dataset.

**Why did the run fail after TTB changed its page?**

The Actor fails closed when it cannot validate the source or schema. Review the run log for HTTP, content, or changed-column details and retry after the official file is available again.

**Why is `operatingName` or `county` blank?**

TTB does not publish those values on every row. The Actor preserves the source rather than inventing missing data.

### Frequently asked questions

**Does this search TTB COLAs Online?**\
No. COLA label approvals are a separate dataset and workflow.

**Does `isNewPermit` mean the permit was issued today?**\
No. It mirrors TTB's “new permit” marker in the current file. TTB controls the publication interval.

**Can I download the full roster?**\
Yes. Leave filters empty and set `maxItems` high enough for the current source volume, up to 10,000.

**Can I filter several states?**\
Yes. Values within `states` are ORed, while different filter fields are ANDed.

**Are filtered-out rows charged?**\
No. The per-item event applies only to records saved to the dataset.

### Related Actors

- [NPPES NPI Registry Provider Search](https://apify.com/automation-lab/npi-registry-provider-search) for US healthcare provider verification.
- [Florida Sunbiz Business Search](https://apify.com/automation-lab/florida-sunbiz-business-search) for Florida company identity and filing research.
- [FCC Amateur Radio License Search](https://apify.com/automation-lab/fcc-amateur-radio-license-search) for official FCC license records.

These are separate official-registry workflows and do not enrich or alter TTB permittee results automatically.

# Actor input Schema

## `query` (type: `string`):

Case-insensitive text matched across permit number, owner and operating names, address, county, and industry type.

## `permitNumbers` (type: `array`):

Return only exact TTB permit numbers, such as KY-S-20016.

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

Return only two-letter US state or territory codes, matched exactly.

## `counties` (type: `array`):

Return only exact premises county names; matching is case-insensitive.

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

Return only exact premises city names; matching is case-insensitive.

## `industryTypes` (type: `array`):

Return only exact TTB industry types. The spirits roster currently uses Distilled Spirits Plant.

## `newPermitsOnly` (type: `boolean`):

Return only rows marked by TTB as new since the previous roster publication.

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

Maximum matching permittee records saved to the dataset.

## Actor input object example

```json
{
  "query": "BOURBON",
  "newPermitsOnly": false,
  "maxItems": 20
}
```

# Actor output Schema

## `dataset` (type: `string`):

Filtered TTB distilled spirits plant permittees

# 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 = {
    "query": "BOURBON",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/ttb-distilled-spirits-plant-permittees").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 = {
    "query": "BOURBON",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/ttb-distilled-spirits-plant-permittees").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 '{
  "query": "BOURBON",
  "maxItems": 20
}' |
apify call automation-lab/ttb-distilled-spirits-plant-permittees --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/ttb-distilled-spirits-plant-permittees"
        }
    }
}
```

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/zHFAZt9Q4hDnlZ0KD/builds/iOL75ivude9EKbqHv/openapi.json
