# CFPB Consumer Complaints Scraper - Companies, Products & Issues (`groundplanes/cfpb-consumer-complaints-feed`) Actor

Scrape consumer financial complaints from the official CFPB Consumer Complaint Database API, filtered by company, financial product, state, and date received.

- **URL**: https://apify.com/groundplanes/cfpb-consumer-complaints-feed.md
- **Developed by:** [Groundplane Labs](https://apify.com/groundplanes) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 complaints

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

## CFPB Consumer Complaints Scraper - Companies, Products & Issues

Extract consumer financial complaints directly from the official Consumer Financial Protection Bureau (**CFPB**) Consumer Complaint Database Search API (`https://www.consumerfinance.gov/data-research/consumer-complaints/search/api/v1/`). Filter by financial institution, product category, state, and receipt date to analyze complaint volume, issue categories, company response classifications, and timeliness.

> **Disclaimer:** This Actor is an independent tool and is not affiliated with or endorsed by the Consumer Financial Protection Bureau (CFPB).

***

### What it does

- **Queries the Official CFPB Search API v1:** Connects directly to `https://www.consumerfinance.gov/data-research/consumer-complaints/search/api/v1/` using `sort=created_date_desc` and cursor pagination (`search_after`) so queries can page cleanly beyond Elasticsearch's 10,000-row offset window.
- **Company, Product, State & Date Filtering:** Supports filtering by one or more CFPB company names, financial product categories (`Credit card`, `Mortgage`, `Checking or savings account`, `Credit reporting or other personal consumer reports`, etc.), US state codes, and `dateFrom`.
- **Narratives (currently unavailable from the source):** The CFPB removed consumer complaint narratives from the database (see the CFPB API release notes). The `narrative` field is therefore null for every row at present, whatever `includeNarrative` is set to. The input and field are kept for compatibility if the CFPB restores narratives.
- **Schema Validation & Newest-First Ordering:** Validates every record before output, deduplicates by `complaintId`, and sorts newest first by `dateReceived` descending (with `complaintId` descending tiebreaker) before applying `maxItems`.

***

### Who it's for

- **Banking & Fintech Compliance Teams:** Monitor complaint trends, issue classifications, and timely response metrics across peer financial institutions.
- **Financial Services Product & Operations Analysts:** Identify recurring operational issues across credit cards, mortgages, auto loans, checking accounts, and credit reporting.
- **Academic & Policy Researchers:** Analyze consumer financial protection trends by state, 3-digit/5-digit ZIP prefix, submission channel, and company response type.

***

### Input

All parameters have working defaults/prefills that return results in under 2 minutes with no proxy and no API key.

| Field | Type | Default / Prefill | Description |
|---|---|---|---|
| `companies` | `array<string>` | `["CAPITAL ONE FINANCIAL CORPORATION"]` | Exact CFPB company names (e.g. `["CAPITAL ONE FINANCIAL CORPORATION"]`, `["JPMORGAN CHASE & CO."]`). Leave empty for all companies. |
| `products` | `array<string>` | `[]` | Optional CFPB product categories (e.g. `["Credit card", "Mortgage"]`). Leave empty for all products. |
| `states` | `array<string>` | `[]` | Two-letter US state or territory codes (e.g. `["AZ", "CA", "FL"]`). Leave empty for all states. |
| `dateFrom` | `string` | `""` (today − 30 days) | Earliest `date_received` in `YYYY-MM-DD` format. Defaults to 30 days ago when blank. |
| `includeNarrative` | `boolean` | `false` | Kept for compatibility. The CFPB no longer publishes complaint narratives, so `narrative` is null for every row even when this is `true`. Defaults to `false`. |
| `maxItems` | `integer` | `200` | Maximum number of complaint records to return. |

#### Example input JSON

```json
{
  "companies": ["CAPITAL ONE FINANCIAL CORPORATION"],
  "products": [],
  "states": [],
  "dateFrom": "",
  "includeNarrative": false,
  "maxItems": 200
}
```

#### Example run options (with default $50 max charge protection)

When invoking via the Apify API or CLI, set a default maximum charge of **$50** (`"maxTotalChargeUsd": 50`):

```json
{
  "maxTotalChargeUsd": 50
}
```

The Actor checks `calculate_max_event_charge_count_within_limit("apify-default-dataset-item")` before every dataset push and stops cleanly when your spending limit is reached.

***

### Output

Results are saved to the default dataset, deduplicates by `complaintId`, and sorted by `dateReceived` descending.

#### Output fields

| Field | Type | Description |
|---|---|---|
| `complaintId` | `string` | Unique CFPB `complaint_id`. Natural deduplication key. |
| `dateReceived` | `string` | Date the CFPB received the complaint (`YYYY-MM-DD`). |
| `dateSentToCompany` | `string \| null` | Date the CFPB sent the complaint to the company (`YYYY-MM-DD`). |
| `company` | `string` | Financial institution or company name. |
| `product` | `string \| null` | Primary financial product category. |
| `subProduct` | `string \| null` | Sub-product classification. |
| `issue` | `string \| null` | Primary consumer issue reported. |
| `subIssue` | `string \| null` | Detailed sub-issue reported. |
| `state` | `string \| null` | Two-letter US state or territory code. |
| `zipCode` | `string \| null` | ZIP code as published by the CFPB (full 5-digit code where published; null where the CFPB suppresses it, e.g. low-population areas). |
| `submittedVia` | `string \| null` | Submission channel (`Web`, `Referral`, `Phone`, `Postal mail`, etc.). |
| `companyResponse` | `string \| null` | Company response classification (`Closed with explanation`, `Closed with monetary relief`, `In progress`, etc.). |
| `companyPublicResponse` | `string \| null` | Optional public response statement chosen by the company. |
| `timely` | `string \| null` | Whether the company responded within the CFPB timeframe (`Yes` / `No`). |
| `consumerDisputed` | `string \| null` | Historical consumer dispute flag. Always null at present: the CFPB removed this field from the database export and API. |
| `tags` | `string \| null` | Special demographic or statutory tags (`Older American`, `Servicemember`, etc.). |
| `narrative` | `string \| null` | Consumer complaint narrative. Always null at present: the CFPB removed narratives from the database. Kept in the schema for compatibility if the CFPB restores them. |
| `complaintUrl` | `string` | Direct URL to the CFPB complaint detail record (`https://www.consumerfinance.gov/data-research/consumer-complaints/search/detail/<complaintId>`). |

#### Sample output row (synthetic example)

```json
{
  "complaintId": "99990001",
  "dateReceived": "2026-10-29",
  "dateSentToCompany": "2026-10-29",
  "company": "EXAMPLE NATIONAL BANK CORP.",
  "product": "Credit card",
  "subProduct": "General-purpose credit card or charge card",
  "issue": "Problem with a purchase shown on your statement",
  "subIssue": "Credit card company isn't resolving a dispute about a purchase on your statement",
  "state": "AZ",
  "zipCode": "85001",
  "submittedVia": "Web",
  "companyResponse": "Closed with explanation",
  "companyPublicResponse": "Company has responded to the consumer and the CFPB and chooses not to provide a public response",
  "timely": "Yes",
  "consumerDisputed": null,
  "tags": null,
  "narrative": null,
  "complaintUrl": "https://www.consumerfinance.gov/data-research/consumer-complaints/search/detail/99990001"
}
```

***

### How pricing works

Pay per result. You only pay for rows returned.

***

### Data source and terms

- **Official Source:** Consumer Financial Protection Bureau (CFPB), [Consumer Complaint Database](https://www.consumerfinance.gov/data-research/consumer-complaints/)
- **Source changes to know about:** Per the [CFPB API release notes](https://cfpb.github.io/api/ccdb/release-notes.html), the CFPB has removed consumer complaint narratives from the database and removed the "Consumer disputed" and "Consumer consent provided" fields from its export. This Actor therefore returns `narrative` and `consumerDisputed` as null on every row, by disclosure, until and unless the CFPB restores them.
- **API Endpoint:** `https://www.consumerfinance.gov/data-research/consumer-complaints/search/api/v1/`
- **Update Cadence:** Updated daily by the CFPB after complaints are forwarded to the company and the company responds (or after 15 days).
- **Terms of Use:** U.S. Government public domain data published under [CFPB Privacy & Open Data Policies](https://www.consumerfinance.gov/privacy/). No personal consumer identifiers are published by the CFPB.

***

### Compliance

This Actor reads public complaint metadata published by the CFPB. The data is aggregated public reporting: it carries no consumer contact details, and the CFPB no longer publishes complaint narratives or the consumer-disputed field in this database. If the CFPB restores narratives in a future release, the output fields will remain gated by the includeNarrative input and the privacy rules in this README. Buyers remain responsible for complying with the TCPA, the National Do-Not-Call Registry, CAN-SPAM, the GDPR where it applies, and all state and local privacy and telemarketing law before contacting any party named in the data.

### Limits, Troubleshooting and FAQ

- **Why is `narrative` null even when `includeNarrative` is `true`?**
  The CFPB has removed consumer complaint narratives from the database entirely (see its API release notes), so no narrative text is available from the source at present. The field stays in the output schema, null, for compatibility if the CFPB restores narratives.
- **What happens when no complaints match my filters?**
  The run succeeds with a `Complete: 0 rows matched your filters` status message that names the filters used and suggests how to widen them. Zero matches is not an error.
- **Run limits:** Each run scans at most 50 API pages (5,000 records) within a 240 second scan budget and at most 2 requests per second, per the CFPB API terms of use. Hitting the page cap or the scan budget ends the run with a `PARTIAL:` status message naming the cap.
- **What does a `PARTIAL:` status message mean?**
  If the upstream CFPB API experiences temporary latency or schema drift mid-run, the Actor preserves all validated rows already collected and sets a `PARTIAL:` status message with per-source counts. If this keeps happening, please open an issue on this actor's Issues tab and we'll fix it fast.
- **Support:** Found a problem or need a field? Open an issue on the Issues tab; we respond quickly.

***

### Changelog

#### v0.1.2 (2026-10-31)

- Red Team correction round: the core-column sentinel now runs on every page, not just page 1, so a field renamed on a later page fails loudly instead of shipping null-filled rows (verified by a page-2 rename fixture). A run with over 20 percent bad rows now fails and pushes nothing. Non-string source values in mapped text fields count as bad rows instead of being silently stringified. The charge-limit PARTIAL now merges any accumulated source-caused reasons, and maxItems rejects JSON booleans. Compliance section added.

#### v0.1.1 (2026-10-31)

- Fixed: zero matches now succeeds with a `Complete: 0 rows matched your filters` message instead of failing the run.
- Fixed: the Issues tab call to action now appears only on `PARTIAL:` runs with a source-caused reason, and the max-charge stop now reports as `PARTIAL:` with the rows pushed.
- Added: first-page core-column sentinel over every `_source` field the Actor depends on (filters, dedupe key, sort key); a missing or renamed field now fails loudly, naming the fields.
- Added: source staleness check using the API response metadata (`is_data_stale`), reported as a `PARTIAL:` reason when the CFPB flags its own data stale.
- Disclosed: the CFPB has removed complaint narratives and the "Consumer disputed" field from the database; `narrative` and `consumerDisputed` are null on every row until the CFPB restores them. Corrected the ZIP code description (full 5-digit code as published, not 3-digit masked).
- Added `defaultRunOptions` (max charge $50, 256 MB, 300 s) and a real `exampleRunInput` matching the input prefill.

#### v0.1 (2026-10-31)

- Initial release supporting CFPB Consumer Complaint Search API v1 with `search_after` cursor pagination, schema validation, and optional narrative extraction.

# Actor input Schema

## `companies` (type: `array`):

Exact CFPB company names to filter by (e.g. \["CAPITAL ONE FINANCIAL CORPORATION"], \["BANK OF AMERICA, NATIONAL ASSOCIATION", "JPMORGAN CHASE & CO."]). Leave empty for all companies.

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

Earliest complaint date_received in YYYY-MM-DD format. Leave blank to default to 30 days ago (today - 30 days).

## `includeNarrative` (type: `boolean`):

Defaults to false. Note: the CFPB removed consumer complaint narratives from the database, so the narrative field is currently null for every row even when this is true. The toggle is kept for compatibility if the CFPB restores narratives.

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

Maximum number of complaint records to return, sorted newest first by dateReceived.

## `products` (type: `array`):

Optional CFPB product categories (e.g. \["Credit card", "Checking or savings account", "Mortgage", "Credit reporting or other personal consumer reports"]). Leave empty for all products.

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

Two-letter US state or territory postal codes (e.g. \["AZ", "TX", "CA"]). Leave empty for all states.

## Actor input object example

```json
{
  "companies": [
    "CAPITAL ONE FINANCIAL CORPORATION"
  ],
  "dateFrom": "",
  "includeNarrative": false,
  "maxItems": 200,
  "products": [],
  "states": []
}
```

# 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 = {
    "companies": [
        "CAPITAL ONE FINANCIAL CORPORATION"
    ],
    "dateFrom": "",
    "includeNarrative": false,
    "maxItems": 200,
    "products": [],
    "states": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("groundplanes/cfpb-consumer-complaints-feed").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 = {
    "companies": ["CAPITAL ONE FINANCIAL CORPORATION"],
    "dateFrom": "",
    "includeNarrative": False,
    "maxItems": 200,
    "products": [],
    "states": [],
}

# Run the Actor and wait for it to finish
run = client.actor("groundplanes/cfpb-consumer-complaints-feed").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 '{
  "companies": [
    "CAPITAL ONE FINANCIAL CORPORATION"
  ],
  "dateFrom": "",
  "includeNarrative": false,
  "maxItems": 200,
  "products": [],
  "states": []
}' |
apify call groundplanes/cfpb-consumer-complaints-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,groundplanes/cfpb-consumer-complaints-feed"
        }
    }
}
```

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/NOhNeri3mWa9Md0Z8/builds/isq44PHvjvQEwKY0i/openapi.json
