# Nonprofit Lookup: 501c3 Status & Form 990 Search (`aiqlabs/nonprofit-lookup`) Actor

Checks whether a US nonprofit's tax-exempt status is still valid. Form 990 data alone cannot tell you: an organisation can carry a decade of filings and the deductible flag while the IRS revoked its exemption. Reconciles the 990 record against the IRS auto-revocation list and Publication 78.

- **URL**: https://apify.com/aiqlabs/nonprofit-lookup.md
- **Developed by:** [Ai-Q Labs](https://apify.com/aiqlabs) (community)
- **Categories:** Lead generation, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 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.

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

## IRS 990 Nonprofit Audit: Revoked Status & Deductibility

Form 990 data tells you what a nonprofit **filed**. It does not tell you whether the IRS still recognises it as tax-exempt.

Those are different questions, and they disagree more often than you would expect.

### The problem, with a real example

**Truechild Inc** (EIN 13-4087914) has twelve years of Form 990 filings on record, a 501(c)(3) subsection code, and IRS deductibility code `1` — *contributions to this organisation are deductible*. Every tool that reads the Form 990 dataset reports exactly that.

The IRS automatically revoked its tax-exempt status on **15 November 2025**.

The revocation is not in the Form 990 data. It is in a separate file — the Automatic Revocation of Exemption List — that the Form 990 record does not link to and does not reflect. The organisation is also absent from Publication 78, the IRS list of organisations eligible to receive deductible contributions.

This is not a rare edge case. In a random sample of 40 recently revoked 501(c)(3)s, **19 still had a full Form 990 filing history** on the public record.

### What this Actor does

For each organisation you give it, it reconciles three official sources and reports where they disagree:

| Source | What it answers |
|---|---|
| ProPublica Nonprofit Explorer (IRS Form 990 extract) | What the organisation filed, and what the Business Master File says about it |
| IRS Automatic Revocation of Exemption List | Whether the exemption was revoked, when, and whether it was reinstated |
| IRS Publication 78 | Whether the IRS lists it as eligible for deductible contributions |

No API key is needed for any of them, and nothing is scraped — all three are published for programmatic use.

#### The checks

| Code | Severity | What it means |
|---|---|---|
| `exemption_revoked` | high | On the IRS auto-revocation list with no reinstatement. **The IRS's own statement.** |
| `deductibility_claim_contradicted` | high | The Form 990 record still says contributions are deductible while the exemption is revoked. The two official sources contradict each other. |
| `revoked_then_reinstated` | medium | Exemption was lost and restored. Contributions in the gap may not be deductible. |
| `filing_gap` | medium / low | Returns appear unfiled. **This is inferred from an absence, not stated by the IRS.** |
| `no_filings_on_record` | low | No Form 990 of any kind on record. |
| `not_in_pub78` | low | Absent from Publication 78 without an obvious reason. |
| `ein_not_found` | medium | No such organisation in the Form 990 dataset. |

### What it deliberately does *not* do

A tool that shouts about healthy organisations is worse than no tool, because the real findings drown. Four rules keep it quiet:

**A revocation and a filing gap are never treated as the same kind of fact.** The first is the IRS saying an exemption is gone. The second is this Actor noticing that filings are missing. They get different codes, different severities, and different wording, because acting on the second as though it were the first means telling a donor that a live charity is dead.

**Absence from Publication 78 is treated as weak evidence, not proof.** Churches are exempt without applying and are frequently unlisted; subordinates under a group exemption ruling are covered by the parent's listing. Measured against a sample drawn from ProPublica: **5 of 12 churches and 4 of 12 universities were absent from Publication 78 while in perfectly good standing.** So it is the lowest severity, it carries the reason, and for religious organisations it is suppressed entirely.

**A 501(c)(4) is not defective for being absent from a list of 501(c)(3)s.** Contributions to it are not deductible by design. That is reported as a fact about the organisation type, never as a warning.

**The filing gap is measured against the extended deadline, not the calendar.** A Form 990 for tax year *Y* is not late until 15 November of *Y+1*. Counting calendar years since the last filing — the obvious way — flags organisations that have filed everything they owe.

**If a source could not be read, the answer is `unknown`, never `ok`.** Half a revocation list looks exactly like a clean one. A truncated download is treated as fatal to the index rather than as an empty result.

### Input

```json
{
  "eins": ["13-4087914", "53-0196605"],
  "checkRevocationList": true,
  "checkPublication78": true
}
```

Hyphenated and bare EINs both work, and a leading zero lost to a spreadsheet's numeric column is restored. Instead of a list you can pass `searchQuery`, `searchState`, `searchNteeCode` or `searchSubsection` to audit everything a search matches.

### Output

One row per organisation. The fields that carry the verdict:

| Field | Values |
|---|---|
| `exemptStatus` | `revoked` · `reinstated` · `listed` · `not_listed` · `not_applicable` · `unknown` |
| `deductibilityVerdict` | `not_deductible` · `deductible` · `deductible_now` · `not_deductible_by_type` · `unconfirmed` · `unknown` (EIN not in the dataset) |
| `exemptStatusReason` | Why, in a sentence |
| `issues[]` | `{ code, severity, message, evidence }` |
| `highestSeverity` | `high` · `medium` · `low` · `ok` |

Alongside them: `revocationDate`, `reinstatementDate`, `revocationGapDays`, `pub78Listed`, `pub78DeductibilityCode`, `propublicaDeductibilityCode`, `latestFilingYear`, `missedFilingYears`, `filingCount`, `totalRevenue`, `totalExpenses`, `totalAssets`, `officerCompensation`, `latestFilingPdf`, and the sources consulted.

Three key-value records are written as well: `SUMMARY` (counts for the whole run, including which sources were readable), `REVOKED` (revoked organisations, largest first) and `NEEDS_ATTENTION`.

### Limits, stated plainly

- **US organisations only.** These are IRS datasets.
- **The IRS files are point-in-time.** They are refreshed periodically; a revocation from last week may not be in them yet. For a decision with money attached, confirm through the IRS Tax Exempt Organization Search.
- **Reinstatement can be retroactive.** The bulk file gives one reinstatement date and does not say whether it was retroactive to the revocation, so the reported gap is an upper bound.
- **990-N e-Postcard filings are largely absent** from the Form 990 dataset. Small organisations therefore show no filings; the filing-gap check is suppressed for them rather than guessing.
- **Each run downloads about 76 MB** of IRS bulk data, regardless of how many EINs you ask about. That is the run's fixed cost; the per-organisation cost is one small JSON request. Switch either file off if you do not need it — but with the revocation list off, every status becomes `unknown`.

### Sources

- ProPublica Nonprofit Explorer API — `https://projects.propublica.org/nonprofits/api/`
- IRS Automatic Revocation of Exemption List — `https://apps.irs.gov/pub/epostcard/data-download-revocation.zip`
- IRS Publication 78 data — `https://apps.irs.gov/pub/epostcard/data-download-pub78.zip`

This Actor reports what those sources say. It is not tax advice, and it does not replace the IRS Tax Exempt Organization Search for a decision you are about to act on.

# Actor input Schema

## `eins` (type: `array`):

Employer Identification Numbers to audit. Hyphenated (13-4087914) and bare (134087914) both work, and a leading zero lost to a spreadsheet is restored.

## `searchQuery` (type: `string`):

Instead of, or in addition to, a list of EINs: find organisations by name or keyword and audit the matches.

## `searchState` (type: `string`):

Two-letter state code to narrow the search, for example NY.

## `searchNteeCode` (type: `string`):

NTEE major group number (1-10) to narrow the search, for example 3 for Health.

## `searchSubsection` (type: `string`):

501(c) subsection to narrow the search, for example 3 for charities.

## `maxOrganizations` (type: `integer`):

Upper bound on how many organisations to audit in one run. Anything beyond it is dropped and the count is reported in SUMMARY.

## `filingGapYears` (type: `integer`):

Report an organisation whose most recent Form 990 is at least this many years old. Three consecutive missed years is what triggers automatic revocation.

## `checkRevocationList` (type: `boolean`):

Download the IRS Automatic Revocation of Exemption List. This is the only source that says whether an exemption was actually lost. Turning it off makes every exempt status unknown rather than intact.

## `checkPublication78` (type: `boolean`):

Download IRS Publication 78 data, the list of organisations eligible to receive tax-deductible contributions.

## `concurrency` (type: `integer`):

How many Form 990 lookups to run at once. ProPublica is a free public service, so keep this modest.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout. The IRS bulk downloads are large, so this guards the handshake rather than the whole transfer.

## Actor input object example

```json
{
  "eins": [
    "13-4087914",
    "53-0196605",
    "13-2967644"
  ],
  "maxOrganizations": 100,
  "filingGapYears": 3,
  "checkRevocationList": true,
  "checkPublication78": true,
  "concurrency": 4,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `revoked` (type: `string`):

No description

## `needsAttention` (type: `string`):

No description

## `summary` (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 = {
    "eins": [
        "13-4087914",
        "53-0196605",
        "13-2967644"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("aiqlabs/nonprofit-lookup").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 = { "eins": [
        "13-4087914",
        "53-0196605",
        "13-2967644",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("aiqlabs/nonprofit-lookup").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 '{
  "eins": [
    "13-4087914",
    "53-0196605",
    "13-2967644"
  ]
}' |
apify call aiqlabs/nonprofit-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=aiqlabs/nonprofit-lookup",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/VldcGM4A56LHDfzo1/builds/5opGcODpOvHotiH2T/openapi.json
