# WA Attorney General Consumer Complaints (`j0401/wa-ag-complaints`) Actor

Washington Attorney General consumer complaints (public open data, 281k records, daily): complaints filed against a business - category, NAICS, status, the dollars the AG recovered, opened by year. Screen a company's complaint history or aggregate by industry / NAICS / year.

- **URL**: https://apify.com/j0401/wa-ag-complaints.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.06 / 1,000 wa ag consumer complaint records

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?

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

## Washington Attorney General Consumer Complaints

Every consumer complaint filed with the Washington Attorney General's office is public record - and the office publishes the whole file as open data. This actor turns it into a **charged-per-record lookup, filter and aggregate tool**: pull the complaints filed against a business, sweep an industry, isolate the live cases, or range over the dollars the AG recovered.

**Built for:** vendors and partners screening a company's complaint history, consumer-protection and compliance teams, market researchers reading an industry's grievance mix, and anyone who needs the **complaint record behind a business** - not a review-site score.

### What it covers

**281,333 complaints** filed against Washington businesses - **20,110 so far in 2026**, running roughly 23-26k a year in recent years (16,005 back in 2019). Each row is one complaint:

- the **business** it was filed against (**216,825 rows carry a name**), with street, city, state and ZIP
- the **industry category** - Electronic Shopping (18,562), Auto Sales (17,443), Telecommunications (15,846), Retail Sales (15,423), Health Care (12,734), Broadband Providers (11,979), Contractors (10,982), and more
- the **NAICS code and name** (**281,245 rows carry a NAICS**), split from the source's compound text into a clean code and label
- the **status** - Closed (276,961), New (2,585), Open (1,787)
- the **dollars recovered** for the consumer - estimated and actual savings, present on **44,532 complaints**
- the **opened date/year**, and the business's **geocode** (latitude/longitude) where the address resolves

### The fine print that matters

Three source quirks are handled for you. The business address fields arrive **whitespace-padded** (`businessZip` = `"         "`, `businessState` = `"  "`), so every value is trimmed - a blank is a real blank, and a ZIP filter matches the digits you'd expect. The **`naics` column is a compound string** (`"522291-Consumer Lending (includes Payday Lenders)"`) - the actor splits it into `naicsCode` and `naicsName`, so a `naics=522291` filter returns Consumer Lending and nothing else. And `businessId` is the **source's own 6-digit id - not a WA UBI** (the UBI is 9 digits); it is surfaced as an opaque id and never claimed as a universal business key.

The **recovered-dollars** angle is the one no directory has: the AG reports what it actually recovered for consumers, and **44,532 complaints carry an amount** - filter `hasSavings` or a minimum to find the cases with money behind them.

### Typical questions

- "Every complaint filed against **Comcast**."
- "**Auto Sales** complaints over the last year."
- "**NAICS 522291** (payday lending) complaints in WA."
- "Complaints where the AG recovered **more than $5,000**."
- "Aggregate complaints by **category**, **NAICS** or **year**."

### Inputs

| Input | What it does |
|---|---|
| `mode` | `rows` (default) / `aggregate` |
| `status` / `business` / `category` / `naics` | who and what |
| `city` / `state` / `zipcode` | where the business is |
| `openedFrom` / `openedTo` | opened-date range |
| `minSavings` / `minActualSavings` / `hasSavings` | the recovered-dollars angle |
| `groupBy` | aggregate over category / status / NAICS / state / year |
| `maxResults` | cap records (default 50) |

**Default run = the 50 most recently opened complaints** - fast for the daily auto-test. For a targeted query add a filter; for a broad view use `aggregate`.

### Example inputs

**One company's complaint history** - `business` is a case-insensitive substring.

```json
{ "business": "Amazon.com", "maxResults": 5 }
```

**The live cases** - `New` and `Open` are the complaints still moving.

```json
{ "status": "Open", "maxResults": 10 }
```

**An industry over a date window** - `category` is a substring, `openedFrom` an ISO date.

```json
{ "category": "Auto Sales", "openedFrom": "2025-01-01", "maxResults": 10 }
```

**Complaints with money recovered** - `hasSavings` keeps only the rows where the AG recovered an amount.

```json
{ "hasSavings": true, "minSavings": 5000, "maxResults": 10 }
```

### Low cost

**From $0.0001 per record, down to $0.00006 at Gold** - billed only for the rows you use, at the low end of the store. Cost scales with what you pull, not with the size of the register, and each record is metered individually.

