Charity Verify API
One API call to check tax-deductible eligibility, revocation status, group exemptions, and OFAC sanctions screening for any U.S. nonprofit.
Overview
The Charity Verify API cross-references multiple IRS data sources and OFAC sanctions lists to give you a single, definitive answer on whether a nonprofit can receive tax-deductible donations.
Data sources checked on every request:
- IRS Exempt Organizations Business Master File (EO BMF)
- IRS Publication 78 (eligible donees)
- IRS Auto-Revocation List (with reinstatement detection)
- Group Exemption resolution (subordinate → parent lookup)
- OFAC SDN & Consolidated sanctions lists (fuzzy matching)
- Form 990 leadership screening against OFAC
Authentication
All API requests require an API key passed in the x-api-key header.
# Include your key in every request curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/verify?ein=53-0196605"
To get an API key, contact support@givalgo.ai. Keys are scoped to a usage plan (Free, Basic, or Pro) which determines your rate limits.
Keep your API key secure. Do not expose it in client-side code or public repositories. If compromised, contact support for a key rotation.
Quickstart
Get up and running in 60 seconds.
cURL
# Verify American Red Cross curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/verify?ein=53-0196605"
Python
import requests response = requests.get( "https://api.givalgo.ai/v1/verify", params={"ein": "53-0196605"}, headers={"x-api-key": "YOUR_API_KEY"} ) data = response.json() if data["deductible"]: print(f"{data['organization']['name']} is eligible!") else: print(f"Status: {data['status']} - {data['message']}")
JavaScript
const response = await fetch( "https://api.givalgo.ai/v1/verify?ein=53-0196605", { headers: { "x-api-key": "YOUR_API_KEY" } } ); const data = await response.json(); console.log(data.status, data.organization?.name);
Verify Endpoint
Returns a complete verification of the nonprofit identified by the given EIN, including tax-deductible eligibility, revocation history, group exemption status, OFAC sanctions screening, and California state compliance (when applicable).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ein | string | Yes | Employer Identification Number. Accepts 13-2875808, 132875808, or 13 1837418. |
donor_state | string | No | Accepted for backward compatibility; ignored since 2026-09-03. The California checks now run on every request. See California Compliance. |
ca_entity_id | string | No | California SOS/FTB entity number for direct lookup in the CA AG Registry and FTB Revocation list. Provides the highest coverage for California compliance checks. |
Response Fields
| Field | Type | Description |
|---|---|---|
status | string | Overall verification result (see Statuses) |
deductible | boolean | Whether donations are tax-deductible. null if not found. |
message | string | Human-readable explanation |
organization | object | IRS EO BMF data (name, city, state, subsection, foundation type, assets, income, NTEE code) |
pub78 | object | Publication 78 listing and deductibility type (PC, PF, POF, SO, etc.) |
revocation | object | Auto-revocation history and reinstatement status |
group_exemption | object | Present when ELIGIBLE via group exemption. Shows parent org details. Only applies to 501(c)(3) subordinates. |
sanctions_screening | object | OFAC screening results (see Sanctions Screening below for match object details) |
state_compliance | object | Always present since 2026-09-03. Contains the california sub-object with the AG May Not Operate list result and FTB status. See California Compliance. |
data_freshness | object | ISO dates showing when each source agency last published their data file (eo_bmf, pub78, revocation, ofac_sdn, ofac_cons) |
checked_at | string | ISO 8601 timestamp of this verification |
sanctions_screening object
| Field | Type | Description |
|---|---|---|
status | string | CLEAR, POTENTIAL_MATCH, STRONG_MATCH, or NOT_SCREENED |
organization_screening | object | Org name screened against OFAC entity entries |
.checked_name | string | Organization name that was screened |
.matches_found | integer | Number of OFAC matches |
.matches[] | array | Array of match objects (see below) |
leadership_screening | object | Form 990 officers/directors screened against OFAC individuals |
.people_data_tax_year | integer | null | Tax year of the Form 990 people data used for screening (e.g., 2023). Null if no people data found. |
.people_checked | integer | Number of people screened from Form 990 |
.people_screened[] | array | List of officers/directors/key employees screened, each with name and title |
.name | string | Person's name as reported on Form 990 |
.title | string | Person's title/role (e.g., "EXECUTIVE DIRECTOR", "BOARD PRESIDENT") |
.matches_found | integer | Number of OFAC matches |
.matches[] | array | Array of match objects (see below) |
checked_against | array | ["OFAC SDN", "OFAC Consolidated"] |
screened_at | string | ISO 8601 timestamp of screening |
Match object (within matches[])
| Field | Type | Description |
|---|---|---|
source | string | "OFAC SDN" or "OFAC Consolidated" |
matched_name | string | Name from OFAC list that matched |
matched_via | string | "primary_name" or "alias (a.k.a. ...)" |
primary_name | string | Primary OFAC name (only present for alias matches) |
similarity_score | number | Fuzzy match score (0.7–1.0). ≥0.9 = STRONG, ≥0.7 = POTENTIAL |
match_strength | string | "STRONG" or "POTENTIAL" |
sdn_type | string | OFAC entity type ("individual", "N" for entity/org) |
program | string | OFAC sanctions program (e.g., "SDGT", "IRAN") |
entry_id | integer | OFAC entry number |
remarks | string | OFAC remarks — often contains date of birth, nationality, passport numbers, and other identifying information. Key for false positive disambiguation. |
addresses | array | Known addresses for this OFAC entry. Each has address, city_state_zip, and country fields. |
Verification Statuses
| Status | Deductible | Meaning |
|---|---|---|
ELIGIBLE | true | Listed in IRS Pub 78 (directly or via group exemption for 501(c)(3) subordinates). Eligible for tax-deductible donations. |
NOT_DEDUCTIBLE | false | In IRS records but not tax-deductible (e.g., 501(c)(4), 501(c)(6)). |
REVOKED | false | Tax-exempt status auto-revoked by the IRS for non-filing. May appear even if the organization has dropped off the Business Master File. |
CAUTION | null | Federally eligible but flagged for review. Possible reasons: (1) In IRS master file as 501(c)(3) but not yet in Pub 78, or (2) California state compliance issue — the organization is delinquent, revoked, or suspended with the CA Attorney General or Franchise Tax Board. Check the message and state_compliance fields for specifics. |
NOT_FOUND | null | Not in IRS records and not on the revocation list. Churches and self-declared orgs may not appear. |
Sanctions Screening
Every verification includes an automated OFAC screening of the organization name and its leadership (officers, directors, key employees from Form 990). Matches are checked against both SDN and Consolidated lists, including primary names and aliases.
Overall status
| Status | Meaning |
|---|---|
CLEAR | No OFAC matches found for the organization or its leadership. |
POTENTIAL_MATCH | Fuzzy match with 70–89% similarity. Manual review recommended. |
STRONG_MATCH | Fuzzy match with 90%+ similarity. Very likely the same entity. |
NOT_SCREENED | OFAC screening was not performed (e.g., organization not found in IRS database). See the reason field for details. |
Disambiguation fields
When matches are found, each match object includes remarks and addresses to help you distinguish true positives from false positives:
remarks— OFAC remarks field containing date of birth, nationality, passport numbers, and other identifying informationaddresses— known addresses for the matched OFAC entry (street, city/state/zip, country)
Example match object
{
"source": "OFAC SDN",
"matched_name": "SMITH, John A.",
"matched_via": "primary_name",
"similarity_score": 0.912,
"match_strength": "STRONG",
"sdn_type": "individual",
"program": "SDGT",
"entry_id": 12345,
"remarks": "DOB 15 Mar 1970; nationality Iran; Passport A1234567",
"addresses": [
{
"address": "123 Example St",
"city_state_zip": "Tehran",
"country": "Iran"
}
]
}In this example, a board member named "John Smith" matched an OFAC entry — but the remarks show an Iranian DOB/passport, and the addresses show Tehran. If your board member is based in Ohio with a different DOB, this is a false positive you can confidently dismiss.
California Compliance
California AB 488 requires fundraising platforms to verify that nonprofits are in good standing with three authorities before facilitating donations involving a California donor or a California-based nonprofit:
- IRS — federal tax-exempt status (always checked)
- CA Attorney General — May Not Operate or Solicit for Charitable Purposes List (11 CCR § 312). Since 2026-09-02 this is the only list the AG publishes; the full registry export (Current / Exempt registrations) was discontinued, so the AG check is a negative-list check: being on the list is the finding, absence from it is the AB 488 good-standing signal (11 CCR § 316(c)).
- CA Franchise Tax Board — state tax-exempt status (revocation check)
The California checks run on every request, for every organization, since 2026-09-03. They used to run only for California-based nonprofits or when donor_state=CA was passed. Two things made that gate wrong: the Attorney General's May Not Operate list is not California-only (about 920 of the organizations on it are based in other states), and AB 488 (Gov. Code § 12599.9, 11 CCR § 316) binds a platform because it serves people in California and covers every organization it solicits for, wherever that organization is based. donor_state is still accepted but changes nothing. ca_entity_id still gives a direct lookup by California SOS/FTB entity number.
When an organization based outside California is on the list, ag_registry.notice explains that the finding bars it from soliciting in California and says nothing about its home-state standing — so a grantmaker in Ohio understands why the overall status is CAUTION.
Lookup cascade
The API uses a three-tier lookup to match against the CA AG May Not Operate list (~30K records; 82% carry a FEIN):
ca_entity_id— direct SOS/FTB entity-number lookup (92% of list rows carry one)- FEIN — federal EIN match (82% of list rows carry one)
- Name + city — trigram similarity ≥0.80 within the same city. No city, no name match: on a negative-only list a false positive is a false accusation.
The match_method field in the response tells you which tier was used: ca_entity_id, fein, or name_match.
state_compliance.california object
| Field | Type | Description |
|---|---|---|
ag_registry | object | Result against the CA Attorney General's May Not Operate list |
.status | string | NOT_LISTED (not on the list), or the finding for a listed org: REVOKED, SUSPENDED, DELINQUENT, CEASE_AND_DESIST, LISTED (unclassified — still not in good standing) |
.registration_status | string | Verbatim AG registration status (e.g., "Revoked", "Delinquent", "Delinquent Platform Charity"). Null for Cease-and-Desist rows |
.entity_status | string | Verbatim AG entity status (e.g., "Listed", "Cease and Desist Order", "Merged Out") |
.notice | string | Present for NOT_LISTED (what the result does and does not establish) and for a listed organization based outside California (barred from soliciting in California; says nothing about its home-state standing) |
.reg_number | string | State charity registration number (e.g., CT0252232) |
.match_method | string | ca_entity_id, fein, or name_match |
.matched_name | string | Present for name_match — the CA AG name that matched |
.name_similarity | number | Present for name_match — similarity score (0.80–1.0) |
ftb | object | CA Franchise Tax Board revocation result |
.status | string | CLEAR or REVOKED |
.revocation_date | string | Date of FTB revocation (if revoked) |
state_status | string | Roll-up: CLEAR (not on the AG list, no FTB revocation), NON_COMPLIANT (on the AG list), or REVOKED (FTB) |
Impact on overall status
| AG Registry | FTB | state_status | Overall status |
|---|---|---|---|
| NOT_LISTED | CLEAR | CLEAR | No change |
| REVOKED / SUSPENDED / DELINQUENT / CEASE_AND_DESIST / LISTED | CLEAR | NON_COMPLIANT | ELIGIBLE → CAUTION |
| Any | REVOKED | REVOKED | ELIGIBLE → CAUTION |
Changed 2026-09-03: COMPLIANT and UNKNOWN were retired with the AG's move to a negative-only list. COMPLIANT rested on finding a Current or Exempt registration, which the AG no longer publishes in bulk; CLEAR states exactly what was checked. Registration details for a specific organization are available in the AG's Registry Search Tool.
Example response
{
"status": "CAUTION",
"message": "This organization is listed in IRS Publication 78 ... However, California state compliance checks indicate that the organization is on the California Attorney General's May Not Operate or Solicit for Charitable Purposes List (status 'Delinquent'). Under California AB 488, fundraising platforms must verify state-level good standing before facilitating donations.",
"state_compliance": {
"california": {
"ag_registry": {
"status": "DELINQUENT",
"registration_status": "Delinquent",
"entity_status": "Listed",
"reg_number": "CT0252232",
"match_method": "fein"
},
"ftb": {
"status": "CLEAR"
},
"state_status": "NON_COMPLIANT"
}
}
}Playground
Test the API directly from your browser. Enter your API key and an EIN to see a live response.
Try it now
Bulk Verify API
Verify hundreds or thousands of EINs in a single async request. Same 5-step verification, delivered at scale.
Overview
The Bulk Verify API accepts up to 250 EINs (semicolon-separated) or 20,000 EINs (CSV upload), processes them asynchronously via background workers, and delivers results via polling endpoint, webhook callback, or CSV download.
Each EIN goes through the same verification as the single Verify endpoint — IRS data cross-referencing, group exemption resolution, and OFAC sanctions screening.
How it works
- Submit — POST your EINs to
/v1/bulk-verify. Get back ajob_idimmediately (202 Accepted). - Process — EINs are split into batches of 10 and verified in parallel by background workers.
- Retrieve — Poll
GET /v1/bulk-verify/{job_id}for progress and results, or receive a webhook callback on completion.
| Input Method | Max EINs | Format |
|---|---|---|
| JSON body (semicolon-separated) | 250 | application/json |
| CSV file upload | 20,000 | multipart/form-data |
Authentication
All API requests require an API key passed in the x-api-key header — the same key used for the single Verify endpoint.
# Include your key in every request curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"eins": "13-2875808;53-0196605"}'
To get an API key, contact support@givalgo.ai. Keys are scoped to a usage plan (Free, Basic, or Pro) which determines your rate limits and max EINs per job.
Job ownership: Jobs are scoped to your API key. You can only view the status and results of jobs you created.
Quickstart
Submit a bulk job and poll for results in under a minute.
cURL — Submit + Poll
# 1. Submit a bulk job curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "eins": "13-2875808;53-0196605;06-0646973", "format": "both" }' # Response: {"job_id": "bulk_abc123", "status": "running", ...} # 2. Poll for results curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/bulk-verify/bulk_abc123"
Python
import requests, time # Submit bulk job resp = requests.post( "https://api.givalgo.ai/v1/bulk-verify", headers={"x-api-key": "YOUR_API_KEY"}, json={ "eins": "13-2875808;53-0196605;06-0646973", "format": "both" } ) job = resp.json() job_id = job["job_id"] # Poll until complete while True: status = requests.get( f"https://api.givalgo.ai/v1/bulk-verify/{job_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "done": print(f"Done! {status['summary']}") break time.sleep(2)
JavaScript
// Submit bulk job const resp = await fetch("https://api.givalgo.ai/v1/bulk-verify", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ eins: "13-2875808;53-0196605;06-0646973", format: "both" }) }); const { job_id } = await resp.json(); // Poll until complete const poll = async () => { const res = await fetch( `https://api.givalgo.ai/v1/bulk-verify/${job_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); const data = await res.json(); if (data.status === "done") return data; await new Promise(r => setTimeout(r, 2000)); return poll(); }; const results = await poll();
Submit Bulk Job
Submit EINs for bulk verification. Returns 202 Accepted with a job_id for tracking.
JSON body (semicolon-separated EINs)
curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "eins": "13-2875808;53-0196605;06-0646973", "webhook_url": "https://your-server.com/hook", "format": "both" }'
CSV file upload
curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@organizations.csv" \ -F "webhook_url=https://your-server.com/hook"
CSV must have an ein column header (case-insensitive). Falls back to the first column if no header match.
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
eins | string | Yes* | Semicolon-separated EINs. *Not needed for CSV upload. |
webhook_url | string | No | URL to POST results to on job completion. |
format | string | No | Result format: "json", "csv", or "both" (default). |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
skip_invalid | boolean | false | If true, skip invalid EINs and process only valid ones. If false, return a 400 error listing all invalid EINs. |
Response (202 Accepted)
{
"job_id": "bulk_a1b2c3d4e5f6",
"status": "running",
"total_eins": 247,
"duplicates_removed": 3,
"estimated_seconds": 8,
"poll_url": "/v1/bulk-verify/bulk_a1b2c3d4e5f6",
"created_at": "2026-03-25T14:30:00Z"
}Validation error (400)
If any EINs fail validation (and skip_invalid is not set), you get a 400 with details:
{
"error": "VALIDATION_ERROR",
"message": "3 EINs failed validation",
"invalid_eins": [
{ "ein": "123", "error": "EIN must be exactly 9 digits" }
],
"valid_count": 247,
"hint": "Add ?skip_invalid=true to process only valid EINs."
}Poll Job Status
Check the progress and retrieve results of a bulk verification job. Jobs are scoped to your API key — you can only access jobs you created.
In progress
{
"job_id": "bulk_a1b2c3d4e5f6",
"status": "running",
"progress": {
"total": 247,
"completed": 180,
"failed": 2,
"percent_complete": 73.7
}
}Completed
For jobs with 1,000 or fewer EINs, results are included inline. Larger jobs return a csv_download_url only. Each result in the array is a full single-verify response — identical to what you'd get from the /v1/verify endpoint, including organization details, Pub78, revocation history, and OFAC sanctions screening.
{
"job_id": "bulk_a1b2c3d4e5f6",
"status": "done",
"summary": {
"eligible": 200,
"not_deductible": 30,
"revoked": 5,
"not_found": 8,
"ofac_flags": 3
},
"results": [
{
"ein": "13-2875808",
"ein_raw": "132875808",
"status": "ELIGIBLE",
"deductible": true,
"message": "Organization is eligible to receive tax-deductible donations.",
"organization": { /* full org details: name, city, state, NTEE, assets, ... */ },
"pub78": { /* listed, deductibility_code, description */ },
"revocation": { /* ever_revoked, currently_revoked, dates */ },
"sanctions_screening": { /* status, org & leadership screening, matches */ },
"checked_at": "2026-03-25T23:11:10Z",
"api_version": "v1"
},
// ... one entry per EIN, same structure as single /v1/verify response
],
"csv_download_url": "https://...presigned...",
"completed_at": "2026-03-25T14:30:07Z"
}CSV download
The csv_download_url (pre-signed S3 URL, valid 1 hour) contains a flattened version of the full results with 46 columns covering organization details, Pub78 status, revocation history, and OFAC screening. Column headers:
ein, status, deductible, message, org_name, org_city, org_state, subsection_code, subsection_label, deductibility_code, deductibility_label, foundation_code, foundation_label, status_code, status_label, affiliation_code, affiliation_label, group_exemption_number, ruling_date, ntee_code, asset_amount, income_amount, revenue_amount, pub78_listed, pub78_deductibility_code, pub78_deductibility_description, pub78_via_group_exemption, ever_revoked, currently_revoked, revocation_date, revocation_posting_date, reinstatement_date, sanctions_status, ofac_org_matches_found, ofac_leadership_matches_found, screened_at, ca_state_status, ca_ag_status, ca_ag_registration_status, ca_ag_entity_status, ca_ag_reg_number, ca_ag_match_method, ca_ftb_status, ca_ftb_revocation_date, error_message, checked_at
The eight ca_* columns (added 2026-09-03) are populated on every row — the California checks run for every organization. ca_state_status is CLEAR, NON_COMPLIANT or REVOKED; ca_ag_status is NOT_LISTED or the finding for an organization on the Attorney General's May Not Operate list. Same meanings as state_compliance.california on GET /v1/verify.
Job statuses
| Status | Meaning |
|---|---|
running | Being processed by background workers. |
done | All EINs have been verified. Results are available. |
failed | The job stopped before finishing. Partial results may be available. |
Webhooks
If you provide a webhook_url when submitting a bulk job, we'll POST a notification when the job completes. This is the recommended approach for large jobs instead of polling.
Webhook payload
# POST to your webhook_url # Headers: # Content-Type: application/json # User-Agent: Givalgo-Webhook/1.0 # X-Givalgo-Event: bulk_verify.completed # X-Givalgo-Job-Id: bulk_a1b2c3d4e5f6 { "event": "bulk_verify.completed", "job_id": "bulk_a1b2c3d4e5f6", "status": "done", "total_eins": 247, "completed": 245, "failed": 2, "summary": { "eligible": 200, "not_deductible": 30, "ofac_flags": 3 }, "csv_download_url": "https://...presigned-url...24hr-expiry...", "poll_url": "/v1/bulk-verify/bulk_a1b2c3d4e5f6" }
Retry policy
Webhook delivery is attempted up to 3 times with exponential backoff (1s, 5s delays). If your endpoint returns a 2xx status, delivery is marked as successful. The csv_download_url is a pre-signed S3 URL valid for 24 hours.
Throughput
| EINs | Estimated Time |
|---|---|
| 10 | ~5 seconds |
| 50 | ~15 seconds |
| 250 | ~30 seconds |
| 1,000 | ~2 minutes |
| 20,000 | ~40 minutes |
Job Statuses
A bulk job progresses through these statuses:
| Status | Meaning |
|---|---|
running | Being processed by background workers. Poll for progress updates. |
done | All EINs have been verified. Results are available inline (for jobs ≤ 1,000 EINs) and via CSV download URL. |
failed | The job stopped before finishing. Partial results may be available in progress. |
Verification Statuses
Each EIN in a bulk job receives the same verification statuses as the single Verify endpoint:
| Status | Deductible | Meaning |
|---|---|---|
ELIGIBLE | true | Listed in IRS Pub 78 (directly or via group exemption). Eligible for tax-deductible donations. |
NOT_DEDUCTIBLE | false | In IRS records but not tax-deductible (e.g., 501(c)(4), 501(c)(6)). |
REVOKED | false | Tax-exempt status auto-revoked by the IRS for non-filing. May appear even if the organization has dropped off the Business Master File. |
CAUTION | null | In IRS master file as 501(c)(3) but NOT in Pub 78. May be a data timing lag — verify via the IRS Tax Exempt Organization Search. |
NOT_FOUND | null | Not in IRS records and not on the revocation list. Churches and self-declared orgs may not appear. |
Limits & Throughput
These limits apply to every bulk job.
| Limit | Value |
|---|---|
| Max EINs (JSON body) | 250 |
| Max EINs (CSV upload) | 20,000 |
| Max concurrent jobs | 5 per API key |
| Result retention | 30 days |
| CSV download URL expiry | 24 hours |
Estimated processing times
| EINs | Estimated Time |
|---|---|
| 10 | ~5 seconds |
| 50 | ~15 seconds |
| 250 | ~30 seconds |
| 1,000 | ~2 minutes |
| 20,000 | ~40 minutes |
Processing time depends on current load and Lambda warm-start state. For time-sensitive workloads, use a webhook for instant notification on completion.
Report API
Generate professional, branded PDF verification reports for any U.S. nonprofit — ready to share with donors, board members, or compliance teams.
Overview
The Report API takes an EIN and returns a multi-page PDF document containing the same verification data as the Verify endpoint — presented in a polished, print-ready format with Givalgo branding.
Each report includes a cover page, verification outcome with status badge, IRS data breakdown, OFAC sanctions screening results, financial snapshot, and a branded back page.
application/pdf
Authentication
The Report API uses the same API key authentication as the Verify and Bulk Verify endpoints. Include your key in the x-api-key header:
curl -H "x-api-key: YOUR_API_KEY" \ -H "Accept: application/pdf" \ "https://api.givalgo.ai/v1/report?ein=13-2875808" \ -o report.pdf
Quickstart
Generate your first PDF report in seconds:
# Download a verification report for Human Rights Watch (EIN 13-2875808) curl -H "x-api-key: YOUR_API_KEY" \ -H "Accept: application/pdf" \ "https://api.givalgo.ai/v1/report?ein=13-2875808" \ -o verification-report.pdf # Open the PDF open verification-report.pdf
Tip: Include the Accept: application/pdf header to receive decoded binary PDF.
In Postman, use Send and Download (dropdown next to Send) to save the file.
Report Endpoint
Takes an EIN and returns a professionally formatted PDF verification report. The underlying data is identical to the /v1/verify endpoint — the Report API simply renders it into a branded, multi-page PDF document.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ein | string | Yes | Employer Identification Number. Accepts 13-2875808, 132875808, or 13 1837418. |
Headers
| Header | Required | Description |
|---|---|---|
x-api-key | Yes | Your API key |
Accept | Recommended | Set to application/pdf for decoded binary response |
Success Response
| Property | Value |
|---|---|
| Status Code | 200 OK |
| Content-Type | application/pdf |
| Content-Disposition | inline; filename="npo-verify-{ein}.pdf" |
Error Responses
Error responses are returned as JSON (not PDF):
| Status | Error Code | Description |
|---|---|---|
400 | MISSING_EIN | The ein query parameter was not provided |
400 | INVALID_EIN | EIN format is invalid (not 9 digits) |
403 | Forbidden | Missing or invalid API key |
500 | INTERNAL_ERROR | Server-side error during PDF generation |
Note: All valid verification statuses (ELIGIBLE, REVOKED, NOT_FOUND, CAUTION, NOT_DEDUCTIBLE) return a 200 with a PDF. Only auth or input errors return JSON.
Report Sections
Each generated PDF contains the following pages:
| Page | Section | Content |
|---|---|---|
| 1 | Cover Page | Givalgo branding, organization name, EIN, report generation date |
| 2 | Outcome & Details | Large status badge (color-coded), deductibility verdict, organization details (city, state, subsection, NTEE code, ruling date) |
| 3 | IRS Status Breakdown | EO BMF status & deductibility, Pub 78 listing, group exemption details, revocation history with dates |
| 4 | OFAC Screening | Organization & leadership sanctions screening status, match details (if any), data source year |
| 5 | Financial Snapshot | Total assets, annual income, annual revenue (from IRS BMF data) |
| 6 | Back Page | Disclaimer, API version, generation timestamp, contact info |
Verification Statuses
The report displays the same verification statuses as the Verify endpoint, rendered as color-coded badges:
| Status | Badge Color | Deductible | Meaning |
|---|---|---|---|
ELIGIBLE | ● Green | Yes | Listed in IRS Pub 78. Eligible for tax-deductible donations. |
NOT_DEDUCTIBLE | ● Orange | No | In IRS records but not tax-deductible. |
REVOKED | ● Red | No | Tax-exempt status auto-revoked by the IRS. |
CAUTION | ● Yellow | Unconfirmed | In IRS master file as 501(c)(3) but not in Pub 78. |
NOT_FOUND | ● Gray | Unknown | Not in IRS records. Churches and self-declared orgs may not appear. |
Important Notes
Response Time
PDF generation typically takes 2–4 seconds (cold start may add 3–5 seconds on first request). The API Gateway timeout is 29 seconds.
Data Source
Reports contain the same data as the /v1/verify endpoint. The PDF is generated on-the-fly from live verification data — it is not cached. Each request produces a fresh report with the current timestamp.
File Size
Reports are typically 115–130 KB depending on the amount of data (e.g., OFAC matches increase size slightly).
Disclaimer
Each report includes a footer disclaimer: "This report is generated from publicly available IRS data and OFAC sanctions lists. It does not constitute legal or tax advice."
Data API
Search 1.9M+ U.S. nonprofits by EIN, name, cause, geography, financials — or a plain-English prompt — and get back clean, lean org records built from 3.3M+ Form 990 filings.
Overview
The Data API is a nonprofit search engine. One endpoint, POST /v1/data, accepts three kinds of request:
search_terms— an EIN ("13-2875808") or an organization name ("Human Rights Watch"). Keywords match organization names — not mission text, and not cause. To search by cause usefilters.organization.causes(plain English) orntee_major_codes(codes), orpromptfor a whole question.filters— structured search: cause (NTEE), geography (state / city / zip), org type, IRS standing, and financial screens (revenue, assets, grants, program expense ratio, months of cash, growth streaks, revenue concentration)prompt— Ask: one plain-English sentence ("private foundations in Ohio funding youth mental health"), parsed and semantically ranked by the same pipeline that powers Ask on Givalgo Discover. Returns the top 20.
Every mode returns the same lean org record (~40 fields): identity, geography, cause, IRS standing, latest financial toplines — plus the organization's logo, website, and website-scraped contact details where available.
When you've found the organization you care about, go deep with the Data Pro API:
POST /v1/dataData — search & discovery; lean records, Ask promptsGET /v1/data-proPro — the full single-org profile (~410 fields: full financial detail, compensation cube, governance, schedules, registration)
Migrating from another nonprofit data API? Familiar request-body conventions and field aliases are accepted where the data overlaps — unsupported fields return a 400 with a hint naming the nearest supported equivalent. See Filters.
Tiers: Data vs Data Pro
The two tiers differ by how you interact, not just field count. The Data API answers "which organizations?" — search, filter, Ask — with lean records. Data Pro answers "tell me everything about this one" — a single-EIN deep profile. Both require the same x-api-key header.
| /v1/data Data | /v1/data-pro Pro | |
|---|---|---|
| Interaction | Search: EIN / name / keywords, structured filters, Ask prompts | Lookup: one EIN per request |
| Response | Page of lean org records (~40 fields each) | One deep profile (~410 fields) |
| Best for | Discovery, screening, autocomplete, pipeline building, monitoring | Due diligence, profiles, analysis |
The deep-profile sections below (organization, financials, trends, compensation, …) document the Data Pro response:
| Section | /v1/data-pro Pro |
|---|---|
organization — identity, NTEE, mission | ✓ |
financials — revenue, expenses, balance sheet | ✓ |
trends — 6-year annual series + CAGR + trend labels | ✓ |
compensation — officers, by_role, peer benchmarks | ✓ |
funding_sources — grantmakers received from | ✓ |
programs — program descriptions + expenses | ✓ |
governance — board + policies | ✓ |
fundraising — events, professional fundraisers | ✓ |
financial_profile — liquidity, HHI, expense composition New | ✓ |
related_organizations | ✓ |
foreign_activities, lobbying_and_political, donor_advised_funds | ✓ |
data_freshness | ✓ |
financials.detail — Part VIII/IX/X line items + Schedule D blocks Pro | ✓ |
governance.detail — full board roster, policy flags, compliance filings | ✓ |
disclosures — Schedules K/L/M/N/F/A/O | ✓ |
registration — IRS BMF + Pub 78 + California AG/FTB | ✓ |
New indicates fields added in the April 2026 release.
Authentication
All requests require an API key passed in the x-api-key header. Keys are scoped to a usage plan (Free, Basic, Pro) which determines rate limits and the daily Ask-prompt quota.
# Data API — search curl -X POST -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"search_terms": "Human Rights Watch"}' \ "https://api.givalgo.ai/v1/data" # Data Pro API — deep single-org profile curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/data-pro?ein=53-0196605"
To upgrade to Pro or request a trial key, contact support@givalgo.ai.
Quickstart
cURL — look up one org (EIN or name)
curl -X POST -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"search_terms": "13-2875808"}' \ "https://api.givalgo.ai/v1/data"
cURL — structured search
curl -X POST -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "search_terms": "community foundation", "filters": {"geography": {"state": ["FL"], "city": "Miami"}}, "size": 10 }' \ "https://api.givalgo.ai/v1/data"
Search by cause — three worked examples
Plain English in filters.organization.causes, resolved to NTEE codes and composed with any other filter. Every response reports what the phrase became in causes_resolved.
# 1. A cause in one city — Plymouth Housing Group, Bellwether Housing { "filters": {"organization": {"causes": "homelessness"}, "geography": {"city": "Seattle", "state": ["WA"]}}, "sort": {"by": "total_revenue", "order": "desc"}, "size": 5 } # 2. A cause + an efficiency screen — Feeding America, Houston Food Bank { "filters": {"organization": {"causes": "hunger relief"}, "financials": {"program_expense_ratio": {"min": 0.90}, "total_revenue": {"min": 5000000}}}, "sort": {"by": "total_revenue", "order": "desc"}, "size": 5 } # 3. Grantmakers working on a cause — Dana-Farber, AACR { "filters": {"organization": {"causes": "cancer research", "org_type": "foundation"}}, "sort": {"by": "grants_paid", "order": "desc"}, "size": 5 }
cURL — Ask (natural language)
curl -X POST -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"prompt": "private foundations in Ohio funding youth mental health"}' \ "https://api.givalgo.ai/v1/data"
Python — search, then go deep with Data Pro
import requests HEADERS = {"x-api-key": "YOUR_API_KEY"} # 1. Find California food banks with $10M+ revenue, biggest first search = requests.post( "https://api.givalgo.ai/v1/data", json={ "filters": { "geography": {"state": ["CA"]}, "organization": {"ntee_major_codes": ["K"]}, "financials": {"total_revenue": {"min": 10000000}}, }, "sort": "revenue_desc", }, headers=HEADERS, ).json() top = search["results"][0] print(top["name"], top["ein"], top["website"]) # 2. Full profile for the top hit deep = requests.get( "https://api.givalgo.ai/v1/data-pro", params={"ein": top["ein"]}, headers=HEADERS, ).json() print(deep["financial_profile"]["expense_composition"]["program_expense_ratio"])
JavaScript
const r = await fetch("https://api.givalgo.ai/v1/data", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ search_terms: "Human Rights Watch" }), }); const { results, total } = await r.json(); console.log(total, results[0].name, results[0].mission);
Endpoints
The Data API. JSON body with search_terms (EIN / organization name), filters, or an Ask prompt — see Request Body. Returns a page of lean org records; the full field list is under Data API Response.
The same endpoint also answers GET /v1/data?ein={ein} — a body-less convenience for single-org lookups, identical to POSTing {"search_terms": "{ein}", "size": 1}. Changed 2026-07: the deep Form 990 profile formerly served on that URL now lives exclusively on /v1/data-pro.
The deep single-org profile (~410 fields): all core sections plus financials.detail, governance.detail, disclosures, and registration. Documented under Data Pro Response.
Parameters (GET endpoints)
| Parameter | Type | Required | Description |
|---|---|---|---|
ein | string | Yes | Employer Identification Number. Accepts 53-0196605, 530196605, or 53 0196605. |
tax_year | integer | No | /v1/data-pro only: 4-digit fiscal year (e.g. 2022). Omit to get the most recent filing. If the requested year has no filing, the response is a 404 with a years_available array. Ignored on GET /v1/data (lean records are latest-year only). |
There is no include parameter. The endpoint is the tier contract: /v1/data-pro returns every Pro section on every request, and data_freshness.includes_applied is always ["all"]. An ?include= on the query string is ignored — a request with and without it returns byte-identical JSON. You never need it to obtain a section.
Search Request Body
POST /v1/data takes a JSON body. At least one of search_terms, filters, prompt is required; prompt cannot be combined with the other two (Ask parses the prompt into its own filters).
| Field | Type | Description |
|---|---|---|
search_terms | string | EIN ("13-2875808") or organization name. Keywords match organization names only — not mission or cause. Combines with filters. For cause, use filters.organization.causes or ntee_major_codes. |
filters.organization.causes | string | string[] | Search by cause in plain English — "hunger relief", "adult literacy". Resolved to NTEE codes server-side; composes with every other filter. Only the cause is read — place and money inside the phrase are ignored (use filters.geography / filters.financials). A phrase that maps to nothing returns 400 UNRESOLVED_CAUSES, never an empty page. Max 5, 200 chars each. |
filters | object | Structured search — see Filters. |
prompt | string | Ask mode: one plain-English sentence, 2–400 chars. Returns the top 20 + an interpretation. Counts against the daily Ask quota. |
sort | string | object | relevance (default), revenue_desc, revenue_asc, assets_desc, grants_desc, name_asc — or a {"sort_by": "total_revenue", "ascending": false} object. |
from | integer | Result offset (default 0). Must be a multiple of size; from + size ≤ 2000. Ignored in Ask mode. |
size | integer | Results per page, 1–50 (default 25). Ignored in Ask mode (fixed top 20). |
Filters
Three sections: geography, organization, financials. Filters AND together across fields; lists within a field are OR. Alternate field nesting (organization.properties.*, financials.most_recent_year.*) is accepted for painless migration from other nonprofit data APIs, as are the aliases noted below. Unsupported fields (e.g. subject_codes, county, last_updated) return 400 UNSUPPORTED_FILTER with a hint naming the nearest equivalent.
geography
| Field | Type | Description |
|---|---|---|
state | string | list | 2-letter state code(s), e.g. ["OH", "MI"]. |
city | string | list | City name(s). Metro aliases expand — "New York" covers all five boroughs. |
zip | string | 5-digit ZIP. |
organization
| Field | Type | Description |
|---|---|---|
ntee_major_codes | list | NTEE major letters, e.g. ["B", "E"]. |
ntee_codes | list | Full NTEE codes, e.g. ["B21"]. Alias: ntee_minor_codes. |
ntee_mode | string | any (default) or all. |
subsection_codes | list | IRS subsection. Accepts "03", 3, "501c3", or "501(c)(3) Public Charity". |
org_type | string | foundation (grantmakers) or nonprofit (grantseekers). Roles are non-exclusive. |
exclude_revoked_organizations | bool | Drop revoked orgs. Also accepted under properties and as exclude_defunct_or_merged_organizations. |
financials
Ranges are {"min": n, "max": n} objects (either bound optional). Also accepted nested under most_recent_year.
| Field | Description |
|---|---|
total_revenue, total_assets, net_assets | Latest-filing dollar ranges. |
grants_made | Grants the org paid out (latest filing). Alias: total_giving. |
revenue_5y, grants_5y | Peak revenue / grants paid across the last 5 filings — catches orgs whose latest year understates them. |
program_expense_ratio | 0–1. Program share of total expenses. |
months_of_cash | Liquid reserves in months of operating expenses. |
revenue_concentration | 0–1 concentration of revenue sources (higher = more concentrated). |
revenue_growth_streak_min, revenue_decline_streak_min, grants_growth_streak_min, grants_decline_streak_min | Integer 1–5: minimum consecutive years of YoY growth / decline. |
The financial-health screens (ratio, cash, streaks, concentration) aren't available in traditional nonprofit data APIs — they're computed from the full filing history.
Data API Response — the Lean Org Record
{
"mode": "search",
"result_kind": "orgs",
"total": 512,
"total_is_capped": false,
"from": 0, "size": 25, "took_ms": 412,
"suggestion": null,
"results": [ { /* lean org record */ } ]
}Each result is a lean org record (~40 fields):
| Group | Fields |
|---|---|
| Identity | ein, name, dba_name, mission, logo_url, logo_tone, logo_bg, logo_view_url (logo_tone: "light" = white/light mark, invisible on white — render it on logo_bg or any dark tile. logo_url is the raw transparent asset: opening a light logo's URL directly shows a blank white page. To see any logo in a browser, open logo_view_url — it renders tone-aware) |
| Geography | city, state, zip, address |
| Contact | website, contact.email, contact.phone, contact.contact_url (website-scraped) |
| Classification | ntee_code, ntee_description, ntee_inferred (our predicted cause code for the ~259K organizations the IRS never coded — present only when ntee_code is null, so an inferred code never overwrites an IRS one), subsection, deductibility, org_type, is_grantmaker, is_grantseeker, legal_status |
| Standing | irs_status, pub78_verified, delisted, delisted_reason, ruling_year |
| Financials | latest_tax_year, total_revenue, total_expenses, total_assets, net_assets, grants_paid, grants_received_total, total_employees |
| Screens | program_expense_ratio, months_of_cash, revenue_growth_streak, revenue_decline_streak, grants_growth_streak, grants_decline_streak, revenue_concentration, relevance_score |
Ask (Natural-Language Search)
Send a prompt and the same pipeline that powers Ask on Givalgo Discover parses it (causes, geography, financial constraints, intent), retrieves deterministically, and semantically reranks. The response adds an interpretation — a plain-English echo of how the prompt was understood.
POST /v1/data
{ "prompt": "private foundations in Ohio funding youth mental health" }
// →
{
"mode": "ask",
"result_kind": "funders",
"interpretation": "Showing foundations based in OH that fund mental health",
"total": 318,
"results": [ …top 20… ]
}result_kind is orgs for most prompts (items = lean org records). Funding-intent prompts return graph-backed kinds with intent-specific item shapes (every item still carries ein + name):
| result_kind | Prompt shape | Extras |
|---|---|---|
funders | “foundations funding <cause>” | grant totals to the cause |
org_funders | “who funds <org>?” | resolved_org, alternatives, total_usd |
funder_grantees | “who does <funder> fund?” | resolved_funder, grantees_total |
peer_funders | “funders like <funder>” | resolved_funder |
related_orgs | “orgs related to <org>” | Schedule R relationships |
recipients | “orgs receiving funding for <cause>” | funding received |
Quota: Ask prompts are LLM-metered and carry a daily budget. Structured searches never touch the quota, and failed or timed-out asks don't count. Over quota returns 429 ASK_QUOTA_EXCEEDED with a quota object and a Retry-After header (next UTC midnight).
Data Pro Response — Top-Level
This section and everything below it document the deep single-org response served by GET /v1/data-pro. (For the Data API's lean search records, see Data API Response.)
Form 990-N (e-Postcard) filers. 1.28 million organizations — 61% of every EIN with a filing on record — have only ever filed the postcard. They return a profile with identity and full IRS registration, and empty financial sections, because the IRS collects no financial data on that form. Check data_availability to tell that apart from a gap. These EINs previously returned 404.
| Field | Type | Description |
|---|---|---|
ein | string | Hyphenated EIN (e.g. "53-0196605"). |
tax_year | integer | Fiscal year of the filing returned. |
return_type | string | "990", "990EZ", "990PF", or "990N" (e-Postcard). |
data_availability | object | Present only when return_type is "990N". {financials: false, reason: "…"} — the IRS collects no financial data on the e-Postcard, so those sections are empty because nothing was filed, not because we are missing it. The key’s absence means a full return, where empty financials would be a real gap. |
filing_date | string (ISO date) | Date IRS received the filing. |
organization | object | Identity, address, NTEE code, mission, formation year, total employees/volunteers. |
financials | object | Summary, revenue breakdown, expenses, balance sheet, and (Pro only) detail sub-object. |
trends | object | Up to 6 years of annual data + computed CAGRs + trend labels. |
compensation | object | People array, by-role highlights, per-employee average, peer benchmark cubes. |
funding_sources | object | Grants received: total_unique_grantmakers, grantmakers[], annual_totals[]. |
grants_made | object[] | Outbound grants (Schedule I), top 50 for the year. Data Pro only — always present. |
programs | object[] | Program service accomplishments. |
governance | object | Board structure, policies, meeting minutes flags. |
fundraising | object | metrics, events[], contractors[], activity_methods, licensed_states. |
financial_profile | object | Liquidity, revenue concentration (HHI), operating sustainability, expense composition New. All with peer benchmarks. |
related_organizations | object[] | Schedule R-style related entities. |
foreign_activities | object[] | Schedule F Part I region-level summary. |
lobbying_and_political | object | Schedule C summary. |
contributor_summary | object | Always an empty object. The IRS redacts Schedule B in public filings, so no contributor data is available to return — see the note below. |
donor_advised_funds | object | null | DAF flags + balances (if Schedule D.I present). |
conservation_easements New | object | Schedule D Part II. Always-on; present only when held. |
art_collections New | object | Schedule D Part III. Always-on; present only when held. |
private_foundation New | object | Form 990-PF metrics. Always-on; 990-PF filers only. |
hospitals New | object | Schedule H. 501(c)(3) hospital filers only; always present for them on Data Pro. |
disclosures Pro | object | Schedules K/L/M/N/F/A/O. Data Pro only — always present. |
registration Pro | object | IRS BMF + Pub 78 + California AG/FTB status, joined at request time. See registration. |
data_freshness | object | filing_source, most_recent_tax_year, filing_date, last_filing_date, years_available[], includes_applied[]. |
organization
| Field | Type | Description |
|---|---|---|
name | string | Legal name on the filing. |
legal_name New | string | null | IRS-filed legal name. Same source as name — kept as a distinct key for path-compat with platforms that split popular-name vs. registered-name. |
also_known_as | string | null | DBA, if present. |
address_type New | string | Always "PRIMARY" on the main organization address. Distinguishes from operations.books_in_care_of.address_type which is "BOOKS_IN_CARE_OF". |
address, city, state, zip | string | Mailing address. |
address_line_2 New | string | null | Second address line from the Form 990 filing header. Often null; filers usually pack everything into address. |
phone | string | null | Phone from the filing header. |
website | string | null | Served only where a live check against the domain actually succeeded. Domains found dead, parked, or taken over by an unrelated party are withheld, so this is null rather than a URL we can’t stand behind. No field explains an absent website — the absence is the signal. |
formation_year | integer | Year of formation. |
mission | string | Mission statement text. |
principal_officer | string | null | Name of principal officer from Form 990 header. |
country | string | absent | ISO country code, present only for foreign-domiciled organizations. Domestic filers omit the key. |
province | string | absent | Sub-national region for a foreign filer (e.g. "ENGLAND"). Foreign filers only — domestic organizations use state, which stays a US state code and never carries a province. |
ntee_code | string | Full NTEE code (e.g. "P210"). |
ntee_description | string | Human-readable NTEE major group (e.g. "Human Services"). |
ntee_codes.primary_code New | string | null | Single-letter NTEE major code (A–Z). E.g. "E" for Health Care. |
ntee_codes.primary_description New | string | null | Description of major code. E.g. "Health Care". |
ntee_codes.sub_code New | string | null | 3- or 4-char NTEE sub-classification from the NCCS taxonomy lookup. E.g. "E21". |
ntee_codes.sub_description New | string | null | Description of the sub-code. E.g. "Community Health Systems". |
is_national_hq New | boolean | True when BMF affiliation code = 1 or 6 (central parent of a group ruling). False otherwise. |
is_non_bmf_org New | boolean | True when no row exists in eo_bmf for this EIN. Very rare — would suggest filing without IRS BMF registration. |
ruling_year | integer | null | Year IRS granted tax-exempt status. |
total_employees, total_volunteers | integer | As reported on the filing. |
financials
| Field | Type | Description |
|---|---|---|
accounting_method New | string | null | "Accrual", "Cash", or null. From Form 990 Part XII Line 1 (MethodOfAccountingAccrualInd). Applies to all return types. |
summary | object | total_revenue, total_expenses, revenue_less_expenses, total_assets_eoy, total_liabilities_eoy, net_assets_eoy. |
revenue | object | contributions_and_grants, program_service_revenue, investment_income, other_revenue, total. |
revenue.gross_sales New | integer | null | 990-EZ Part I Line 7a — gross sales of inventory. Null on full 990 / 990-PF. |
revenue.cost_goods New | integer | null | 990-EZ Part I Line 7b — cost of goods sold. Null on full 990 / 990-PF. |
revenue.gross_profit New | integer | null | 990-EZ Part I Line 7c (gross_sales − cost_goods). Null on full 990 / 990-PF. |
revenue.revenue_sales New | integer | null | Net gain/(loss) on sales of assets (Part VIII Line 7d). Compatibility alias for net_gain_loss_assets. |
revenue.part_viii_line_detail Pro | object | null | Form 990 Part VIII columnar line splits (full-990 only). Nested: investment_income_bond_proceeds (Line 4); rental {gross_rents / rental_expenses / net_rental_income × real & personal, Lines 6a–6c}; sales_of_assets {gross_amount / cost_basis / gain_loss × securities & other, Lines 7a–7c}; gaming_gross_income (Line 9a); gaming_direct_expenses (Line 9b). Data Pro only — always present. |
revenue.part_viii_line_detail.gaming_gross_income New | integer | null | Part VIII Line 9a — gross income from gaming, before direct expenses. Distinct from net_gaming, which is Line 9c (9a − 9b). Reported by roughly 8% of full-990 filers; null where the organization ran no gaming activity. |
revenue.gross_sales_assets_ez / cost_basis_assets_ez / gain_loss_assets_ez New | integer | null | 990-EZ Part I Lines 5a / 5b / 5c — gross amount from sale of assets, cost or other basis, and the resulting gain/(loss). Under revenue.breakdown. Note that on the EZ the top-level revenue_sales is Line 5c (the net gain), so gross-revenue comparisons spanning 990 and 990-EZ filers should use gross_sales_assets_ez. Null on full 990 / 990-PF. |
expenses | object | program_services, management_and_general, fundraising, total, and allocation (program_pct, fundraising_pct). |
expenses.professional_fees New | integer | null | Part IX Lines 11a–11g sum. Promoted from financial_profile.expense_composition for response-shape clarity. |
expenses.other_expenses New | integer | null | Part IX Line 24e — "Other Expenses" rollup. 990-EZ Part I Line 16 maps directly. |
expenses.total_expense_disbursements New | integer | null | 990-PF Part I col D total charitable disbursements (Line 26). PF-only; null on 990 / 990-EZ. |
expenses.expense_operating_admin New | integer | null | Compatibility field. On 990 / 990-EZ aliases management_and_general; on 990-PF derived as total_expenses − pf_disb_charitable_total (approximates PF Part I Line 24). |
expenses.expense_operating_admin_is_derived_for_pf New | boolean | True on PF returns where the value is derived (not directly reported). False on 990 / 990-EZ. |
expenses.joint_costs_indicator New | boolean | null | Part IX Line 26 — whether the org reported joint costs from a combined educational-campaign + fundraising solicitation. The IRS e-file schema carries only this boolean, not the 4-column dollar split. |
balance_sheet | object | Cash, investments, PP&E, assets, liabilities, net assets (with unrestricted split). |
balance_sheet.investments_us_government New | integer | null | 990-PF Part II Line 10a FMV — US Government obligations. PF-only. |
balance_sheet.investments_stock New | integer | null | 990-PF Part II Line 10b FMV — corporate stock. PF-only. |
balance_sheet.investments_bonds New | integer | null | 990-PF Part II Line 10c FMV — corporate bonds. PF-only. |
balance_sheet.investments_other New | integer | null | 990-PF Part II Line 13 FMV — other investments. PF-only. |
balance_sheet.capital_stock_trust_principal_boy / _eoy New | integer | null | Part X Line 30 (BOY/EOY) — capital stock or trust principal, or current funds. Populated only for the ~5% of orgs using the stock-corporation / trust-principal framework instead of net assets; null otherwise. |
balance_sheet.paid_in_capital_surplus_boy / _eoy New | integer | null | Part X Line 31 (BOY/EOY) — paid-in or capital surplus, or land/building/equipment fund. Stock-corp framework only. |
balance_sheet.retained_earnings_boy / _eoy New | integer | null | Part X Line 32 (BOY/EOY) — retained earnings, endowment, accumulated income, or other funds. Stock-corp framework only. |
prior_year New | object | null | Prior-year revenue/expense figures the org self-reported in the "Prior Year" column of this return — a one-line YoY comparison without a second request for tax_year−1. Present only when ≥1 prior-year value is non-null; absent for first-year filers and 990-PF returns. Sub-objects: tax_year (string); summary {total_revenue, total_expenses, revenue_less_expenses}; revenue {contributions_and_grants, program_service_revenue, investment_income, other_revenue, total}; expenses {salaries_and_benefits, grants_paid, total}. Form 990 carries prior-year figures for the summary lines only (no prior-year balance sheet). |
expense_detail_ez New | object | null | 990-EZ Part I expense line items (Lines 10–18) for the flatter EZ expense structure: benefits_paid_to_members (L10), professional_fees (L13, independent contractors), occupancy_rent_utilities (L14), printing_publications_postage (L15), excess_or_deficit_for_year (L18). Present only on 990-EZ returns with ≥1 non-null value; null on full-990 and 990-PF. (EZ revenue gross/cost/profit live under revenue.) |
detail Pro | object | null | Full Part VIII / Part IX / Schedule D line-by-line breakdown. null on base tier. |
detail.balance_sheet.assets.notes_loans_receivable New | object | null | Part X Line 7 — notes and loans receivable, net of allowance, as {boy, eoy}. Distinct from the two insider-receivable lines beside it: receivable_from_officers (Line 5) and receivable_from_disqualified (Line 6). Roughly a quarter of full-990 filers report it. |
financial_documents New
Filing-document links. Synthesized from EIN + object_id + return_type via ProPublica Nonprofit Explorer (we don't host PDFs directly; ProPublica's open viewer is the de-facto public equivalent).
| Field | Type | Description |
|---|---|---|
form990_url | string | null | ProPublica Nonprofit Explorer URL for the filing. Form-type segment maps as 990 → /IRS990, 990-EZ → /IRS990EZ, 990-PF → /IRS990PF. Null when EIN or object_id is missing. |
trends
| Field | Type | Description |
|---|---|---|
years_available | integer[] | All tax years on file for this EIN. |
annual[] | object[] | Per-year rollup: total_revenue, total_expenses, total_assets_eoy, net_assets_eoy, contributions, program_service_revenue, investment_income, program_expense_ratio, fundraising_expense_ratio, months_of_cash, total_employees, top_officer_comp. |
computed.years_of_data | integer | Count of annual entries returned. |
computed.revenue_cagr_pct | number | Compound annual growth rate of revenue. |
computed.expense_cagr_pct | number | CAGR of expenses. |
computed.asset_cagr_pct | number | CAGR of net assets. |
computed.revenue_trend | string | Label: GROWING (>3% CAGR), STABLE, or DECLINING (<−3%). |
computed.expense_trend New | string | Matching label for expense CAGR. |
computed.asset_trend New | string | Matching label for asset CAGR. |
compensation
Top-level scalars are drawn from Form 990 Part VII (officers, key employees, highest-paid) and Part IX (salaries & benefits aggregate).
Top-level fields
| Field | Type | Description |
|---|---|---|
tax_year | integer | Fiscal year this compensation data belongs to. |
people_count | integer | Number of people in the people[] array. |
employees_over_100k New | integer | null | Schedule J Part I Line 1 — total count of individuals receiving > $100K in reportable compensation. Org-wide count from the filing; distinct from people_count (the number of Part VII Section A people listed individually). null when Schedule J is not filed. |
people[] | object[] | Array of Part VII persons with name, title, hours_per_week, is_officer, is_key_employee, is_director, is_highest_paid, reportable_comp_org, reportable_comp_related, other_compensation, total_compensation. |
highest_paid_person | string | Name of the highest-paid person in the list. |
highest_compensation | integer | Dollar amount. |
comp_to_expense_pct | number | Sum of Part VII compensation ÷ total expenses, as a percent. |
top_officer_comp_to_revenue_pct New | number | Top officer total comp ÷ total revenue, as a percent. Scale-normalized so small and large orgs can be compared fairly. |
avg_compensation_per_employee New | integer | Whole-org payroll ÷ total employees, in dollars. Sourced from Form 990 Part IX Lines 5-10 (salaries + benefits + pension + payroll tax). Populated for ~29% of filings where both fields are non-zero. |
by_role New
Highest-paid person whose title matches common patterns for each role. null per role if no match.
| Field | Type | Description |
|---|---|---|
by_role.ceo | object | null | {title, total_compensation}. Matches titles like Executive Director, Chief Executive, President, CEO. |
by_role.cfo | object | null | Matches titles like Chief Financial Officer, CFO, Treasurer, Director of Finance, VP Finance. |
by_role.development_director | object | null | Matches Chief Development Officer, Development Director, VP Development, Chief Advancement Officer. |
peer_benchmarks_by_metric New
One cube per metric — see How Cubes Work. Metrics with no peer data (e.g. small revenue bands) are omitted.
top_officer_comp— absolute top-officer dollar amountstop_officer_comp_to_revenue_pct— scale-normalizedceo_comp,cfo_comp,dev_director_comp— role-specific benchmarksavg_compensation_per_employee— whole-org payroll per FTE
The legacy key peer_benchmarks (top-level, not nested) remains for backwards compatibility and mirrors peer_benchmarks_by_metric.top_officer_comp.
schedule_j_supplemental New
Best-effort parse of the Schedule J Part III narrative (Part I Lines 4A/4B free text). Present only when the filing has Schedule J narrative text.
| Field | Type | Description |
|---|---|---|
severance_payments[] | object[] | Each {person_name, amount}. Severance paid during the year. |
accrued_severance_unpaid[] | object[] | Each {person_name, amount}. Severance accrued but not yet paid. |
retirement_plan_changes[] | object[] | Each {person_name, amount}. Supplemental retirement / deferred-comp amounts. |
raw_narrative[] | object[] | Each {reference, explanation} — the unparsed Schedule J narrative entries. |
Note: each individual compensation_breakdown object in people[] also carries the Schedule J Part II base/bonus/deferred/nontaxable split (filing_org + related_orgs) for people listed on Schedule J.
funding_sources
Aggregated from Schedule I of grantmakers that reported this EIN as a grant recipient.
| Field | Type | Description |
|---|---|---|
total_unique_grantmakers | integer | Count of distinct grantmakers returned in grantmakers[]. |
grantmakers[] | object[] | Up to 50 grantmakers (by total awarded): grantmaker_ein, grantmaker_name, grants_count, total_awarded, most_recent_year, most_recent_amount, purpose, funder_type (daf_sponsor / donation_processor / affiliate_regranter, or null for a direct institutional funder). |
annual_totals[] | object[] | Per-year: tax_year, grants_received, unique_grantmakers. |
programs & grants
| Field | Type | Description |
|---|---|---|
programs[] | object[] | Program service accomplishments: sequence, description, expenses, grants_included, revenue, beneficiaries. |
grants_made[] | object[] | Top-level outbound grants (Schedule I), top 50 by amount for the current year: recipient_name, recipient_ein, recipient_address, recipient_city, recipient_state, recipient_zip, irc_section, relationship, purpose, cash_amount, noncash_amount, noncash_description, is_placeholder_recipient (true when the filer reported an attachment reference like “SEE STATEMENTS 18 & 20” instead of a recipient — exclude from recipient counts). Data Pro only — always present. |
governance
| Field | Type | Description |
|---|---|---|
board_size | integer | null | Total voting members (Part VI Line 1a). |
independent_members | integer | null | Independent voting members (Part VI Line 1b). |
independence_pct | number | null | Independent share of the board. |
policies.conflict_of_interest | boolean | null | Part VI Line 12. |
policies.whistleblower, policies.document_retention | boolean | null | Part VI Lines 13–14. |
policies.board_reviews_990 | boolean | null | Board reviewed the Form 990 before filing (Part VI Line 11a). |
policies.fs_audited | boolean | null | Financial statements audited by an independent accountant (Part XII Line 2b). A compilation or review only is reported separately as detail.compliance_filings.accountant_compile_or_review. |
detail Pro | object | null | Rosters (board, officers, key_employees, highest_paid_employees, former_insiders), headcount, the full ~22-boolean policy_flags, schedule_j_flags, activity_indicators, contributions_handling, compliance_filings, schedule_o_explanations, books_in_care_of, liquidation. Data Pro only — always present there, absent on /v1/data. Schedule L lives under disclosures, not here. |
fundraising
| Field | Type | Description |
|---|---|---|
metrics | object | cost_to_raise_a_dollar, fundraising_roi, fundraising_expense_pct. |
events[] | object[] | Schedule G Part II: event_name, gross_receipts, charitable_contributions, gross_revenue, direct_expenses, direct_expense_breakdown (6 sub-fields), net_income. |
activity_methods New | object | null | Schedule G Part I solicitation-method booleans: mail_solicitations, email_solicitations, phone_solicitations, in_person_solicitations, solicitation_of_non_govt_grants, solicitation_of_govt_grants, special_fundraising_events. null when no flags are populated. |
licensed_states New | string | null | Schedule G Part I — states where the org is registered/licensed to solicit contributions (raw filing value). |
contractors[] New
Schedule G Part I — paid fundraising contractors. Always-on (no include gate). Distinct from operations.contractors[] (Form 990 Part VII Section B top-5 highest-paid independent contractors).
| Field | Type | Description |
|---|---|---|
name | string | Contractor / firm name. |
activity_description | string | Description of fundraising activity. |
is_fundraiser | boolean | True when this entity is identified as a professional fundraiser (vs. another activity type). |
has_custody_of_funds | boolean | True when the contractor has custody / control of contributions. |
gross_receipts | integer | Gross receipts from the activity. |
amount_paid_to_org | integer | Amount retained by the org (net of contractor share). |
amount_paid_to_fundraiser | integer | Amount paid to or retained by the contractor. |
financial_profile
Our headline analytics section — every ratio here comes with a peer benchmark cube.
liquidity
| Field | Type | Description |
|---|---|---|
months_of_cash | number | Cash & savings ÷ monthly expenses. |
months_of_liquid_assets | number | Cash + savings + publicly traded investments, over monthly expenses. |
operating_reserve_months New | number | Unrestricted net assets ÷ monthly expenses. The standard donor-facing operating-reserve figure. |
peer_benchmarks | object | Legacy: 7-cut cube for months_of_cash. |
peer_benchmarks_by_metric New | object | Cubes for months_of_cash and operating_reserve_months. |
revenue_concentration
| Field | Type | Description |
|---|---|---|
herfindahl_index | number | HHI computed across 5 revenue buckets (contributions, program service, investment, fundraising events, other). Lower = more diversified. |
diversification_level | string | DIVERSIFIED (HHI < 0.30), MODERATE (0.30–0.55), or CONCENTRATED (≥ 0.55). Thresholds rebased April 2026 to match the real HHI floor of 0.20 (5 equal buckets). |
diversification_description New | string | Plain-English explanation of the bucket. |
index_range New | object | {min: 0.20, max: 1.00, buckets: 5, note: ...} — so consumers know how to interpret the scalar. |
operating_sustainability
| Field | Type | Description |
|---|---|---|
operating_margin_pct | number | (Revenue − expenses) ÷ |revenue|, percent. |
operating_reserve_months | number | Same as liquidity. |
peer_benchmarks_by_metric New | object | Cubes for both metrics. |
expense_composition New
Two views of where the dollars go — the functional split Charity Navigator uses, plus the FASB-required natural-category split from Form 990 Part IX.
Functional view (program / admin / fundraising)
| Field | Type | Description |
|---|---|---|
program_expense_ratio | number | Program services ÷ total expenses × 100. The traditional "how much goes to mission" ratio. |
admin_expense_ratio | number | Management & general ÷ total expenses × 100. |
fundraising_expense_ratio | number | Fundraising ÷ total expenses × 100. |
cost_to_raise_a_dollar | number | Fundraising expense ÷ contributions. Lower is more efficient. |
comp_to_expense_pct | number | Top-N compensation as percent of total expenses. |
Natural view (FTA — Part IX line items) New
| Field | Type | Description |
|---|---|---|
salaries_benefits_ratio | number | Part IX lines 5–10 ÷ total expenses × 100. Labor intensity — service-delivery orgs run 60–80%; grant-makers much lower. |
professional_fees_ratio | number | Part IX lines 11a–11g ÷ total expenses × 100. High values can flag heavy consultant / outside-fundraiser reliance. |
occupancy_ratio | number | Part IX line 16 ÷ total expenses × 100. Real-estate-heavy orgs (museums, hospitals) run higher. |
travel_ratio | number | Part IX line 17 ÷ total expenses × 100. |
grants_paid_ratio | number | Part IX lines 1–3 ÷ total expenses × 100. Grant-making foundations run very high; direct-service orgs near zero. |
salaries_benefits · professional_fees · occupancy · travel · grants_paid | integer | Raw Part IX dollar amounts — for callers that want their own cut (e.g. $/employee, $/beneficiary). |
peer_benchmarks_by_metric | object | Percentile cubes for the functional ratios + the two most universally benchmarkable FTA ratios (salaries_benefits_ratio, professional_fees_ratio). Occupancy / travel / grants_paid vary too much by org type to percentile meaningfully. |
total_expenses_before_depreciation New | integer | null | Derived: total_expenses − depreciation. Falls back to total_expenses unchanged when depreciation is unreported. |
total_expenses_percent_change_over_prior_year New | number | null | YoY % change in total_expenses. NULL when no prior-year filing exists for this EIN. |
Note: absolute third-party thresholds (Charity Navigator's 70% program benchmark, BBB Wise Giving's 65%, etc.) are deliberately NOT returned. Peer percentiles already place the org in its cohort, and absolute thresholds often penalize nonprofits for legitimate sector differences.
profitability New
Profitability derivations from financial_trends_analysis.business_model_indicators. Always-on under financial_profile (no include gate).
| Field | Type | Description |
|---|---|---|
unrestricted_surplus_before_depreciation | integer | null | total_revenue − (total_expenses − depreciation). Falls back to total_revenue − total_expenses when depreciation is unreported. |
surplus_as_percent_of_expenses_before_depreciation | number | null | surplus_before_dep / expenses_before_dep × 100. |
total_revenue_percent_change_over_prior_year | number | null | YoY % change in total_revenue. NULL when no prior-year row exists. |
_inputs | object | Debug/audit trail: total_revenue, total_expenses, depreciation, prior_total_revenue, prior_total_expenses. Exposed so callers can verify and reproduce the math. |
full_cost_components New
"True cost of operating" composite. PARTIAL today — includes only the IRS-derivable components (expenses + depreciation). Does NOT include debt_principal_payment + fixed_asset_additions, which come from audited financials (not on the 990).
| Field | Type | Description |
|---|---|---|
one_month_of_savings | number | null | total_expenses / 12 — the dollar value of one month of operating runway. |
total_full_costs_estimated | integer | null | Partial composite: total_expenses + depreciation. Under-estimates the full audited figure (which also includes audited-FS components). |
total_full_costs_is_partial | boolean | null | Always true until the audited-FS components surface. Flag so consumers don't mistake the partial sum for the full figure. |
total_full_costs_missing_components | string[] | null | Names of the components not included in the partial sum: ["debt_principal_payment", "fixed_asset_additions"]. |
balance_sheet_composition New
Land/buildings/equipment basis + ratios. Sourced from Schedule D Part VI line rollups. NULL when filing has no Schedule D Part VI.
| Field | Type | Description |
|---|---|---|
gross_land_buildings_and_equipment_lbe | integer | null | Sum of Schedule D Part VI cost_or_basis_invest + cost_or_basis_other. NULL when filing has no Schedule D Part VI. |
less_accumulated_depreciation | integer | null | Sum of Schedule D Part VI accumulated_depreciation. |
accumulated_depreciation_as_percent_of_lbe | number | null | accumulated_dep / gross_lbe × 100. |
liabilities_as_percent_of_net_assets | number | null | total_liabilities / net_assets × 100. |
accounting_ratios New
Liquidity ratio. IRS Form 990 does NOT break out current vs. non-current liabilities — we approximate "current liabilities" as Accounts Payable + Grants Payable + Deferred Revenue (Part X Lines 17+18+19).
| Field | Type | Description |
|---|---|---|
liquidity | number | null | cash_and_savings_eoy / current_liabilities_proxy. NULL when the proxy is zero or none of the three proxy components are populated. |
_inputs | object | Debug/audit trail: cash_and_savings_eoy, current_liabilities_proxy, components (AP / GP / DR), and a note explaining the proxy choice. |
operations New
Filing-level operations metadata. Always-on (no include gate).
contractors[] New
Top-5 highest-paid Form 990 Part VII Section B independent contractors. Distinct from fundraising.contractors[] (Schedule G Part I).
| Field | Type | Description |
|---|---|---|
name | string | Contractor / firm name. |
services | string | Description of services provided. |
compensation | integer | Annual compensation paid to the contractor. |
address New | string | null | Contractor business address line 1 (Part VII Section B). null when the filing does not list an address for the contractor. |
books_in_care_of New
Form 990 Part VI Line 20 — record-keeper contact (the person/firm responsible for keeping the books). Object emitted when at least one of the 5 fields is populated.
| Field | Type | Description |
|---|---|---|
address_type New | string | Always "BOOKS_IN_CARE_OF" when this block is present. Distinguishes from organization.address_type = "PRIMARY". |
name | string | Records-custodian name. |
city | string | City of records-custodian address. |
state | string | 2-letter state code. |
zip | string | ZIP code. |
phone | string | Phone number. |
Board / co-leader name fields New
Board-leadership names derived from a title-text heuristic over the Part VII Section A people roster. Documented as a heuristic — not a structured IRS field. Each key is emitted only when a match is found.
| Field | Type | Description |
|---|---|---|
board_chair_name | string | null | First person whose title matches /chair|chairman|chairwoman|chairperson/i AND does NOT match /vice/i and was not already classified as co-chair. |
board_co_chair_name | string | null | First person whose title matches /co-chair|co chair|co-chairperson/i. |
co_leader_name | string | null | First person whose title matches /co-ceo|co ceo|co-president|co-executive director/i AND is_officer = true. |
disclosures New Pro
Schedule-shaped disclosure data — tax-exempt bonds, non-cash contributions, significant dispositions, foreign individual grants, and interested-person transactions. Data Pro only — always present on /v1/data-pro, never on /v1/data. Schedule L lives here, not under governance.detail.
schedule_k.bonds[] New
Tax-exempt bond issues from Schedule K Parts I + IV. One row per bond.
| Field | Type | Description |
|---|---|---|
bond_ref_num | string | Issuer-supplied bond reference letter (A/B/C/D). |
issuer_name | string | Name of the issuing authority. |
issuer_ein | string | EIN of the issuer. |
cusip_number | string | CUSIP identifier for the bond issue. |
date_issued | string (date) | Date the bond was issued. |
issue_price_amt | integer | Issue price. |
issue_description | string | Description of the bond purpose. |
term_of_bond_yr | number | Term in years. |
maturity_date | string (date) | Maturity date. |
variable_rate_ind | boolean | True if variable-rate bond. |
deferred_issuance_costs_amt | integer | Deferred issuance costs. |
on_behalf_of_issuer_ind | boolean | True when the issuer was issued on behalf of the org. |
pooled_financing_ind | boolean | True for pooled-financing arrangements. |
private_loan_ind | boolean | True if proceeds funded a private loan. |
unspent_proceeds_amt | integer | Unspent bond proceeds remaining. |
acquired_bonds_amt | integer | Acquired bonds amount. |
nonqualified_bonds_outstanding | boolean | True when non-qualified bonds remain outstanding. |
arbitrage_yield | number | Arbitrage yield percentage. |
escrow_established_ind | boolean | True when defeasance escrow was established. |
defeasance_ind | boolean | True when bonds have been defeased. |
qualified_hedge_date | string (date) | Date of qualified hedge identification. |
no_private_biz_use_ind | boolean | True when no private business use is reported. |
voluntary_closing_agreement_ind | boolean | True when a voluntary closing agreement is in place. |
schedule_n.dispositions[] New
Significant disposition / liquidation detail (Schedule N). Only emitted when array is non-empty. Distinct from the top-level dissolution block (whole-org liquidation signal).
| Field | Type | Description |
|---|---|---|
description | string | Description of the asset / activity disposed. |
date | string (date) | Date of disposition. |
amount | integer | Amount of the disposition. |
acquirer | string | Name of acquirer / recipient. |
schedule_m.contributions[] New
Non-cash contribution types received during the tax year (Schedule M).
| Field | Type | Description |
|---|---|---|
type | string | Contribution-type label (e.g. Publicly-Traded Securities, Art, Vehicles). |
count | integer | Number of contributions of this type. |
revenue_amt | integer | Reported revenue amount. |
valuation_method | string | Method used to determine value (e.g. fair market value, appraisal). |
schedule_f New
Schedule F — foreign grants & activities. Distinct from the top-level foreign_activities[] (Part I region-level).
| Field | Type | Description |
|---|---|---|
individual_grants[] | object[] | Part III — grants to foreign individuals: region, purpose, recipient_count, cash_amount, disbursement_method, noncash_amount, noncash_desc. |
organization_grants[] | object[] | Part II — grants to foreign organizations: recipient_name, recipient_irc_section, recipient_ein, region, purpose, cash_amount, disbursement_method, noncash_amount, noncash_desc, valuation_method. |
metadata | object | null | Part II counts (irs_recognized_charity_count, other_organization_count, maintains_grant_records) + Part IV forms_required flags (form_926/3520/5471/8621/8865/5713). |
schedule_l New
Schedule L — transactions with interested persons. indicators carries the Part IV checklist flags. transactions is every Schedule L row for the organization-year in one flat array, and the four named arrays are that same array regrouped by transaction_type (each present only when it has rows).
Do not sum transactions with the four typed arrays — on a Form 990 filer they are the same rows, and adding them double-counts. Use one or the other. The exception is 990-PF filers: self-dealing rows (transaction_type: SelfDealing) appear in transactions and in none of the typed arrays, which are filtered to the Form 990 types.
All transaction objects share the same leaf set: person_name, relationship, description, amount, in_default, corrected, excess_benefit_date, uncorrected_amt, loan_from_org_ind, loan_written_agreement, grant_recipient_rel, grant_type_desc.
| Field | Type | Description |
|---|---|---|
excess_benefit_transactions[] | object[] | Part I — excess benefit transactions. |
loans_with_interested_persons[] | object[] | Part II — loans to/from interested persons. |
grants_to_interested_persons[] | object[] | Part III — grants/assistance benefiting interested persons. |
business_transactions[] | object[] | Part IV — business transactions involving interested persons. |
schedule_a New
Schedule A — public charity status & public support test. Present only when the filing has a public-support block.
| Field | Type | Description |
|---|---|---|
public_charity_status | string | Public-charity classification (e.g. 170(b)(1)(A)(vi)). |
public_support_pct | number | Public-support percentage. |
section_509a2 | object | 509(a)(2) figures: public_support_amt, total_support_amt, investment_income_amt, public_support_cy_pct, thirty_three_pct_test_passed. |
supporting_org | object | 509(a)(3) metadata: type, supported_org_count, all_supported_orgs_listed, notifies_supported_orgs, is_type3_functionally_integrated, is_type3_non_functionally_integrated, controlled_by_supported_orgs. |
supplemental_narrative[] | object[] | null | Part VI narrative entries: {reference, explanation}. |
schedule_o New
Schedule O — supplemental narrative explanations. Flat array; each entry is {form_section, reference, explanation}.
dissolution New
Top-level block. Emitted ONLY when the org liquidated or ceased operations during the tax year (i.e. liquidation_date or cessation_of_operations_date is populated on the filing). Absent otherwise. Always-on.
| Field | Type | Description |
|---|---|---|
date | string (date) | Date of liquidation or cessation. |
type | string | LIQUIDATION or CESSATION_OF_OPERATIONS — reflects which underlying date column was populated. |
conservation_easements / art_collections / private_foundation New
Three top-level always-on blocks (no include gate). Each is present only when the org has the relevant data; absent otherwise.
conservation_easements
Schedule D Part II.
| Field | Type | Description |
|---|---|---|
held_at_eoy_count | integer | Easements held at end of year. |
total_acreage | number | Total acreage under easement. |
modifications_count | integer | Easements modified, transferred, released, extinguished, or terminated during the year. |
violations_count | integer | Easement violations identified during the year. |
art_collections
Schedule D Part III — art / historical-treasure collection policies (booleans).
| Field | Type | Description |
|---|---|---|
uses_revenue_for_public_purpose | boolean | Collection revenue is used for a public service purpose. |
loans_to_public | boolean | Collection items are loaned for public exhibition. |
accession_not_for_financial_gain | boolean | Items are accessioned for purposes other than financial gain. |
private_foundation 990-PF only
Form 990-PF metrics. Present ONLY on 990-PF filers; absent for public charities (990 / 990-EZ). Five top-level rollup scalars plus nested Part I col B/C/D walkers, Part II FMV breakdown, Part III changes-in-net-assets, and Part XII qualifying-distributions detail. Empty nested objects mean “section not filed”.
| Field | Type | Description |
|---|---|---|
total_assets_fmv | integer | Total assets at fair market value. |
qualifying_distributions | integer | Qualifying distributions (Part XII). |
distributable_amount | integer | Distributable amount (Part XI). |
min_investment_return | integer | Minimum investment return (Part X). |
excise_tax | integer | Excise tax on net investment income. |
net_investment_income | object | Part I col B walker: total, interest, dividends, capital_gain, other_income, total_expenses. |
adjusted_net_income, charitable_disbursements, … | object | Additional Part I col C/D walkers + Part II FMV breakdown, Part III rollforward, Part XII components. |
hospitals New
Schedule H data. Present ONLY for 501(c)(3) hospital filers that report at least one hospital facility. Data Pro only — always present for those filers.
filing_summary
Org-wide Schedule H scalars (Parts I–III).
| Field | Type | Description |
|---|---|---|
has_financial_assistance_policy | boolean | Org has a written financial-assistance (charity-care) policy. |
total_community_benefit_at_cost | integer | Total community benefit at cost (Part I). |
direct_offsetting_revenue | integer | Direct offsetting revenue. |
net_community_benefit | integer | Net community benefit expense. |
community_benefit_pct_of_expenses | number | Net community benefit as a percent of total expenses. |
bad_debt_expense | integer | Bad debt expense (Part III Section A). |
medicare_revenue | integer | Medicare allowable revenue (Part III Section B). |
medicare_allowable_cost | integer | Medicare allowable cost. |
medicare_surplus_or_shortfall | integer | Medicare surplus (positive) or shortfall (negative). |
has_written_collection_policy | boolean | Org has a written debt-collection policy. |
hospital_facilities_count | integer | Number of hospital facilities reported. |
other_facilities_count | integer | Number of non-hospital health-care facilities. |
facilities[] · facility_policies[] · other_facilities[] · joint_ventures[] · community_benefit_by_category[]
| Field | Type | Description |
|---|---|---|
facilities[] | object[] | Part V Section A: facility_num, name, address_line1, city, state, zip, website, state_license, facility_types[] (tags: licensed_hospital, general_medical_surgical, childrens_hospital, teaching_hospital, critical_access, research_facility, er_24_hours, er_other), reporting_group, other_facility_desc. |
facility_policies[] | object[] | Part V Section B per facility: reporting_group, facility_name, chna (CHNA flags + years), fap (financial-assistance policy thresholds + flags), billing_and_collection (collection-activity flags). |
other_facilities[] | object[] | Part V Section D: facility_num, name, address_line1, city, state, zip, facility_type. |
joint_ventures[] | object[] | Part IV: entity_name, primary_activities, org_ownership_pct, officers_ownership_pct, physicians_ownership_pct. |
community_benefit_by_category[] | object[] | Part I by category: section, category, activities_or_programs_count, persons_served, total_community_benefit_expense, direct_offsetting_revenue, net_community_benefit_expense, expense_pct_of_total. |
related_organizations / foreign_activities / lobbying / DAFs
| Section | Source | Description |
|---|---|---|
related_organizations | Schedule R | Full entity roster: disregarded entities, related tax-exempt orgs, related taxable partnerships/corps. Read org_type first — it names the Schedule R part the entity came from (disregarded_entity = Part I, tax_exempt = II, taxable_partnership = III, taxable_corp_trust = IV) and determines which fields are populated. predominant_income_type, disproportionate_allocations and general_or_managing_partner appear for Part III only; entity_type for Part IV only. Every object carries the full key set, so out-of-part fields are null, not missing. ein is often null — filers need not supply one for disregarded or foreign entities. |
related_organization_transactions New | Schedule R Part V | Inter-organization transaction ledger. Top-level flat array, sibling to related_organizations. Each row: related_org_name, transaction_type (Part V Line 1 code a–s), transaction_amt. Always-on. |
foreign_activities | Schedule F | Regions, activity types, revenue & expenditures abroad, foreign grants. |
lobbying_and_political | Schedule C | Lobbying expenditures (direct & grassroots), 501(h) election, political campaign expenditures. |
contributor_summary | — | Always empty. The IRS redacts the entire Schedule B contributor block in public filings — not just names, but addresses and contribution amounts too, each replaced with the literal string RESTRICTED. No provider can return this data from public filings. The key is retained for response-shape stability. |
donor_advised_funds | Schedule D.I | Funds held, aggregate balance, contributions during year. |
registration Pro
IRS Business Master File, Publication 78, and California AG/FTB status, joined at request time. This is registration status — it comes from the agencies, not from the Form 990 filing, so it reflects the org today rather than the tax year you requested.
registration.bmf
Business Master File fields: subsection, classification, foundation_code, deductibility_code, affiliation, ruling_date (YYYY-MM), group_exemption, activity, status, ntee, filing_requirement, pf_filing_requirement, in_care_of, plus the financials_at_ruling snapshot.
Every coded field is also served pre-decoded, so you don’t need to carry IRS code tables:
| Field | Type | Description |
|---|---|---|
subsection_label | string | null | Decoded subsection, e.g. "501(c)(3) — Religious, educational, charitable…". |
classification_labels | string[] | null | Decoded classification. An array, because the classification code is a bitmask that decodes to zero or more labels — e.g. ["Charitable"]. |
foundation_label | string | null | Decoded foundation_code, e.g. "Publicly supported charity 170(b)(1)(A)(vi)…". |
deductibility_label | string | null | Decoded deductibility_code, e.g. "Contributions are deductible". |
affiliation_label | string | null | Decoded affiliation, e.g. "Central — central organization of a grouping (no group exemption)". |
status_label | string | null | Decoded status, e.g. "Unconditional exemption". |
filing_requirement_label | string | null | Decoded filing_requirement, e.g. "Form 990 required (all filers)". |
pf_filing_requirement_label | string | null | Decoded pf_filing_requirement, e.g. "Form 990-PF not required". |
financials_at_ruling carries a BMF financial snapshot (tax_period, accounting_period_end_month, asset_code/asset_code_label, income_code/income_code_label, asset_amount, income_amount, revenue_amount). Despite the name it reflects the most recent BMF refresh, not the ruling year literally.
registration.pub78
| Field | Type | Description |
|---|---|---|
listed | boolean | Whether the org appears in IRS Publication 78. |
deductibility | string | null | Raw Pub 78 deductibility code, e.g. "PC". |
deductibility_labels | string[] | null | Decoded, e.g. ["Public charity"]. An array because one code can carry more than one meaning. |
registration.california
CA Attorney General Registry + Franchise Tax Board status — the same shape GET /v1/verify returns as state_compliance.california.
When the checks run: for every organization, since 2026-09-03. The Attorney General's list is not California-only, so there is no "not applicable" case any more.
| Field | Type | Description |
|---|---|---|
state_status | string | Roll-up: CLEAR (not on the AG May Not Operate list, no FTB revocation), NON_COMPLIANT (on the AG list), REVOKED (FTB). Test this field rather than null-checking the sub-objects. |
ag_registry | object | null | Result against the AG's May Not Operate list: status (NOT_LISTED, or REVOKED / SUSPENDED / DELINQUENT / CEASE_AND_DESIST / LISTED), registration_status and entity_status (verbatim), reg_number, match_method, notice (when not listed). |
ftb | object | null | FTB result: status — "CLEAR" or "REVOKED". |
Two things to get right. NOT_APPLICABLE means the org isn’t California-based and no check ran — it is not a compliance finding, so don’t render it as one. And ag_registry.match_method tells you how much to trust the match: fein is an exact EIN match, while name is a name+city match and is weaker evidence — treat name-matched results as advisory.
For a non-CA org the block is still present with a stable shape:
{
"trigger": null,
"ag_registry": null,
"ftb": null,
"state_status": "NOT_APPLICABLE"
}filing_meta New
Filing-level metadata for the response year, drawn from the Form 990 return header. Always-on (no include gate).
| Field | Type | Description |
|---|---|---|
tax_period_begin | string | null | Start of the tax period (YYYY-MM-DD). |
tax_period_end | string | null | End of the tax period (YYYY-MM-DD). |
schema_version | string | null | IRS e-file schema version of the filing. |
preparer_firm | string | null | Paid-preparer firm name. |
preparer_firm_address New | string | null | Paid-preparer firm street address (return header). |
preparer_firm_city New | string | null | Preparer firm city. |
preparer_firm_state New | string | null | Preparer firm 2-letter state code. |
preparer_firm_zip New | string | null | Preparer firm ZIP (first 5 digits). |
preparer_firm_ein New | string | null | Preparer firm EIN. |
preparer_phone New | string | null | Preparer phone number. Note: the IRS schema attaches the phone to the preparer person, not the firm — there is no firm-phone element. |
tax_status_type | string | null | 501(c) / 4947(a) tax-status type from the return header. |
data_freshness
| Field | Type | Description |
|---|---|---|
filing_source | string | Always "IRS TEOS e-file". |
most_recent_tax_year | integer | Latest filing year ingested for this EIN. |
filing_date | string | Filing date of the response year. |
last_filing_date | string | Most recent filing date for this EIN overall. |
years_available | integer[] | All tax years on file. |
includes_applied | string[] | Diagnostic — which internal bundles applied to this response (empty on base). |
Peer Benchmarks: How Cubes Work
Every ratio in compensation.peer_benchmarks_by_metric and financial_profile.*.peer_benchmarks_by_metric returns a cube: the same metric sliced across seven peer-group dimensions, each with the same six percentile stats.
Shape of a single cube
{
"by_ntee": { ...PeerStats },
"by_revenue_band": { ...PeerStats },
"by_state": { ...PeerStats },
"by_ntee_and_revenue_band": { ...PeerStats },
"by_ntee_and_state": { ...PeerStats },
"by_revenue_band_and_state": { ...PeerStats },
"by_ntee_and_revenue_band_and_state": { ...PeerStats }
}PeerStats
| Field | Type | Description |
|---|---|---|
peer_group | string | Human label, e.g. "P – Human Services ∩ $100M+ ∩ DC". |
peer_count | integer | Number of orgs in the cohort for that tax year. |
p10, p25, median, p75, p90, mean | number | Distribution statistics for the metric in that cohort. |
note | string (optional) | Appears when peer_count < 10: "Small peer group — interpret with caution". |
Peer Benchmarks: 7 Dimensional Cuts
Each cube is the same metric sliced seven ways. The intersection cuts (triple) are the narrowest and usually most meaningful, but may have small cohorts.
NTEE major group
A–Z first letter of the IRS National Taxonomy of Exempt Entities code. Examples: A Arts & Culture, B Education, E Health, P Human Services.
Revenue band
Eight bands based on total revenue: 1 <$100K, 2 <$500K, 3 <$1M, 4 <$5M, 5 <$10M, 6 <$50M, 7 <$100M, 8 $100M+.
State
Two-letter state code from the organization's mailing address.
Intersections
4 compound cuts: ntee ∩ revenue_band, ntee ∩ state, revenue_band ∩ state, and the triple ntee ∩ revenue_band ∩ state.
Peer Benchmarks: Metrics Covered
17 metrics currently ship with peer-benchmark cubes. New metrics are added as underlying data coverage grows.
| Metric | Surfaced at |
|---|---|
total_revenue | (internal reference) |
top_officer_comp | compensation.peer_benchmarks, compensation.peer_benchmarks_by_metric |
top_officer_comp_to_revenue_pct New | compensation.peer_benchmarks_by_metric |
ceo_comp New | compensation.peer_benchmarks_by_metric |
cfo_comp New | compensation.peer_benchmarks_by_metric |
dev_director_comp New | compensation.peer_benchmarks_by_metric |
avg_compensation_per_employee New | compensation.peer_benchmarks_by_metric |
program_expense_ratio | financial_profile.expense_composition.peer_benchmarks_by_metric |
admin_expense_ratio New | financial_profile.expense_composition.peer_benchmarks_by_metric |
fundraising_expense_ratio New | financial_profile.expense_composition.peer_benchmarks_by_metric |
cost_to_raise_a_dollar New | financial_profile.expense_composition.peer_benchmarks_by_metric |
comp_to_expense_pct | financial_profile.expense_composition.peer_benchmarks_by_metric |
salaries_benefits_ratio New | financial_profile.expense_composition.peer_benchmarks_by_metric |
professional_fees_ratio New | financial_profile.expense_composition.peer_benchmarks_by_metric |
months_of_cash | financial_profile.liquidity.peer_benchmarks, financial_profile.liquidity.peer_benchmarks_by_metric |
operating_reserve_months New | financial_profile.liquidity + operating_sustainability |
operating_margin_pct | financial_profile.operating_sustainability.peer_benchmarks_by_metric |
Pro-Only Attachments Pro
Calling /v1/data-pro attaches four extra sections on top of the base shape.
financials.detail
Line-by-line Part VIII (revenue) and Part IX (expenses) breakdowns plus Schedule A (public charity status), Schedule D (supplemental financials) highlights, Schedule O (narratives). Also carries summary_extras.gross_receipts, expenses_extras.other_program_service_expenses, reconciliation (Part XII) and reconciliation_part_xi.net_unrealized_gains_losses.
Newly surfaced child-table arrays New
| Field | Type | Description |
|---|---|---|
part_8_revenue.programs[] | object[] | Form 990 Part VIII Line 2a–2f program-service revenue rows. Each: description, business_code, total_revenue, related_revenue, unrelated_revenue, exclusion_amt. |
part_8_revenue.lines[] New | object[] | Form 990 Part VIII Lines 3–12 — investment income, bond proceeds, royalties, net rental, sales of assets, fundraising events, gaming, sales of inventory, miscellaneous revenue (11a–d) and the Line 12 total. Same four-column split as programs[], plus line_number (IRS label as text, e.g. "6d"). A line appears only if the organization filed it — absent means “not reported”, not zero. |
part_8_revenue.totals New | object | Part VIII Line 12 as filed: total_revenue, related_revenue, unrelated_business_revenue, exclusion_amount. Use this for unrelated business revenue. Summing unrelated_revenue across programs[] gives program-service revenue only — investment income, rents, royalties and asset sales carry unrelated amounts too. |
schedule_d.assets[] | object[] | Schedule D Part IX — other assets detail. Each: description, book_value_boy, book_value_eoy. |
schedule_d.investments[] | object[] | Schedule D Parts VII (other securities) + VIII (program-related investments). Each: description, valuation_method, book_value_eoy. |
schedule_d.other_liabilities[] | object[] | Schedule D Part X — other liabilities. Each: description, book_value_eoy. Note: only EOY (no BOY pair). |
governance.detail
Full voting-board roster with titles, committee memberships, compensation-review process, meeting-minutes flags, and policies (conflict of interest, whistleblower, document retention, joint venture, independence). Also headcount, schedule_j_flags, activity_indicators, contributions_handling, compliance_filings, schedule_o_explanations, books_in_care_of and liquidation. Schedule L lives under disclosures, not here.
registration
IRS Business Master File (with every coded field also served pre-decoded), Publication 78 listing, and California AG Registry + FTB status. Full field list under registration.
Error Codes
| Status | Error | Meaning |
|---|---|---|
| 400 | MISSING_EIN | The ein query param is required. |
| 400 | INVALID_EIN | EIN is not 9 digits / not parseable. |
| 400 | INVALID_TAX_YEAR | tax_year must be a 4-digit integer. |
| 403 | IP_NOT_ALLOWED | The calling IP is not on the allowlist for this API key. |
| 403 | ENDPOINT_NOT_LICENSED | Your key reached an endpoint your agreement does not include — access is scoped per product. The body names the product you asked for (requested_product) and lists the ones your key holds (licensed_products), so you can tell a licensing boundary apart from a bug without contacting us. |
| 403 | Forbidden | Missing or invalid x-api-key header. |
| 404 | NOT_FOUND | No filing of any kind for this EIN, including Form 990-N (or none for the requested year). Response includes years_available[], which counts postcard years too. Data Pro only. |
| 500 | INTERNAL_ERROR | Unexpected server error. request_id included for support tickets. |
Search-specific errors (POST /v1/data)
| Status | Error | Meaning |
|---|---|---|
| 400 | EMPTY_REQUEST | Body needs search_terms, filters, or prompt. |
| 400 | PROMPT_CONFLICT | prompt cannot be combined with search_terms/filters. |
| 400 | UNKNOWN_FIELD | Unrecognized field; the message names the JSON path. |
| 400 | UNSUPPORTED_FILTER | A recognized field from other nonprofit data APIs that we don't carry — the hint names the nearest equivalent. |
| 400 | INVALID_FILTERS / INVALID_RANGE / INVALID_SORT / INVALID_SIZE / INVALID_PAGINATION / INVALID_PROMPT / INVALID_SEARCH_TERMS | Field-level validation; the message says which field and why. |
| 400 | MULTI_EIN_NOT_SUPPORTED | One EIN per request (multi-EIN monitoring is planned). |
| 429 | ASK_QUOTA_EXCEEDED | Daily Ask-prompt quota reached. Body carries quota {plan, limit, used, resets_at}; Retry-After header set. Structured search unaffected. |
| 502 | UPSTREAM_ERROR | Search engine temporarily unavailable — retry. |
| 504 | UPSTREAM_TIMEOUT | Search engine timeout — retry. Timed-out asks don't count against quota. |
FaithVerify API
Verify a church from its name, city and state. No EIN required — and for most churches, none exists.
POST /v1/faith-verify
{"name": "First Baptist Church", "city": "Dallas", "state": "TX"}
Every response carries a verdict with a plain-language recommendation, and the evidence behind it.
Why churches are different
A church is exempt under 501(c)(3) automatically, without applying, and is not required to file a Form 990 or appear in Publication 78 (IRC §508(c)(1)(A)).
Two consequences shape this API:
- The EIN often does not exist.
GET /v1/verifystarts from one, so it cannot answer for a congregation that never applied. - Absence from IRS lists is normal, not suspicious. A church missing from Publication 78 has not failed anything — it was never required to be there. Treating that absence as a negative signal would decline legitimate churches at scale.
So FaithVerify gathers evidence in layers and tells you which layer answered, rather than reducing everything to present-or-absent in one list.
Quickstart
curl -X POST https://api.givalgo.ai/v1/faith-verify \
-H "x-api-key: $GIVALGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "First Baptist Church", "city": "Dallas", "state": "TX"}'
A church the IRS or a denomination vouches for answers immediately:
{
"triage": { "status": "resolved", "matched_in": ["denominational_directory", "irs_bmf"] },
"irs": { "ein": "75-0800696", "irs_classified_church": true },
"verdict": { "status": "ELIGIBLE", "deductible": true, "basis": "group_exemption" }
}
If the records do not settle it, you get 202 and a poll URL instead — see Website evidence.
Verify a church
POST /v1/faith-verify
| Field | Required | Notes |
|---|---|---|
name | yes | As you know it. Abbreviations resolve — “First UMC” finds “First United Methodist Church”. |
city | yes | |
state | yes | Two-letter code (OH) or the full name (Ohio), either case. Send DC or District of Columbia for the District — Washington alone is the state. |
street | no | Needed only when a call returns needs_street. Supplying it up front never hurts. |
skip_website_evidence | no | Stay synchronous — never return 202, take the record answer as final. |
force_refresh | no | Re-crawl even if a review from the last 30 days exists. |
Poll a website review
GET /v1/faith-verify/{job_id}
Returns the complete verification — triage, irs, denominational, website_evidence and verdict — not just the crawl. You never hold half an answer.
The record-layer findings are exactly as they stood when the job was created, so the verdict stays pinned to the facts that produced it.
Cadence: most reviews finish in under two minutes. Poll first at ~20s, then every 10s, and give up after 5 minutes.
status is queued or running while it works, then done — including when the review was inconclusive, which is a result rather than a failure. failed means our own error and is safe to retry.
Job ids are scoped to the key that created them; polling another key’s returns 404.
Three outcomes
All three return 200. Branch on triage.status.
| Status | Meaning | What to do |
|---|---|---|
resolved | We know which church you mean. | Read the verdict. |
needs_street | We cannot pin the church to one record from the name alone — either several match, or the closest listing names a town rather than a congregation and does not carry your name. | Re-send with street; triage.candidate_addresses lists what we hold. When reason is location_only_listing the website review runs too, so a street adds a record match rather than identifying the church. |
not_found | No record matched — not in the IRS BMF, not in the 28 denominational directories, in your city or within 5 miles of it. It is not a statement that the church does not exist. | Nothing: this is what launches the website review. If that review comes back empty too, check the spelling or try the name as it appears on the building. |
A name that identifies a town is not an answer. Some denominations file congregations under the place rather than a congregation name — an LDS ward listed as Batesville, a Kingdom Hall listed as its address. Such a record is only returned when you wrote that same name; ask about a different church in that town and you get needs_street with the addresses to choose from, never the ward.
A church one town over is still found. Congregations routinely file with the IRS under a neighbouring municipality, so matching your city exactly reports real churches as missing — A Restoration Church, Glenview IL is in the IRS file under Northbrook, 3.8 miles away. When your city matches nothing, we search the towns within 5 miles and return triage.nearby_city_match naming the city we actually found it in and the distance. A church in the town you named always wins; the radius is only a fallback.
When we hold no record of your church, the website review runs. That is what it is for. We ask you to choose an address only when the choice is real — several congregations we genuinely cannot tell apart, or a record carrying your church's name at a different address. If nothing we hold matches the name you gave, you get not_found and a website review, not a list of other people's churches.
needs_street and not_found are answers, not errors. Your request was well-formed; we could not settle it. A 4xx would put them in the same bucket as a malformed body and invite a retry instead of an action.
A city holds six Kingdom Halls and four St Mary’s. When a name identifies a denomination and a town rather than a congregation, guessing would produce a confidently wrong answer — so we ask instead.
The three evidence layers
Gathered strongest first, stopping at the first that answers. An answer from the IRS is not improved by reading a website.
| Layer | Source | Response |
|---|---|---|
| 1 · IRS | EO BMF → Publication 78, directly or via the parent’s group exemption. Includes revocation and OFAC screening. | Immediate |
| 2 · Denominational | 175,881 congregations across 28 denominations. A listing means the denomination’s group ruling covers it. | Immediate |
| 3 · Website | The five characteristics the IRS uses to recognise a church, read from the church’s own site. | 202 → poll |
Many churches are covered by a denomination’s group exemption rather than holding their own determination. We follow that link: a subordinate not listed in Publication 78 is checked against its parent, and irs.group_exemption reports the parent’s EIN and standing.
Website evidence
Reached only when neither record layer settles the question. We find the church’s website — for most we have no URL on file — confirm it belongs to that congregation, read it, and grade five criteria.
| Criterion | What comes back |
|---|---|
ministers | Organized leadership — every named leader or officer with their title, plus how leadership is constituted. Satisfied by ordained clergy or a named governing body: the IRS criterion is "an organization of ordained ministers" or "a definite and distinct ecclesiastical government", and many traditions ordain nobody. |
doctrine | The denomination claimed, the topics the statement covers, and the church’s own wording of what it believes. |
services | Each regular gathering as day, time, and the schedule line exactly as printed. |
calendar | Named events with dates, recurring programmes, and what the church’s month looks like. |
contact | Street address, city, state, ZIP, phone and email. |
Each carries a status, a summary, and evidence_url — the page the finding came from, so you can check it yourself.
Every value is verified against the page
Anything you might act on — an address, a phone number, an email, a service time, a person’s name — is dropped if we cannot find it on the page we read, and listed in facts.unverified_dropped. That distinction matters: “the church publishes no phone number” and “a phone number was produced that we could not confirm” are different facts.
Doctrinal statements are kept with verbatim: false when they are close paraphrases rather than quotes.
not_found is not fail
A criterion is not_found when no page addressing it was available to read, and fail when the topic was covered and the characteristic was genuinely absent. A church with a thin website has not failed the test.
What a denominational listing means — and what it does not
A listing is evidence the congregation exists and is affiliated with that body. It is not confirmation of IRS group-exemption coverage. Where coverage exists we report it from the organisation’s own IRS record, under irs.group_exemption — never inferred from the directory, and never estimated from how the denomination usually files.
We measured the difference rather than assuming it. For up to 300 congregations per directory we found the organisation in the IRS Business Master File and read its affiliation code: subordinacy is the norm for some bodies (ELCA 99%, LCMS 99%, Assemblies of God 90%) and rare for others (UPCI 29%, LDS 9%). That measurement is why this endpoint no longer tells you a listing means the denomination’s group ruling covers the congregation — for several bodies that was simply untrue.
⚠️ We deliberately do not publish that per-denomination rate as a field. affiliation and group_code are Business Master File columns, so being recorded as a group subordinate requires an IRS record. A congregation that has one already has its own answer in irs.group_exemption; a congregation that has none is excluded from the population the rate was measured on, and its absence is if anything mild evidence against coverage. A denomination-level average shown beside it would reassure in the wrong direction.
What the sanctions screening covers
The organization’s name, and nothing else. A church does not file a Form 990 — that is the premise of this endpoint — so we hold no officer or director records for one. Rather than return an empty leadership_screening block that reads as “we checked the leadership and found nothing”, the block is omitted and sanctions_screening.scope says what was covered.
⚠️ Leaders named on the church’s own website appear under website_evidence and have not been screened. If a church does have officers on record, the leadership_screening block is present and real.
We do not score it
evidence.tally counts the five criteria by status — pass, partial, fail, not_found. That is all it does. There is no threshold and no pass mark, because the IRS sets none: its own guidance says “there is no minimum number of criteria an organization must meet” and warns that a numerical approach “might unconstitutionally favor established churches at the expense of newer, less traditional institutions”. We collect the evidence; the rule for acting on it is yours.
Results are cached for 30 days per church. Two requests arriving together share one crawl.
Limited release
FaithVerify is enabled per API key while we finish validating it against real queries. If your key does not have access yet, every FaithVerify route answers 403 with error: "FORBIDDEN" — your key is fine, this endpoint just is not switched on for it. Email support@givalgo.ai to request access. Every other endpoint is unaffected.
Verdicts
The status says where the church was found, not how eligible we judge it. What was found lives in deductible, basis and message.
| Status | Meaning |
|---|---|
FOUND_IRS | An IRS record — Publication 78, a group exemption whose parent is listed, or the church classification itself. |
REVOKED_ON_IRS | The IRS has auto-revoked the organization’s exempt status. Also an IRS record, but kept separate because it is the one finding that means no — it overrides every positive basis, and carries deductible: false. |
FOUND_DENOMINATIONAL_DIRECTORY | The congregation appears in a denomination’s own current directory, and nowhere in IRS data. |
FOUND_WEBSITE_EVIDENCE | No record in either, so we read the church’s own website against the five IRS characteristics. |
UNDETERMINED | We looked everywhere and came back empty: no IRS record, no directory listing, and either no website found or a review that produced nothing. Not the same as ineligible — no evidence, not evidence against. |
⚠️ Revocation is the exception, and it has its own status. Everything else the status tells you is where we found the church; deductible and basis tell you what we found. One case is not visible in the status at all: a church whose IRS row contradicts itself returns FOUND_IRS with deductible: null and basis: "irs_church_classification_contradicted". Branch on deductible and basis if that matters to you.
verdict.basis names what the answer rests on, and verdict.recommendation says what to do next in plain words.
Deductibility code 2 does not by itself decline a church. Where Publication 78 or a group ruling settles the question, deductible stays true and the contradictory code is reported next to it — the code is applied inconsistently even within a single group ruling, so it is not treated as decisive. Where the church classification is the only basis, nothing outvotes it: deductible becomes null and the basis is irs_church_classification_contradicted. /v1/verify answers the same way for the same organization.
Website evidence never moves the verdict at all. It stays UNDETERMINED with basis: "website_evidence", and the marks, the extracted facts and the tally are attached for you to judge. Five characteristics on a website are not an IRS determination — and neither is a count of them, which is why we stopped publishing one.
Research API
Pass a nonprofit's website URL or its EIN and get back the fields the organization states about itself. Today that is its mission.
With an EIN we resolve the organization's website for you, from IRS filings and our own verified records — useful when your own records are stale, incomplete, or point at a domain that has since lapsed.
This endpoint reads the organization's website and nothing else. It is not derived from Form 990 — for filing data use the Data API. The two pair naturally: Data Pro tells you what an organization filed, Research tells you what it says.
It is also not a generic web scraper. You do not get HTML or markdown to post-process; you get named fields, each carrying the exact URL it came from.
mission is quoted from the page character for character — we verify every quote against the crawled page before returning it. If we cannot find a quotable mission, mission is null and a separate mission_proxy field may carry a short description we composed. The two are never mixed. See Mission & mission_proxy.
Authentication
Send your API key in the x-api-key header on every request, exactly as with the other Givalgo APIs.
curl https://api.givalgo.ai/v1/research \
-H "x-api-key: YOUR_API_KEY"
Don't have a key? Contact support@givalgo.ai.
Quickstart
Step 1 — submit. Send a url, an ein, or both. At least one is required.
By website:
curl -X POST https://api.givalgo.ai/v1/research \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{"url": "https://www.hrw.org", "ein": "13-2875808"}'
By EIN alone — we resolve the website ourselves:
curl -X POST https://api.givalgo.ai/v1/research \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{"ein": "13-2875808"}'
The response's url_source tells you which happened: caller if we used the URL you sent, discovered if we resolved it.
{
"job_id": "rj_evtOorsq7NkVSx6AUNs4Rw",
"status": "queued",
"host": "hrw.org",
"poll": "/v1/research/rj_evtOorsq7NkVSx6AUNs4Rw",
"cached": false,
"expected_ready_in_seconds": 30
}
Step 2 — poll for the result.
curl https://api.givalgo.ai/v1/research/rj_evtOorsq7NkVSx6AUNs4Rw \
-H "x-api-key: YOUR_API_KEY"
{
"job_id": "rj_evtOorsq7NkVSx6AUNs4Rw",
"status": "done",
"host": "hrw.org",
"url": "https://www.hrw.org",
"fetch_status": "ok",
"cached": false,
"crawled_at": "2026-08-10T22:44:29Z",
"pages_crawled": 3,
"fields": {
"mission": {
"value": "Everything we do circles back to our commitment to justice, dignity, compassion, and equality.",
"kind": "unlabeled",
"source_url": "https://www.hrw.org/about-us",
"verbatim": true
},
"mission_proxy": null
}
}
Polling
Every submit returns 202 with a job_id — including when we already hold a recent result for that website. There is no synchronous variant, so your integration has one code path no matter how slow the target site is.
A crawl typically completes in 15–30 seconds. Poll first at about 20 seconds, then every 10 seconds, and give up after 5 minutes.
"cached": true, the result already exists and your first poll returns it immediately.
If you would rather not poll at all, tell us — a webhook callback is on the roadmap and customer demand sets its priority.
Submit a job
POST /v1/research
| Field | Type | Required | Description |
|---|---|---|---|
url | string | One of these two | The organization's website. Accepts what signup forms actually collect — with or without scheme, with or without www., mixed casing, a trailing path. |
ein | string | One of these two | EIN with or without hyphen. Send it even when you have the URL — see below. |
fields | array | No | Which fields to extract. Defaults to ["mission"], currently the only available field. |
force_refresh | boolean | No | Bypass the 30-day cache and re-crawl. Always counts against your quota. |
Returns 202 with job_id, host, poll, cached, url_source, and expected_ready_in_seconds.
status is queued when a crawl has started, or done when the answer is already available — a website we hold a recent result for, or a request that resolved to nothing crawlable. The result is never in this response either way, so always poll; done just means the wait is nil.
| Error | Status | Meaning |
|---|---|---|
MISSING_URL | 400 | url was absent or empty. |
INVALID_URL | 400 | No usable website host could be derived (e.g. "N/A", an email address, an aggregator profile page). |
FIELD_NOT_AVAILABLE | 403 | A recognized field that is not yet available. The response names the tier that will carry it. |
UNKNOWN_FIELD | 400 | Not a field this API extracts. The response lists what is available. |
RESEARCH_QUOTA_EXCEEDED | 429 | Daily crawl quota reached. Carries Retry-After and a quota object. |
Sending a URL, an EIN, or both
At least one is required. What you send changes what we can do for you.
| You send | What we crawl |
|---|---|
url only | Your URL. If it turns out to be a domain we know is parked or hijacked, we stop and tell you — there is nothing to resolve from. |
ein only | We resolve the organization's website ourselves, from IRS filings and our own verified records. |
| both | Your URL — unless it is a parked or hijacked domain, in which case we resolve the real website from the EIN instead. |
The response always reports which we used:
| Field | Meaning |
|---|---|
url_source: "caller" | We crawled the URL you sent. |
url_source: "discovered" | We resolved the website ourselves from the organization's IRS filings. Check url to see which site that was. |
url_source: "llm_discovered" | Every website we had on file for this organization is verified unreachable (or we had none), so we searched the web for its current site and confirmed the match against the organization's own IRS name and location before crawling it. |
reason | Why you did not get a mission — see the table below. Always set when mission is null. |
You are never silently redirected — if we crawled a different site than you asked about, url_source and url say so.
Why a mission can be missing
A null mission always comes with a reason. The first three are settled answers about the organization; the rest are about one website read that did not work.
reason | Meaning | Retry? |
|---|---|---|
website_dead | The website this organization filed with the IRS no longer resolves, and a web search for a current one found nothing we could confirm as theirs. | No |
no_website_on_file | We hold no website for this organization, and a web search found none. | No |
caller_url_parked / caller_url_hijacked | The URL you sent is a parked or squatted domain. We will not crawl it and report what it is instead. | No — send an EIN and we will look for the real site |
fetch_failed, js_required, rate_limited, robots_denied | We had a live site and could not read it. Mirrors fetch_status. | fetch_failed and rate_limited: yes, later |
Poll a job
GET /v1/research/{job_id}
status is queued or running while the crawl is in flight, then done. A failed status means the job errored on our side.
done result, not an error. You get status: "done", a null mission, and a fetch_status explaining why. Your signup or onboarding flow should never break because a nonprofit's website was down or blocks crawlers.
Job IDs are scoped to the API key that created them. Polling another key's job returns 404 JOB_NOT_FOUND.
Mission & mission_proxy
These are two separate fields, and the distinction is the point of the API.
| Field | What it contains |
|---|---|
mission | Text quoted from the website, character for character. Every value is verified against the crawled page before it is returned; anything we cannot locate on the page never appears here. Carries verbatim: true and the source_url it came from. |
mission_proxy | A short description we composed from the site's content, returned only when the website never states a mission you could quote. Carries verbatim: false and a derived_from array. |
If you want only what the organization actually wrote, read mission and ignore mission_proxy entirely. Both may be null — some websites simply don't say.
The kind field
| Value | Meaning |
|---|---|
labeled | Found under an explicit mission heading — "Our Mission", "Mission Statement". |
unlabeled | A mission-functioning sentence found elsewhere: a homepage banner line, the opening line of an About page, a "We exist to…" sentence in prose. |
Both are quoted verbatim — kind only tells you where on the site the sentence sits. Many nonprofits never write the words "Our Mission" but state one perfectly clearly, and unlabeled is how you get it.
Fetch statuses
fetch_status tells you how the crawl went. Anything other than ok or partial means we returned less than we wanted to.
| Status | Meaning | Retry? |
|---|---|---|
ok | Crawled cleanly. | — |
partial | Some pages were reachable, some were not. | — |
js_required | The site renders entirely in JavaScript and we could not read enough of it. | Unlikely to help |
fetch_failed | DNS, TLS, 404, or timeout. | Yes, later |
rate_limited | The site throttled us. | Yes, later |
robots_denied | The site's robots.txt disallows crawling. We respect it. | No |
no_website | The submitted URL named no usable website. | No — fix the URL |
We identify ourselves honestly as GivalgoResearchBot and honor robots.txt. Some sites will legitimately yield nothing.
Caching
Results are cached for 30 days per website. Caching is keyed on the website's host, so example.org, www.example.org, and https://WWW.Example.org/about all resolve to the same cached result.
Cached results do not count against your daily quota. Two fields tell you what you got:
| Field | Meaning |
|---|---|
cached | true when the result came from an earlier crawl; false when this job produced it. |
crawled_at | When the underlying crawl actually ran — the result's true age. |
Pass force_refresh: true to bypass the cache. Forced requests always count against your quota.
Organizations API
List every nonprofit in a place — a county, a city, a ZIP, or a whole state — with a lean identity-and-location record and a cursor that does not run out.
Built for the case where you need the whole set rather than the best matches: a community foundation mapping its service area, a directory keeping a county in sync, a dashboard counting nonprofits per county.
When to use this instead of the Data API
| /v1/organizations Organizations | /v1/data Data | |
|---|---|---|
| Question | “Give me every org in here” | “Which orgs best match this?” |
| Ordering | Stable, by EIN | By relevance |
| Depth limit | None — page until has_more is false | 2,000 results |
| Record | Lean: identity + location | Rich: financials, mission, contact, NTEE detail |
| County filter | Yes | No |
| Free-text search | No | Yes, plus natural-language Ask |
Rule of thumb: if you would page through more than a couple of hundred results, or you need a county, use this endpoint. If you are searching by name, mission or cause, use the Data API.
Quickstart
Every 501(c)(3) in Harris County, Texas — a hundred at a time:
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&subsection=03&limit=100"
{
"organizations": [
{
"ein": "01-0610009",
"name": "HOPE CENTER INC",
"dba_name": null,
"street": "3618 STASSEN ST",
"city": "HOUSTON",
"state": "TX",
"zip": "77051-1544",
"county": "Harris County",
"ntee_code": "T20",
"ntee_source": "irs",
"subsection": "03"
}
],
"count": 100,
"next_cursor": "010610009",
"has_more": true
}
Then walk the cursor until has_more is false:
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&subsection=03&limit=100&cursor=010610009"
List organizations
One endpoint. view chooses what comes back — organizations (the default), counts, or the change feed — and every filter below means the same thing in all three.
view | Returns |
|---|---|
list (default) | Organizations. |
facets | Counts along one dimension. Needs by. |
changes | The change feed — events, not organizations. |
A scope is required — at least one of state, county, city or zip. Enumerating the whole Business Master File (1,957,340 organizations) is a bulk export rather than a page; contact us if that is what you need.
Typical latency is ~90ms state-wide and ~180ms for a county, regardless of how large the county is.
A city is not a metro
These three scopes answer three different questions about “Houston”:
| Scope | Organizations |
|---|---|
city=HOUSTON | 15,977 |
county=Harris | 20,207 |
| The nine-county Houston metro | 31,951 |
The city field is the municipality the organization files under, which is not the same as the area it serves. For a metro, request each county and merge.
Filters
All four endpoints take the same filter vocabulary, so learning one teaches you the rest.
| Parameter | Type | Description |
|---|---|---|
state | string | Two-letter code. Required when county is given. |
region | string | A named region — bay area, midwest, north florida… Counts as a scope on its own. See Regions. |
county | string | One county or a comma-separated list of up to 25. With or without the County suffix. Louisiana parishes and Alaska boroughs match under their census names. Requires state. |
city | string | City as the IRS records it, matched case-insensitively. |
zip | string | 5-digit ZIP. A ZIP+4 is accepted and truncated. |
ntee | string | Comma-separated NTEE codes, matched by prefix. B matches every B*; B21 matches B21. |
ntee_source | string | irs (default), inferred, or inferred_soft. See below. |
subsection | string | Comma-separated 501(c) subsections. 03, 3, 501c3 and 501(c)(3) all mean the same thing. |
view | string | list (default), facets, changes. |
ruling_since | string | YYYY-MM — only organizations ruled at or after this month. |
sort | string | ein (default), revenue_desc, revenue_asc, assets_desc, name_asc, ruling_desc. See Sorting. |
limit | integer | 1–2000, default 250. Bigger is markedly cheaper per organization and costs fewer calls — see Sorting Pagination. |
cursor | string | Opaque. Pass back the previous response's next_cursor. |
Regions
Rather than naming counties, ask for a region. region uses the same vocabulary as Ask, so “Bay Area” means the same set of organizations in both products. It counts as a geographic scope on its own — no state needed.
Multi-state regions — these constrain the state:
deep south, east coast, great lakes, gulf coast, mid-atlantic, midatlantic, midwest, new england, northeast, northwest, pacific northwest, rust belt, rustbelt, southeast, southwest, sun belt, sunbelt, tri-state, tristate, west coast
Sub-state regions — these constrain state and city:
| State | Regions |
|---|---|
| CA | bay area, central coast, central valley, nocal, nor cal, norcal, northern california, so cal, socal, southern california |
| FL | central florida, fl panhandle, florida panhandle, ne florida, north florida, northeast florida, northern florida, northwest florida, nw florida, se florida, south east florida, south florida, south west florida, southeast florida, southern florida, southwest florida, space coast, sw florida, tampa bay, tampa bay area, tampa bay region, treasure coast |
| NY | upstate, upstate new york, upstate ny |
| TX | central texas, dallas fort worth, dfw, east texas, greater houston, gulf coast texas, houston metro, metroplex, north texas, south texas, texas gulf coast, texas triangle, west texas |
Hyphens, underscores and capitalisation are ignored, so Bay Area, bay-area and BAY_AREA are the same request.
How a sub-state region is defined
A sub-state region matches its counties OR its cities — a union, not an intersection.
Counties are the primary definition: socal is the ten Southern California counties (Imperial, Kern, Los Angeles, Orange, Riverside, San Bernardino, San Diego, San Luis Obispo, Santa Barbara, Ventura). The city list is kept as a safety net, because 7.9% of organizations have a ZIP the US Census crosswalk cannot place and so carry no county at all — 1,566 of them in Houston alone. Matching on counties only would silently drop them.
Regions deliberately overlap where geography does: Santa Barbara and Ventura are both socal and central coast; Kern is both socal and central valley; Brevard is both central florida and space coast. A nonprofit in Ventura is honestly in both, and forcing a partition would mean picking a loser.
Passing a state the region does not contain returns 400 REGION_STATE_CONFLICT rather than an empty page — an empty page reads as “no organizations here” when the truth is that the two filters contradict each other.
⚠️ region and county answer different questions
region is a curated list of city names. county is exhaustive by geography. For the same metro they do not return the same set:
| Request | 501(c)(3)s |
|---|---|
region=bay area (61 named cities) | 34,851 |
county=Alameda,Contra Costa,Marin,… (9 counties) | 38,497 |
The county form finds 9% more, because county lines also catch the smaller towns nobody lists by name. Use region to match how Ask talks about a place; use county when you need every organization in it.
County names are not unique
There are 30 Washington Counties and 25 Jefferson Counties in the United States, so county without state returns 400 COUNTY_NEEDS_STATE rather than guessing. A name we do not recognise returns 400 UNKNOWN_COUNTY with the near misses:
{
"error": "UNKNOWN_COUNTY",
"message": "No county named 'Harrs' in TX.",
"hint": "Did you mean: Harris County, Harrison County?"
}
A typo gets an answer that names the problem, never an empty page that reads as “no organizations here”.
Choosing an NTEE source
Roughly a quarter of registered nonprofits were never assigned an NTEE code by the IRS. We infer codes for many of them, and ntee_source decides whether the ntee filter considers those:
| Value | Matches | Accuracy |
|---|---|---|
irs (default) | Only codes the IRS assigned | Authoritative |
inferred | Also codes we assigned to uncoded organizations | ~84–88% major-letter |
inferred_soft | Additionally our below-threshold guesses | ~55–58% major-letter |
What each tier is worth, across all 1,957,340 registered organizations:
ntee_source | Organizations with a code | Coverage |
|---|---|---|
irs | 1,382,893 | 70.7% |
inferred | 1,641,850 | 83.9% |
inferred_soft | 1,722,059 | 88.0% |
Every record reports which tier produced its code in ntee_source, so an inferred code is never mistaken for an IRS one. Reach for inferred when recall matters — building a candidate list — and stay on irs when you are going to act on the classification.
Note: POST /v1/data folds inferred codes into its NTEE matching with no way to opt out. The same ntee=B therefore returns different counts on the two endpoints. This endpoint defaults to IRS-only on purpose.
The record
| Field | Description |
|---|---|
ein | Hyphenated 9-digit EIN. |
name | Legal name as the IRS records it. |
dba_name | Doing-business-as name from Form 990 when it differs from the legal name. Sparse — present for 29,670 of 1,957,340 organizations (1.5%), because most never file one. |
street | Street address from the Business Master File. |
city, state, zip | As filed. zip may be ZIP or ZIP+4. |
county | Derived from the ZIP via the US Census crosswalk. null for 7.9% of organizations whose ZIP is not in that file. |
ntee_code | NTEE code. Read together with ntee_source. |
ntee_source | irs, inferred, inferred_soft, or null when the organization has no code at any tier. |
subsection | 501(c) subsection, zero-padded. 03 is a public charity. |
display_name | name, cased for display (“Hope Center Inc”). Additional — name itself is unchanged. null until the nightly refresh reaches the organization. |
display_street | street, cased for display. |
display_city | city, cased for display. |
Two forms of every string
name, street and city come back exactly as the IRS publishes them, which means ALL CAPS — HOPE CENTER INC, HOUSTON, 3618 STASSEN ST. That is the stable form, and the one to match against your own records.
Alongside each is a display-cased twin:
| Verbatim | Display |
|---|---|
name — HOPE CENTER INC | display_name — Hope Center Inc |
street — 3618 STASSEN ST | display_street — 3618 Stassen St |
city — HOUSTON | display_city — Houston |
These are additional fields, not replacements. Match on the verbatim ones, render the display ones. Nothing you already integrated against changes.
Casing is computed once per Business Master File reload and shared with the rest of the platform, so the same organization can never appear differently in two places. It is a hard problem — YMCA is an acronym, KNIGHTS is a word, CT is Connecticut in a name and Court in an address — so a display field is null rather than wrong when we have not computed one yet.
Sorting
Default is ein — a stable enumeration order, which is what you want when mirroring a place. Add sort to rank instead.
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&subsection=03&sort=revenue_desc&limit=1000"
That is the 1,000 largest charities in Houston — Memorial Hermann Health System, Methodist Hospital Group, Texas Children's Hospital, and on down to about $700K.
| Value | Meaning |
|---|---|
ein (default) | By EIN ascending. Stable; use it to enumerate. |
revenue_desc | Largest first. |
revenue_asc | Smallest first — read the warning below. |
assets_desc | Most assets first. |
name_asc | Alphabetical. |
⚠️ Only 28.6% of organizations have a revenue figure
Across all 1,957,340 registered organizations:
| Reported revenue | Organizations |
|---|---|
| above zero | 560,280 (28.6%) |
| exactly zero | 825,946 (42.2%) |
| nothing reported | 569,235 (29.1%) |
An organization filing the 990-N postcard reports no revenue at all, and the IRS records that as 0 as often as it leaves it blank. So revenue_desc ranks the 28.6% that have a figure and then trails everything else in EIN order.
Those organizations are unmeasured, not small. A top-1,000 or top-5,000 is sound. Paging deep into a revenue sort is paging through unranked rows.
⚠️ revenue_asc is rarely what you want. “Smallest first” returns 825,946 zero-revenue organizations before it reaches anything with a real figure. It exists for symmetry with the Data API.
Keep sort constant across a walk
The cursor encodes the ordering it was issued under. Handing a cursor to a different sort returns 400 INVALID_CURSOR rather than silently skipping or repeating rows. Every response echoes sort so you can assert it never changed.
Pagination
Cursor-based, with no depth ceiling. Read next_cursor from each response and pass it back as cursor; stop when has_more is false.
cursor = None
while True:
params = {"state": "TX", "county": "Harris", "limit": 500}
if cursor:
params["cursor"] = cursor
r = requests.get("https://api.givalgo.ai/v1/organizations",
params=params, headers={"x-api-key": KEY}).json()
yield from r["organizations"]
if not r["has_more"]:
break
cursor = r["next_cursor"]
Pick a page size deliberately
Larger pages are markedly cheaper per organization. Measured over a full California enumeration (176,698 organizations):
limit | Calls | Total time |
|---|---|---|
| 100 | 1,767 | 148s |
| 250 (default) | 707 | 89s |
| 500 | 354 | 61s |
| 1000 | 177 | 51s |
| 2000 (max) | 89 | 35s |
If you are mirroring a whole state, use limit=2000. Fewer calls is also a smaller bill.
Responses are compressed
Send Accept-Encoding: gzip — every HTTP client does by default — and responses come back gzipped. This JSON compresses well because the same keys repeat once per organization: a 1,000-row page is 374KB raw and 78KB compressed, a 79% saving. A 2,000-row page is roughly 156KB on the wire.
If your client does not send the header you still get plain JSON, byte for byte as before.
Treat the cursor as opaque — a value we did not issue returns 400 INVALID_CURSOR. Ordering is stable (by EIN), so a page never repeats or skips a record while you walk.
Counts — view=facets
The dashboard question — “how many nonprofits in each county in Texas?” — answered without transferring 153,779 records.
Takes the same filters as the list endpoint plus a required by: county, city, state, ntee or subsection.
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?view=facets&state=TX&by=county&limit=5"
{
"dimension": "county",
"facets": [
{ "value": "Harris County", "count": 20207 },
{ "value": null, "count": 18709 },
{ "value": "Dallas County", "count": 14117 },
{ "value": "Tarrant County", "count": 9691 },
{ "value": "Travis County", "count": 9143 }
],
"total": 153779,
"truncated": true
}
null is a real bucket
That second row is not a bug. Those 18,709 organizations have a ZIP that is absent from the US Census crosswalk, so we cannot place them in a county. They are included as their own bucket so the counts sum to total — if we hid them you would have no way to tell missing coverage from genuine absence.
total counts everything matching your filters across every bucket, so it stays correct even when limit truncates the list. truncated tells you whether it did.
by=county and by=city require a state (or narrower) scope — counting every county in the country reads the entire dataset.
Newly formed — a filter, not a view
“Who just formed in my service area?” is not a separate view — it is a filter (ruling_since) and an ordering (sort=ruling_desc) over the same rows. Every record carries a ruling_month.
Because it is a plain filter it composes with any sort, which a dedicated endpoint could not do. The largest organizations formed since 2015:
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&ruling_since=2015-01&sort=revenue_desc"
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&ruling_since=2026-01&sort=ruling_desc"
since is a month, not a day, because that is the resolution the IRS publishes.
Two things this date is not
- A determination can be backdated, so a ruling month is not the month the organization appeared in our data.
- The IRS Business Master File reloads monthly, so “new since last month” can lag reality by up to that reload.
It is still the right field: it is the only date the IRS publishes for this, and it never changes — re-running the same query tomorrow gives the same answer for the same period.
The cursor here is a composite YYYYMM:EIN, because ordering is by ruling month and many organizations share one. Treat it as opaque, like any other cursor.
Changes — view=changes
An append-only feed of material changes, so you can keep a mirror current without re-enumerating a whole county.
Call it once without since_id and keep the next_cursor; from then on pass that cursor as since_id and you receive only what has happened since. A geographic scope is optional here — the cursor already bounds the response.
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?view=changes&state=TX&county=Harris&since_id=448817"
| Event type | Meaning |
|---|---|
new_filing | A new Form 990 arrived. |
revenue_swing | Revenue moved materially year over year. |
officer_change | The reported top officer changed. |
revocation_added | The organization appeared on the IRS auto-revocation list. |
pub78_dropped | The organization left IRS Publication 78. |
pub78_restored | It returned to Publication 78 after a drop. |
adverse_media | Adverse media was matched to the organization. |
What this feed does not cover
This reports things that happened to an organization. It does not report edits to the fields this API returns: an organization that renames itself or moves across town emits no event.
Every response repeats this in a covers object, because assuming otherwise is the expensive mistake — you would hold a stale name indefinitely with nothing to tell you. To pick those up, re-enumerate the scope periodically; monthly matches the IRS reload cadence.
The feed is bursty, not daily
IRS batches land every 21–35 days. A daily poll sees nothing for three weeks and then tens of thousands of events at once — that is normal, not an outage. Page the cursor rather than assuming a day fits in one response.
Coverage & caveats
Everything worth knowing before you build on this, in one place.
| Field | Coverage | Why |
|---|---|---|
ein, name, street, city, state, zip | 100% | Straight from the IRS Business Master File. |
county | 92.1% | Derived from ZIP via the US Census crosswalk; ZIPs absent from that file yield null. |
ntee_code (irs) | 70.7% | 1,382,893 of 1,957,340 organizations. The IRS never assigned a code to the rest — ntee_source=inferred recovers most of them (see below). |
dba_name | 1.5% | Only organizations that filed a DBA on a Form 990 have one. |
subsection | 100% | From the Business Master File. |
What the list contains
The universe here is the IRS Business Master File — organizations the IRS currently recognises as tax-exempt. Organizations that lost exemption drop off the BMF and therefore out of these results.
This endpoint tells you an organization exists and is registered. It does not tell you a donation to it is deductible today — that is Charity Verify, which checks Publication 78, the auto-revocation list and OFAC. Enumerate here, verify there before you grant.
Freshness
The Business Master File reloads monthly, so registration facts move on that cadence. The change feed detects new filings daily, but those arrive in IRS batches every 21–35 days.