# Nonprofit Financials & Filings (`dataio/nonprofit-financials-filings`) Actor

US nonprofits with their finances attached: revenue, expenses, assets, operating margin and multi-year growth from IRS 990 filings, with NTEE and 501(c) codes decoded.

- **URL**: https://apify.com/dataio/nonprofit-financials-filings.md
- **Developed by:** [Tom Awake](https://apify.com/dataio) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$6.00 / 1,000 organizations

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?

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

## Nonprofit Financials & Filings

US nonprofits **with their finances attached** — revenue, expenses, assets,
operating margin and multi-year growth, from IRS Form 990 filings.

ProPublica's Nonprofit Explorer holds 1.8 million tax-exempt US
organisations. It exposes two things that do not talk to each other: a
**search** that returns a directory — name, city, NTEE code, and *no
figures at all* — and a **per-organisation record** that holds the money.
Searching for "food banks in California" and wanting their revenue means
hundreds of manual lookups.

This Actor does the lookups.

No API key. No account.

***

### The four things this does that the source does not

**1. It joins the directory to the money.**

Every search result is enriched with its organisation record and its latest
filing. That is one extra request per organisation, measured at **0.27
seconds each with no rate limiting observed** — a thousand organisations
takes about five minutes, and you do none of it by hand.

**2. It finds the revenue that is actually there.**

`revenue_amount` on the organisation record is populated on only **43%** of
organisations. `totrevenue` on the latest filing is populated on **95.8%**.
We take the best available source and record which one in
`revenueSource`, so a figure is never silently of a different kind than it
looks.

Measured on a 120-organisation run: 103 revenues came from the latest
filing, 14 from the organisation record, 3 had none — **97.5% coverage**
against 43% from the obvious field.

**3. It decodes the codes.**

`P210` and `subsection_code: 3` are not answers. They are *Human Services*
and *501(c)(3) Charitable, religious or educational*. All 26 NTEE
categories and the common 501(c) subsections are decoded, plus
`donationsDeductible`, which is the first thing a donor needs to know.

**4. It turns a stack of filings into a trajectory.**

An organisation carries up to 14 filings (median 8). The source lists them;
it never compares them. `revenueGrowth1yPct` and `revenueGrowth3yPct` match
the same fiscal years rather than adjacent rows, because filing years skip.

Growth is left empty below a $10,000 starting base. An organisation that
went from $1,000 to $1M is arithmetically up 99,694%, which is accounting
noise, and it filled the top of every sort.

***

### Field coverage

Measured on a 120-organisation run.

| Field | Coverage |
|---|---|
| `ein`, `name` | 100% |
| `address`, `city`, `state`, `zipcode` | 99.2% |
| `taxExemptType` | 96.7% |
| `rulingDate`, `yearsRegistered` | 94.2% |
| `totalRevenue` (with fallback) | 97.5% |
| `totalAssets` | 85.8% |
| `fiscalYear` | 85.8% |
| `netAssets`, `operatingMargin` | 83–84% |
| `nteeCategory` | 83.3% |
| `revenueGrowth1yPct` | 76.7% |
| `contributionsReceived` | 55.8% |
| `officerCompensation` | 30.8% |

***

### What it is for

- **Fundraising and grant seeking.** Filter by sector and revenue to find
  foundations that actually have money to give, with their mailing address
  and the size of their last grant-making year.
- **Selling to the nonprofit sector.** `totalRevenue` and
  `salariesAndWages` size an organisation far better than its name does.
- **Due diligence and donor research.** `operatingMargin`,
  `netAssets` and three-year growth show whether an organisation is
  running a surplus or eating its reserves. 20 of 73 in one sample were
  running a deficit.
- **Sector research.** NTEE category plus revenue, aggregated across
  thousands of organisations.

Three dataset views ship with the Actor: **Organisations**, **Financials**
and **Outreach list**.

***

### Limits

Stated plainly, because they affect what you can conclude.

- **990 data is old, and the Actor tells you how old.** Median filing age
  in a typical run is **3 years**. The IRS publishes returns well after
  the fiscal year, and an organisation that merged or dissolved simply
  stops filing. `filingAgeYears` carries this on every row — never read a
  revenue figure without it.
- **Small organisations file a postcard.** Organisations under the filing
  threshold submit a 990-N, which carries no financial data at all. That
  is most of the 3% with no revenue figure; `withFinancialsOnly` removes
  them.
- **Negative revenue is real.** A foundation whose investment losses
  exceed its contributions reports negative total revenue. Those rows are
  not errors and are kept.
- **Search results cap at 10,000.** Broad queries report exactly 10,000
  matches, which is the ceiling rather than the true count. Narrow by
  state or sector to get past it.
- **One request per state.** The API accepts a single state at a time, so
  listing ten states makes ten times the search requests.
- **`officerCompensationPct` is dropped when negative.** The source
  occasionally reports a negative share of compensation. A negative share
  has no meaning, so the field is left empty rather than filled with it.
- **EIN is the join key, not the name.** Nonprofit names repeat heavily
  across states and chapters. Match on `ein`.

***

### Source

[ProPublica Nonprofit Explorer](https://projects.propublica.org/nonprofits/),
built on IRS Form 990 data and the IRS Exempt Organizations Business Master
File. Public, no authentication. This Actor is not affiliated with
ProPublica; please credit them if you republish the data.

# Actor input Schema

## `searchText` (type: `string`):

Organisation name or keyword, for example food bank, cancer research, animal shelter. Can be left empty if you set a category, state or subsection instead.

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

Two-letter state codes, for example CA, NY, TX. One request per state is made, so listing many states makes the run longer.

## `nteeCategory` (type: `string`):

The NTEE major group. Leave empty for all sectors.

## `subsectionCode` (type: `integer`):

3 for 501(c)(3) charities, where donations are tax-deductible. 4 for social welfare, 6 for business leagues and chambers of commerce, 7 for social clubs, 19 for veterans' organisations.

## `minRevenue` (type: `integer`):

Applied after enrichment, on the best available revenue figure. Organisations with no known revenue are excluded when this is set.

## `withFinancialsOnly` (type: `boolean`):

Drops organisations with no revenue figure at all — around 3% of results. Small and newly registered organisations file a postcard return that carries no financial data.

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

Each organisation costs one extra request to enrich with its financials, at roughly 0.27s each, so 1,000 organisations takes about five minutes.

## Actor input object example

```json
{
  "searchText": "cancer research",
  "withFinancialsOnly": false,
  "maxItems": 200
}
```

# Actor output Schema

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

Every row produced by the run, in JSON.

# 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 = {
    "searchText": "cancer research",
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataio/nonprofit-financials-filings").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 = {
    "searchText": "cancer research",
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("dataio/nonprofit-financials-filings").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 '{
  "searchText": "cancer research",
  "maxItems": 200
}' |
apify call dataio/nonprofit-financials-filings --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataio/nonprofit-financials-filings"
        }
    }
}
```

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/03aHHbZjwSvyrVyFq/builds/uoPiA1trgZbwuBf2F/openapi.json