The traps are in the cleaning. The address fields are **padded with runs of spaces**, so a naive `zip = '98004'` match returns nothing and a filter that forgets to trim silently drops every row; the industry is stored as a **compound code-and-name string** that has to be split before a NAICS filter means anything; and the `business_id` looks like a business key but is **not the state's UBI**, so treating it as one silently mis-joins two different companies. Normalizing the padded address, the compound NAICS and the mis-labeled id into a schema where a `business` / `category` / `zipcode` query returns exactly the complaints you mean is the actual product. Every pull is integrity-checked against the register's known shape, so a degraded source fails loudly instead of returning bad rows.

### Example output

**One complaint** - `business="Amazon.com"` returns records like this one:

```json
{
  "platform": "wa-ag-complaints",
  "source": "wa-attorney-general-consumer-complaints",
  "mode": "rows",
  "groupKey": "",
  "groupCount": "",
  "groupBy": "",
  "complaintId": "733076",
  "businessId": "120419",
  "openedDate": "2026-09-15",
  "openedYear": "2026",
  "status": "New",
  "business": "Amazon.com",
  "category": "Electronic Shopping",
  "naics": "454100-Electronic Shopping & Mail Order Houses",
  "naicsCode": "454100",
  "naicsName": "Electronic Shopping & Mail Order Houses",
  "streetLine1": "PO Box 81226",
  "streetLine2": "",
  "city": "Seattle",
  "state": "WA",
  "zipcode": "98108-1226",
  "estimatedSavings": "0.00",
  "actualSavings": "0.00",
  "latitude": "",
  "longitude": ""
}
```

**`mode=aggregate`, `groupBy=category`** - one row per industry group (`groupKey` + `groupCount`):

```
Electronic Shopping   18,562
Auto Sales            17,443
Telecommunications    15,846
Retail Sales          15,423
```

### Source

- [Washington Open Data: Attorney General Consumer Complaints](https://data.wa.gov/Consumer-Protection/Attorney-General-Consumer-Complaints/gpri-47xz) - complaints filed with the WA AG office, refreshed daily. Public open data. Records are the office's own published fields; a complaint is an allegation, not a finding of wrongdoing.

# Actor input Schema

## `mode` (type: `string`):

rows = complaints matching your filters (default). aggregate = one count row per group (see groupBy).

## `status` (type: `string`):

Complaint status. New + Open = live (~4.4k of 281k). Blank = any.

## `business` (type: `string`):

Business name substring, e.g. 'Comcast'. Screens one company's complaint history.

## `category` (type: `string`):

Industry category substring, e.g. 'Auto Sales', 'Telecommunications', 'Contractors'. Blank = any.

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

NAICS code substring, e.g. '522291'. Blank = any.

## `city` (type: `string`):

Business city substring. Blank = any.

## `state` (type: `string`):

Business state (2-letter, e.g. 'WA'). Blank = any.

## `zipcode` (type: `string`):

Business ZIP code.

## `openedFrom` (type: `string`):

Only complaints opened on/after this date (YYYY-MM-DD).

## `openedTo` (type: `string`):

Only complaints opened before this date. ISO date, YYYY-MM-DD (e.g. 2026-01-01).

## `minSavings` (type: `number`):

Minimum estimated savings recovered for the consumer (USD).

## `minActualSavings` (type: `number`):

Minimum actual savings recovered for the consumer (USD). ~40.5k rows carry an actual amount.

## `hasSavings` (type: `boolean`):

When on, returns only complaints where the AG recovered an estimated or actual amount.

## `groupBy` (type: `string`):

Which dimension to aggregate over. category -> complaint mix by industry; openedYear -> volume over time.

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

Cap the number of records pushed (0 = up to ~10k per run; each record is metered individually, so there is no per-run charge cap). Aggregate mode returns every group; maxResults applies to rows only.

## Actor input object example

```json
{
  "mode": "rows",
  "status": "",
  "business": "",
  "category": "",
  "naics": "",
  "city": "",
  "state": "",
  "zipcode": "",
  "openedFrom": "",
  "openedTo": "",
  "hasSavings": false,
  "groupBy": "category",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Washington AG consumer complaint records or aggregates - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/wa-ag-complaints").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/wa-ag-complaints").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 '{}' |
apify call j0401/wa-ag-complaints --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/wa-ag-complaints"
        }
    }
}
```

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/7mTGgCsBcJc8AttXY/builds/19lRl1dt79oaDkhG1/openapi.json
