Charity Verify API
Single EIN verification
Bulk Verify API
Batch EIN verification (up to 20K)
Report API
PDF verification reports
Data API
Base & Pro tiers — financials, comp, peer benchmarks
Organizations API
Every nonprofit in a county, city or state
Research API
Website Research
FaithVerify API
Church verification — no EIN needed
▶ Playground Support

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
BASE URL   https://api.givalgo.ai/v1

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

GET   /v1/verify?ein={ein}&donor_state={state}&ca_entity_id={id}

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

ParameterTypeRequiredDescription
einstringYesEmployer Identification Number. Accepts 13-2875808, 132875808, or 13 1837418.
donor_statestringNoAccepted for backward compatibility; ignored since 2026-09-03. The California checks now run on every request. See California Compliance.
ca_entity_idstringNoCalifornia 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

FieldTypeDescription
statusstringOverall verification result (see Statuses)
deductiblebooleanWhether donations are tax-deductible. null if not found.
messagestringHuman-readable explanation
organizationobjectIRS EO BMF data (name, city, state, subsection, foundation type, assets, income, NTEE code)
pub78objectPublication 78 listing and deductibility type (PC, PF, POF, SO, etc.)
revocationobjectAuto-revocation history and reinstatement status
group_exemptionobjectPresent when ELIGIBLE via group exemption. Shows parent org details. Only applies to 501(c)(3) subordinates.
sanctions_screeningobjectOFAC screening results (see Sanctions Screening below for match object details)
state_complianceobjectAlways 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_freshnessobjectISO dates showing when each source agency last published their data file (eo_bmf, pub78, revocation, ofac_sdn, ofac_cons)
checked_atstringISO 8601 timestamp of this verification

sanctions_screening object

FieldTypeDescription
statusstringCLEAR, POTENTIAL_MATCH, STRONG_MATCH, or NOT_SCREENED
organization_screeningobjectOrg name screened against OFAC entity entries
  .checked_namestringOrganization name that was screened
  .matches_foundintegerNumber of OFAC matches
  .matches[]arrayArray of match objects (see below)
leadership_screeningobjectForm 990 officers/directors screened against OFAC individuals
  .people_data_tax_yearinteger | nullTax year of the Form 990 people data used for screening (e.g., 2023). Null if no people data found.
  .people_checkedintegerNumber of people screened from Form 990
  .people_screened[]arrayList of officers/directors/key employees screened, each with name and title
    .namestringPerson's name as reported on Form 990
    .titlestringPerson's title/role (e.g., "EXECUTIVE DIRECTOR", "BOARD PRESIDENT")
  .matches_foundintegerNumber of OFAC matches
  .matches[]arrayArray of match objects (see below)
checked_againstarray["OFAC SDN", "OFAC Consolidated"]
screened_atstringISO 8601 timestamp of screening

Match object (within matches[])

FieldTypeDescription
sourcestring"OFAC SDN" or "OFAC Consolidated"
matched_namestringName from OFAC list that matched
matched_viastring"primary_name" or "alias (a.k.a. ...)"
primary_namestringPrimary OFAC name (only present for alias matches)
similarity_scorenumberFuzzy match score (0.7–1.0). ≥0.9 = STRONG, ≥0.7 = POTENTIAL
match_strengthstring"STRONG" or "POTENTIAL"
sdn_typestringOFAC entity type ("individual", "N" for entity/org)
programstringOFAC sanctions program (e.g., "SDGT", "IRAN")
entry_idintegerOFAC entry number
remarksstringOFAC remarks — often contains date of birth, nationality, passport numbers, and other identifying information. Key for false positive disambiguation.
addressesarrayKnown addresses for this OFAC entry. Each has address, city_state_zip, and country fields.

Verification Statuses

StatusDeductibleMeaning
ELIGIBLEtrueListed in IRS Pub 78 (directly or via group exemption for 501(c)(3) subordinates). Eligible for tax-deductible donations.
NOT_DEDUCTIBLEfalseIn IRS records but not tax-deductible (e.g., 501(c)(4), 501(c)(6)).
REVOKEDfalseTax-exempt status auto-revoked by the IRS for non-filing. May appear even if the organization has dropped off the Business Master File.
CAUTIONnullFederally 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_FOUNDnullNot 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

StatusMeaning
CLEARNo OFAC matches found for the organization or its leadership.
POTENTIAL_MATCHFuzzy match with 70–89% similarity. Manual review recommended.
STRONG_MATCHFuzzy match with 90%+ similarity. Very likely the same entity.
NOT_SCREENEDOFAC 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 information
  • addresses — 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:

  1. IRS — federal tax-exempt status (always checked)
  2. 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)).
  3. 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):

  1. ca_entity_id — direct SOS/FTB entity-number lookup (92% of list rows carry one)
  2. FEIN — federal EIN match (82% of list rows carry one)
  3. 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

FieldTypeDescription
ag_registryobjectResult against the CA Attorney General's May Not Operate list
  .statusstringNOT_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_statusstringVerbatim AG registration status (e.g., "Revoked", "Delinquent", "Delinquent Platform Charity"). Null for Cease-and-Desist rows
  .entity_statusstringVerbatim AG entity status (e.g., "Listed", "Cease and Desist Order", "Merged Out")
  .noticestringPresent 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_numberstringState charity registration number (e.g., CT0252232)
  .match_methodstringca_entity_id, fein, or name_match
  .matched_namestringPresent for name_match — the CA AG name that matched
  .name_similaritynumberPresent for name_match — similarity score (0.80–1.0)
ftbobjectCA Franchise Tax Board revocation result
  .statusstringCLEAR or REVOKED
  .revocation_datestringDate of FTB revocation (if revoked)
state_statusstringRoll-up: CLEAR (not on the AG list, no FTB revocation), NON_COMPLIANT (on the AG list), or REVOKED (FTB)

Impact on overall status

AG RegistryFTBstate_statusOverall status
NOT_LISTEDCLEARCLEARNo change
REVOKED / SUSPENDED / DELINQUENT / CEASE_AND_DESIST / LISTEDCLEARNON_COMPLIANTELIGIBLE → CAUTION
AnyREVOKEDREVOKEDELIGIBLE → 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

  1. Submit — POST your EINs to /v1/bulk-verify. Get back a job_id immediately (202 Accepted).
  2. Process — EINs are split into batches of 10 and verified in parallel by background workers.
  3. Retrieve — Poll GET /v1/bulk-verify/{job_id} for progress and results, or receive a webhook callback on completion.
Input MethodMax EINsFormat
JSON body (semicolon-separated)250application/json
CSV file upload20,000multipart/form-data
BASE URL   https://api.givalgo.ai/v1

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

POST   /v1/bulk-verify

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

FieldTypeRequiredDescription
einsstringYes*Semicolon-separated EINs. *Not needed for CSV upload.
webhook_urlstringNoURL to POST results to on job completion.
formatstringNoResult format: "json", "csv", or "both" (default).

Query parameters

ParameterTypeDefaultDescription
skip_invalidbooleanfalseIf 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

GET   /v1/bulk-verify/{job_id}

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

StatusMeaning
runningBeing processed by background workers.
doneAll EINs have been verified. Results are available.
failedThe 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

EINsEstimated 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:

StatusMeaning
runningBeing processed by background workers. Poll for progress updates.
doneAll EINs have been verified. Results are available inline (for jobs ≤ 1,000 EINs) and via CSV download URL.
failedThe 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:

StatusDeductibleMeaning
ELIGIBLEtrueListed in IRS Pub 78 (directly or via group exemption). Eligible for tax-deductible donations.
NOT_DEDUCTIBLEfalseIn IRS records but not tax-deductible (e.g., 501(c)(4), 501(c)(6)).
REVOKEDfalseTax-exempt status auto-revoked by the IRS for non-filing. May appear even if the organization has dropped off the Business Master File.
CAUTIONnullIn 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_FOUNDnullNot 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.

LimitValue
Max EINs (JSON body)250
Max EINs (CSV upload)20,000
Max concurrent jobs5 per API key
Result retention30 days
CSV download URL expiry24 hours

Estimated processing times

EINsEstimated 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.

GET   /v1/report?ein={ein}  →  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

GET   /v1/report?ein={ein}

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

ParameterTypeRequiredDescription
einstringYesEmployer Identification Number. Accepts 13-2875808, 132875808, or 13 1837418.

Headers

HeaderRequiredDescription
x-api-keyYesYour API key
AcceptRecommendedSet to application/pdf for decoded binary response

Success Response

PropertyValue
Status Code200 OK
Content-Typeapplication/pdf
Content-Dispositioninline; filename="npo-verify-{ein}.pdf"

Error Responses

Error responses are returned as JSON (not PDF):

StatusError CodeDescription
400MISSING_EINThe ein query parameter was not provided
400INVALID_EINEIN format is invalid (not 9 digits)
403ForbiddenMissing or invalid API key
500INTERNAL_ERRORServer-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:

PageSectionContent
1Cover PageGivalgo branding, organization name, EIN, report generation date
2Outcome & DetailsLarge status badge (color-coded), deductibility verdict, organization details (city, state, subsection, NTEE code, ruling date)
3IRS Status BreakdownEO BMF status & deductibility, Pub 78 listing, group exemption details, revocation history with dates
4OFAC ScreeningOrganization & leadership sanctions screening status, match details (if any), data source year
5Financial SnapshotTotal assets, annual income, annual revenue (from IRS BMF data)
6Back PageDisclaimer, API version, generation timestamp, contact info

Verification Statuses

The report displays the same verification statuses as the Verify endpoint, rendered as color-coded badges:

StatusBadge ColorDeductibleMeaning
ELIGIBLE● GreenYesListed in IRS Pub 78. Eligible for tax-deductible donations.
NOT_DEDUCTIBLE● OrangeNoIn IRS records but not tax-deductible.
REVOKED● RedNoTax-exempt status auto-revoked by the IRS.
CAUTION● YellowUnconfirmedIn IRS master file as 501(c)(3) but not in Pub 78.
NOT_FOUND● GrayUnknownNot 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 use filters.organization.causes (plain English) or ntee_major_codes (codes), or prompt for 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)
  • promptAsk: 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/data Data — search & discovery; lean records, Ask prompts
  • GET /v1/data-pro Pro — the full single-org profile (~410 fields: full financial detail, compensation cube, governance, schedules, registration)
BASE URL   https://api.givalgo.ai/v1

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
InteractionSearch: EIN / name / keywords, structured filters, Ask promptsLookup: one EIN per request
ResponsePage of lean org records (~40 fields each)One deep profile (~410 fields)
Best forDiscovery, screening, autocomplete, pipeline building, monitoringDue 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

POST   /v1/data   Data

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.

GET   /v1/data-pro?ein={ein}&tax_year={year}   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)

ParameterTypeRequiredDescription
einstringYesEmployer Identification Number. Accepts 53-0196605, 530196605, or 53 0196605.
tax_yearintegerNo/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).

FieldTypeDescription
search_termsstringEIN ("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.causesstring | 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.
filtersobjectStructured search — see Filters.
promptstringAsk mode: one plain-English sentence, 2–400 chars. Returns the top 20 + an interpretation. Counts against the daily Ask quota.
sortstring | objectrelevance (default), revenue_desc, revenue_asc, assets_desc, grants_desc, name_asc — or a {"sort_by": "total_revenue", "ascending": false} object.
fromintegerResult offset (default 0). Must be a multiple of size; from + size ≤ 2000. Ignored in Ask mode.
sizeintegerResults 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

FieldTypeDescription
statestring | list2-letter state code(s), e.g. ["OH", "MI"].
citystring | listCity name(s). Metro aliases expand — "New York" covers all five boroughs.
zipstring5-digit ZIP.

organization

FieldTypeDescription
ntee_major_codeslistNTEE major letters, e.g. ["B", "E"].
ntee_codeslistFull NTEE codes, e.g. ["B21"]. Alias: ntee_minor_codes.
ntee_modestringany (default) or all.
subsection_codeslistIRS subsection. Accepts "03", 3, "501c3", or "501(c)(3) Public Charity".
org_typestringfoundation (grantmakers) or nonprofit (grantseekers). Roles are non-exclusive.
exclude_revoked_organizationsboolDrop 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.

FieldDescription
total_revenue, total_assets, net_assetsLatest-filing dollar ranges.
grants_madeGrants the org paid out (latest filing). Alias: total_giving.
revenue_5y, grants_5yPeak revenue / grants paid across the last 5 filings — catches orgs whose latest year understates them.
program_expense_ratio0–1. Program share of total expenses.
months_of_cashLiquid reserves in months of operating expenses.
revenue_concentration0–1 concentration of revenue sources (higher = more concentrated).
revenue_growth_streak_min, revenue_decline_streak_min, grants_growth_streak_min, grants_decline_streak_minInteger 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):

GroupFields
Identityein, 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)
Geographycity, state, zip, address
Contactwebsite, contact.email, contact.phone, contact.contact_url (website-scraped)
Classificationntee_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
Standingirs_status, pub78_verified, delisted, delisted_reason, ruling_year
Financialslatest_tax_year, total_revenue, total_expenses, total_assets, net_assets, grants_paid, grants_received_total, total_employees
Screensprogram_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_kindPrompt shapeExtras
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.

FieldTypeDescription
einstringHyphenated EIN (e.g. "53-0196605").
tax_yearintegerFiscal year of the filing returned.
return_typestring"990", "990EZ", "990PF", or "990N" (e-Postcard).
data_availabilityobjectPresent 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_datestring (ISO date)Date IRS received the filing.
organizationobjectIdentity, address, NTEE code, mission, formation year, total employees/volunteers.
financialsobjectSummary, revenue breakdown, expenses, balance sheet, and (Pro only) detail sub-object.
trendsobjectUp to 6 years of annual data + computed CAGRs + trend labels.
compensationobjectPeople array, by-role highlights, per-employee average, peer benchmark cubes.
funding_sourcesobjectGrants received: total_unique_grantmakers, grantmakers[], annual_totals[].
grants_madeobject[]Outbound grants (Schedule I), top 50 for the year. Data Pro only — always present.
programsobject[]Program service accomplishments.
governanceobjectBoard structure, policies, meeting minutes flags.
fundraisingobjectmetrics, events[], contractors[], activity_methods, licensed_states.
financial_profileobjectLiquidity, revenue concentration (HHI), operating sustainability, expense composition New. All with peer benchmarks.
related_organizationsobject[]Schedule R-style related entities.
foreign_activitiesobject[]Schedule F Part I region-level summary.
lobbying_and_politicalobjectSchedule C summary.
contributor_summaryobjectAlways 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_fundsobject | nullDAF flags + balances (if Schedule D.I present).
conservation_easements NewobjectSchedule D Part II. Always-on; present only when held.
art_collections NewobjectSchedule D Part III. Always-on; present only when held.
private_foundation NewobjectForm 990-PF metrics. Always-on; 990-PF filers only.
hospitals NewobjectSchedule H. 501(c)(3) hospital filers only; always present for them on Data Pro.
disclosures ProobjectSchedules K/L/M/N/F/A/O. Data Pro only — always present.
registration ProobjectIRS BMF + Pub 78 + California AG/FTB status, joined at request time. See registration.
data_freshnessobjectfiling_source, most_recent_tax_year, filing_date, last_filing_date, years_available[], includes_applied[].

organization

FieldTypeDescription
namestringLegal name on the filing.
legal_name Newstring | nullIRS-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_asstring | nullDBA, if present.
address_type NewstringAlways "PRIMARY" on the main organization address. Distinguishes from operations.books_in_care_of.address_type which is "BOOKS_IN_CARE_OF".
address, city, state, zipstringMailing address.
address_line_2 Newstring | nullSecond address line from the Form 990 filing header. Often null; filers usually pack everything into address.
phonestring | nullPhone from the filing header.
websitestring | nullServed 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_yearintegerYear of formation.
missionstringMission statement text.
principal_officerstring | nullName of principal officer from Form 990 header.
countrystring | absentISO country code, present only for foreign-domiciled organizations. Domestic filers omit the key.
provincestring | absentSub-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_codestringFull NTEE code (e.g. "P210").
ntee_descriptionstringHuman-readable NTEE major group (e.g. "Human Services").
ntee_codes.primary_code Newstring | nullSingle-letter NTEE major code (A–Z). E.g. "E" for Health Care.
ntee_codes.primary_description Newstring | nullDescription of major code. E.g. "Health Care".
ntee_codes.sub_code Newstring | null3- or 4-char NTEE sub-classification from the NCCS taxonomy lookup. E.g. "E21".
ntee_codes.sub_description Newstring | nullDescription of the sub-code. E.g. "Community Health Systems".
is_national_hq NewbooleanTrue when BMF affiliation code = 1 or 6 (central parent of a group ruling). False otherwise.
is_non_bmf_org NewbooleanTrue when no row exists in eo_bmf for this EIN. Very rare — would suggest filing without IRS BMF registration.
ruling_yearinteger | nullYear IRS granted tax-exempt status.
total_employees, total_volunteersintegerAs reported on the filing.

financials

FieldTypeDescription
accounting_method Newstring | null"Accrual", "Cash", or null. From Form 990 Part XII Line 1 (MethodOfAccountingAccrualInd). Applies to all return types.
summaryobjecttotal_revenue, total_expenses, revenue_less_expenses, total_assets_eoy, total_liabilities_eoy, net_assets_eoy.
revenueobjectcontributions_and_grants, program_service_revenue, investment_income, other_revenue, total.
revenue.gross_sales Newinteger | null990-EZ Part I Line 7a — gross sales of inventory. Null on full 990 / 990-PF.
revenue.cost_goods Newinteger | null990-EZ Part I Line 7b — cost of goods sold. Null on full 990 / 990-PF.
revenue.gross_profit Newinteger | null990-EZ Part I Line 7c (gross_sales − cost_goods). Null on full 990 / 990-PF.
revenue.revenue_sales Newinteger | nullNet gain/(loss) on sales of assets (Part VIII Line 7d). Compatibility alias for net_gain_loss_assets.
revenue.part_viii_line_detail Proobject | nullForm 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 Newinteger | nullPart 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 Newinteger | null990-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.
expensesobjectprogram_services, management_and_general, fundraising, total, and allocation (program_pct, fundraising_pct).
expenses.professional_fees Newinteger | nullPart IX Lines 11a–11g sum. Promoted from financial_profile.expense_composition for response-shape clarity.
expenses.other_expenses Newinteger | nullPart IX Line 24e — "Other Expenses" rollup. 990-EZ Part I Line 16 maps directly.
expenses.total_expense_disbursements Newinteger | null990-PF Part I col D total charitable disbursements (Line 26). PF-only; null on 990 / 990-EZ.
expenses.expense_operating_admin Newinteger | nullCompatibility 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 NewbooleanTrue on PF returns where the value is derived (not directly reported). False on 990 / 990-EZ.
expenses.joint_costs_indicator Newboolean | nullPart 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_sheetobjectCash, investments, PP&E, assets, liabilities, net assets (with unrestricted split).
balance_sheet.investments_us_government Newinteger | null990-PF Part II Line 10a FMV — US Government obligations. PF-only.
balance_sheet.investments_stock Newinteger | null990-PF Part II Line 10b FMV — corporate stock. PF-only.
balance_sheet.investments_bonds Newinteger | null990-PF Part II Line 10c FMV — corporate bonds. PF-only.
balance_sheet.investments_other Newinteger | null990-PF Part II Line 13 FMV — other investments. PF-only.
balance_sheet.capital_stock_trust_principal_boy / _eoy Newinteger | nullPart 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 Newinteger | nullPart 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 Newinteger | nullPart X Line 32 (BOY/EOY) — retained earnings, endowment, accumulated income, or other funds. Stock-corp framework only.
prior_year Newobject | nullPrior-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 Newobject | null990-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 Proobject | nullFull Part VIII / Part IX / Schedule D line-by-line breakdown. null on base tier.
detail.balance_sheet.assets.notes_loans_receivable Newobject | nullPart 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).

FieldTypeDescription
form990_urlstring | nullProPublica 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.

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

FieldTypeDescription
tax_yearintegerFiscal year this compensation data belongs to.
people_countintegerNumber of people in the people[] array.
employees_over_100k Newinteger | nullSchedule 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_personstringName of the highest-paid person in the list.
highest_compensationintegerDollar amount.
comp_to_expense_pctnumberSum of Part VII compensation ÷ total expenses, as a percent.
top_officer_comp_to_revenue_pct NewnumberTop officer total comp ÷ total revenue, as a percent. Scale-normalized so small and large orgs can be compared fairly.
avg_compensation_per_employee NewintegerWhole-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.

FieldTypeDescription
by_role.ceoobject | null{title, total_compensation}. Matches titles like Executive Director, Chief Executive, President, CEO.
by_role.cfoobject | nullMatches titles like Chief Financial Officer, CFO, Treasurer, Director of Finance, VP Finance.
by_role.development_directorobject | nullMatches 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 amounts
  • top_officer_comp_to_revenue_pct — scale-normalized
  • ceo_comp, cfo_comp, dev_director_comp — role-specific benchmarks
  • avg_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.

FieldTypeDescription
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.

FieldTypeDescription
total_unique_grantmakersintegerCount 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

FieldTypeDescription
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

FieldTypeDescription
board_sizeinteger | nullTotal voting members (Part VI Line 1a).
independent_membersinteger | nullIndependent voting members (Part VI Line 1b).
independence_pctnumber | nullIndependent share of the board.
policies.conflict_of_interestboolean | nullPart VI Line 12.
policies.whistleblower, policies.document_retentionboolean | nullPart VI Lines 13–14.
policies.board_reviews_990boolean | nullBoard reviewed the Form 990 before filing (Part VI Line 11a).
policies.fs_auditedboolean | nullFinancial 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 Proobject | nullRosters (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

FieldTypeDescription
metricsobjectcost_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 Newobject | nullSchedule 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 Newstring | nullSchedule 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).

FieldTypeDescription
namestringContractor / firm name.
activity_descriptionstringDescription of fundraising activity.
is_fundraiserbooleanTrue when this entity is identified as a professional fundraiser (vs. another activity type).
has_custody_of_fundsbooleanTrue when the contractor has custody / control of contributions.
gross_receiptsintegerGross receipts from the activity.
amount_paid_to_orgintegerAmount retained by the org (net of contractor share).
amount_paid_to_fundraiserintegerAmount paid to or retained by the contractor.

financial_profile

Our headline analytics section — every ratio here comes with a peer benchmark cube.

liquidity

FieldTypeDescription
months_of_cashnumberCash & savings ÷ monthly expenses.
months_of_liquid_assetsnumberCash + savings + publicly traded investments, over monthly expenses.
operating_reserve_months NewnumberUnrestricted net assets ÷ monthly expenses. The standard donor-facing operating-reserve figure.
peer_benchmarksobjectLegacy: 7-cut cube for months_of_cash.
peer_benchmarks_by_metric NewobjectCubes for months_of_cash and operating_reserve_months.

revenue_concentration

FieldTypeDescription
herfindahl_indexnumberHHI computed across 5 revenue buckets (contributions, program service, investment, fundraising events, other). Lower = more diversified.
diversification_levelstringDIVERSIFIED (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 NewstringPlain-English explanation of the bucket.
index_range Newobject{min: 0.20, max: 1.00, buckets: 5, note: ...} — so consumers know how to interpret the scalar.

operating_sustainability

FieldTypeDescription
operating_margin_pctnumber(Revenue − expenses) ÷ |revenue|, percent.
operating_reserve_monthsnumberSame as liquidity.
peer_benchmarks_by_metric NewobjectCubes 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)

FieldTypeDescription
program_expense_rationumberProgram services ÷ total expenses × 100. The traditional "how much goes to mission" ratio.
admin_expense_rationumberManagement & general ÷ total expenses × 100.
fundraising_expense_rationumberFundraising ÷ total expenses × 100.
cost_to_raise_a_dollarnumberFundraising expense ÷ contributions. Lower is more efficient.
comp_to_expense_pctnumberTop-N compensation as percent of total expenses.

Natural view (FTA — Part IX line items) New

FieldTypeDescription
salaries_benefits_rationumberPart IX lines 5–10 ÷ total expenses × 100. Labor intensity — service-delivery orgs run 60–80%; grant-makers much lower.
professional_fees_rationumberPart IX lines 11a–11g ÷ total expenses × 100. High values can flag heavy consultant / outside-fundraiser reliance.
occupancy_rationumberPart IX line 16 ÷ total expenses × 100. Real-estate-heavy orgs (museums, hospitals) run higher.
travel_rationumberPart IX line 17 ÷ total expenses × 100.
grants_paid_rationumberPart 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_paidintegerRaw Part IX dollar amounts — for callers that want their own cut (e.g. $/employee, $/beneficiary).
peer_benchmarks_by_metricobjectPercentile 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 Newinteger | nullDerived: total_expenses − depreciation. Falls back to total_expenses unchanged when depreciation is unreported.
total_expenses_percent_change_over_prior_year Newnumber | nullYoY % 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).

FieldTypeDescription
unrestricted_surplus_before_depreciationinteger | nulltotal_revenue − (total_expenses − depreciation). Falls back to total_revenue − total_expenses when depreciation is unreported.
surplus_as_percent_of_expenses_before_depreciationnumber | nullsurplus_before_dep / expenses_before_dep × 100.
total_revenue_percent_change_over_prior_yearnumber | nullYoY % change in total_revenue. NULL when no prior-year row exists.
_inputsobjectDebug/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).

FieldTypeDescription
one_month_of_savingsnumber | nulltotal_expenses / 12 — the dollar value of one month of operating runway.
total_full_costs_estimatedinteger | nullPartial composite: total_expenses + depreciation. Under-estimates the full audited figure (which also includes audited-FS components).
total_full_costs_is_partialboolean | nullAlways true until the audited-FS components surface. Flag so consumers don't mistake the partial sum for the full figure.
total_full_costs_missing_componentsstring[] | nullNames 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.

FieldTypeDescription
gross_land_buildings_and_equipment_lbeinteger | nullSum of Schedule D Part VI cost_or_basis_invest + cost_or_basis_other. NULL when filing has no Schedule D Part VI.
less_accumulated_depreciationinteger | nullSum of Schedule D Part VI accumulated_depreciation.
accumulated_depreciation_as_percent_of_lbenumber | nullaccumulated_dep / gross_lbe × 100.
liabilities_as_percent_of_net_assetsnumber | nulltotal_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).

FieldTypeDescription
liquiditynumber | nullcash_and_savings_eoy / current_liabilities_proxy. NULL when the proxy is zero or none of the three proxy components are populated.
_inputsobjectDebug/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).

FieldTypeDescription
namestringContractor / firm name.
servicesstringDescription of services provided.
compensationintegerAnnual compensation paid to the contractor.
address Newstring | nullContractor 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.

FieldTypeDescription
address_type NewstringAlways "BOOKS_IN_CARE_OF" when this block is present. Distinguishes from organization.address_type = "PRIMARY".
namestringRecords-custodian name.
citystringCity of records-custodian address.
statestring2-letter state code.
zipstringZIP code.
phonestringPhone 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.

FieldTypeDescription
board_chair_namestring | nullFirst 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_namestring | nullFirst person whose title matches /co-chair|co chair|co-chairperson/i.
co_leader_namestring | nullFirst 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.

FieldTypeDescription
bond_ref_numstringIssuer-supplied bond reference letter (A/B/C/D).
issuer_namestringName of the issuing authority.
issuer_einstringEIN of the issuer.
cusip_numberstringCUSIP identifier for the bond issue.
date_issuedstring (date)Date the bond was issued.
issue_price_amtintegerIssue price.
issue_descriptionstringDescription of the bond purpose.
term_of_bond_yrnumberTerm in years.
maturity_datestring (date)Maturity date.
variable_rate_indbooleanTrue if variable-rate bond.
deferred_issuance_costs_amtintegerDeferred issuance costs.
on_behalf_of_issuer_indbooleanTrue when the issuer was issued on behalf of the org.
pooled_financing_indbooleanTrue for pooled-financing arrangements.
private_loan_indbooleanTrue if proceeds funded a private loan.
unspent_proceeds_amtintegerUnspent bond proceeds remaining.
acquired_bonds_amtintegerAcquired bonds amount.
nonqualified_bonds_outstandingbooleanTrue when non-qualified bonds remain outstanding.
arbitrage_yieldnumberArbitrage yield percentage.
escrow_established_indbooleanTrue when defeasance escrow was established.
defeasance_indbooleanTrue when bonds have been defeased.
qualified_hedge_datestring (date)Date of qualified hedge identification.
no_private_biz_use_indbooleanTrue when no private business use is reported.
voluntary_closing_agreement_indbooleanTrue 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).

FieldTypeDescription
descriptionstringDescription of the asset / activity disposed.
datestring (date)Date of disposition.
amountintegerAmount of the disposition.
acquirerstringName of acquirer / recipient.

schedule_m.contributions[] New

Non-cash contribution types received during the tax year (Schedule M).

FieldTypeDescription
typestringContribution-type label (e.g. Publicly-Traded Securities, Art, Vehicles).
countintegerNumber of contributions of this type.
revenue_amtintegerReported revenue amount.
valuation_methodstringMethod 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).

FieldTypeDescription
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.
metadataobject | nullPart 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.

FieldTypeDescription
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.

FieldTypeDescription
public_charity_statusstringPublic-charity classification (e.g. 170(b)(1)(A)(vi)).
public_support_pctnumberPublic-support percentage.
section_509a2object509(a)(2) figures: public_support_amt, total_support_amt, investment_income_amt, public_support_cy_pct, thirty_three_pct_test_passed.
supporting_orgobject509(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[] | nullPart 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.

FieldTypeDescription
datestring (date)Date of liquidation or cessation.
typestringLIQUIDATION 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.

FieldTypeDescription
held_at_eoy_countintegerEasements held at end of year.
total_acreagenumberTotal acreage under easement.
modifications_countintegerEasements modified, transferred, released, extinguished, or terminated during the year.
violations_countintegerEasement violations identified during the year.

art_collections

Schedule D Part III — art / historical-treasure collection policies (booleans).

FieldTypeDescription
uses_revenue_for_public_purposebooleanCollection revenue is used for a public service purpose.
loans_to_publicbooleanCollection items are loaned for public exhibition.
accession_not_for_financial_gainbooleanItems 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”.

FieldTypeDescription
total_assets_fmvintegerTotal assets at fair market value.
qualifying_distributionsintegerQualifying distributions (Part XII).
distributable_amountintegerDistributable amount (Part XI).
min_investment_returnintegerMinimum investment return (Part X).
excise_taxintegerExcise tax on net investment income.
net_investment_incomeobjectPart I col B walker: total, interest, dividends, capital_gain, other_income, total_expenses.
adjusted_net_income, charitable_disbursements, …objectAdditional 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).

FieldTypeDescription
has_financial_assistance_policybooleanOrg has a written financial-assistance (charity-care) policy.
total_community_benefit_at_costintegerTotal community benefit at cost (Part I).
direct_offsetting_revenueintegerDirect offsetting revenue.
net_community_benefitintegerNet community benefit expense.
community_benefit_pct_of_expensesnumberNet community benefit as a percent of total expenses.
bad_debt_expenseintegerBad debt expense (Part III Section A).
medicare_revenueintegerMedicare allowable revenue (Part III Section B).
medicare_allowable_costintegerMedicare allowable cost.
medicare_surplus_or_shortfallintegerMedicare surplus (positive) or shortfall (negative).
has_written_collection_policybooleanOrg has a written debt-collection policy.
hospital_facilities_countintegerNumber of hospital facilities reported.
other_facilities_countintegerNumber of non-hospital health-care facilities.

facilities[] · facility_policies[] · other_facilities[] · joint_ventures[] · community_benefit_by_category[]

FieldTypeDescription
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

SectionSourceDescription
related_organizationsSchedule RFull 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 NewSchedule R Part VInter-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_activitiesSchedule FRegions, activity types, revenue & expenditures abroad, foreign grants.
lobbying_and_politicalSchedule CLobbying expenditures (direct & grassroots), 501(h) election, political campaign expenditures.
contributor_summaryAlways 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_fundsSchedule D.IFunds 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:

FieldTypeDescription
subsection_labelstring | nullDecoded subsection, e.g. "501(c)(3) — Religious, educational, charitable…".
classification_labelsstring[] | nullDecoded classification. An array, because the classification code is a bitmask that decodes to zero or more labels — e.g. ["Charitable"].
foundation_labelstring | nullDecoded foundation_code, e.g. "Publicly supported charity 170(b)(1)(A)(vi)…".
deductibility_labelstring | nullDecoded deductibility_code, e.g. "Contributions are deductible".
affiliation_labelstring | nullDecoded affiliation, e.g. "Central — central organization of a grouping (no group exemption)".
status_labelstring | nullDecoded status, e.g. "Unconditional exemption".
filing_requirement_labelstring | nullDecoded filing_requirement, e.g. "Form 990 required (all filers)".
pf_filing_requirement_labelstring | nullDecoded 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

FieldTypeDescription
listedbooleanWhether the org appears in IRS Publication 78.
deductibilitystring | nullRaw Pub 78 deductibility code, e.g. "PC".
deductibility_labelsstring[] | nullDecoded, 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.

FieldTypeDescription
state_statusstringRoll-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_registryobject | nullResult 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).
ftbobject | nullFTB 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).

FieldTypeDescription
tax_period_beginstring | nullStart of the tax period (YYYY-MM-DD).
tax_period_endstring | nullEnd of the tax period (YYYY-MM-DD).
schema_versionstring | nullIRS e-file schema version of the filing.
preparer_firmstring | nullPaid-preparer firm name.
preparer_firm_address Newstring | nullPaid-preparer firm street address (return header).
preparer_firm_city Newstring | nullPreparer firm city.
preparer_firm_state Newstring | nullPreparer firm 2-letter state code.
preparer_firm_zip Newstring | nullPreparer firm ZIP (first 5 digits).
preparer_firm_ein Newstring | nullPreparer firm EIN.
preparer_phone Newstring | nullPreparer phone number. Note: the IRS schema attaches the phone to the preparer person, not the firm — there is no firm-phone element.
tax_status_typestring | null501(c) / 4947(a) tax-status type from the return header.

data_freshness

FieldTypeDescription
filing_sourcestringAlways "IRS TEOS e-file".
most_recent_tax_yearintegerLatest filing year ingested for this EIN.
filing_datestringFiling date of the response year.
last_filing_datestringMost recent filing date for this EIN overall.
years_availableinteger[]All tax years on file.
includes_appliedstring[]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

FieldTypeDescription
peer_groupstringHuman label, e.g. "P – Human Services ∩ $100M+ ∩ DC".
peer_countintegerNumber of orgs in the cohort for that tax year.
p10, p25, median, p75, p90, meannumberDistribution statistics for the metric in that cohort.
notestring (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.

MetricSurfaced at
total_revenue(internal reference)
top_officer_compcompensation.peer_benchmarks, compensation.peer_benchmarks_by_metric
top_officer_comp_to_revenue_pct Newcompensation.peer_benchmarks_by_metric
ceo_comp Newcompensation.peer_benchmarks_by_metric
cfo_comp Newcompensation.peer_benchmarks_by_metric
dev_director_comp Newcompensation.peer_benchmarks_by_metric
avg_compensation_per_employee Newcompensation.peer_benchmarks_by_metric
program_expense_ratiofinancial_profile.expense_composition.peer_benchmarks_by_metric
admin_expense_ratio Newfinancial_profile.expense_composition.peer_benchmarks_by_metric
fundraising_expense_ratio Newfinancial_profile.expense_composition.peer_benchmarks_by_metric
cost_to_raise_a_dollar Newfinancial_profile.expense_composition.peer_benchmarks_by_metric
comp_to_expense_pctfinancial_profile.expense_composition.peer_benchmarks_by_metric
salaries_benefits_ratio Newfinancial_profile.expense_composition.peer_benchmarks_by_metric
professional_fees_ratio Newfinancial_profile.expense_composition.peer_benchmarks_by_metric
months_of_cashfinancial_profile.liquidity.peer_benchmarks, financial_profile.liquidity.peer_benchmarks_by_metric
operating_reserve_months Newfinancial_profile.liquidity + operating_sustainability
operating_margin_pctfinancial_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

FieldTypeDescription
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[] Newobject[]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 NewobjectPart 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

StatusErrorMeaning
400MISSING_EINThe ein query param is required.
400INVALID_EINEIN is not 9 digits / not parseable.
400INVALID_TAX_YEARtax_year must be a 4-digit integer.
403IP_NOT_ALLOWEDThe calling IP is not on the allowlist for this API key.
403ENDPOINT_NOT_LICENSEDYour 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.
403ForbiddenMissing or invalid x-api-key header.
404NOT_FOUNDNo 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.
500INTERNAL_ERRORUnexpected server error. request_id included for support tickets.

Search-specific errors (POST /v1/data)

StatusErrorMeaning
400EMPTY_REQUESTBody needs search_terms, filters, or prompt.
400PROMPT_CONFLICTprompt cannot be combined with search_terms/filters.
400UNKNOWN_FIELDUnrecognized field; the message names the JSON path.
400UNSUPPORTED_FILTERA recognized field from other nonprofit data APIs that we don't carry — the hint names the nearest equivalent.
400INVALID_FILTERS / INVALID_RANGE / INVALID_SORT / INVALID_SIZE / INVALID_PAGINATION / INVALID_PROMPT / INVALID_SEARCH_TERMSField-level validation; the message says which field and why.
400MULTI_EIN_NOT_SUPPORTEDOne EIN per request (multi-EIN monitoring is planned).
429ASK_QUOTA_EXCEEDEDDaily Ask-prompt quota reached. Body carries quota {plan, limit, used, resets_at}; Retry-After header set. Structured search unaffected.
502UPSTREAM_ERRORSearch engine temporarily unavailable — retry.
504UPSTREAM_TIMEOUTSearch 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/verify starts 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

FieldRequiredNotes
nameyesAs you know it. Abbreviations resolve — “First UMC” finds “First United Methodist Church”.
cityyes
stateyesTwo-letter code (OH) or the full name (Ohio), either case. Send DC or District of Columbia for the District — Washington alone is the state.
streetnoNeeded only when a call returns needs_street. Supplying it up front never hurts.
skip_website_evidencenoStay synchronous — never return 202, take the record answer as final.
force_refreshnoRe-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.

StatusMeaningWhat to do
resolvedWe know which church you mean.Read the verdict.
needs_streetWe 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_foundNo 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.

LayerSourceResponse
1 · IRSEO BMF → Publication 78, directly or via the parent’s group exemption. Includes revocation and OFAC screening.Immediate
2 · Denominational175,881 congregations across 28 denominations. A listing means the denomination’s group ruling covers it.Immediate
3 · WebsiteThe 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.

CriterionWhat comes back
ministersOrganized 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.
doctrineThe denomination claimed, the topics the statement covers, and the church’s own wording of what it believes.
servicesEach regular gathering as day, time, and the schedule line exactly as printed.
calendarNamed events with dates, recurring programmes, and what the church’s month looks like.
contactStreet 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.

StatusMeaning
FOUND_IRSAn IRS record — Publication 78, a group exemption whose parent is listed, or the church classification itself.
REVOKED_ON_IRSThe 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_DIRECTORYThe congregation appears in a denomination’s own current directory, and nowhere in IRS data.
FOUND_WEBSITE_EVIDENCENo record in either, so we read the church’s own website against the five IRS characteristics.
UNDETERMINEDWe 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.

Verbatim means verbatim. Text returned in 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.

5 minutes is a timeout, not a cadence. Polling on a multi-minute timer adds that delay to your flow for no benefit — by then the result has usually been ready for minutes. If the submit response says "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

FieldTypeRequiredDescription
urlstringOne 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.
einstringOne of
these two
EIN with or without hyphen. Send it even when you have the URL — see below.
fieldsarrayNoWhich fields to extract. Defaults to ["mission"], currently the only available field.
force_refreshbooleanNoBypass 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.

ErrorStatusMeaning
MISSING_URL400url was absent or empty.
INVALID_URL400No usable website host could be derived (e.g. "N/A", an email address, an aggregator profile page).
FIELD_NOT_AVAILABLE403A recognized field that is not yet available. The response names the tier that will carry it.
UNKNOWN_FIELD400Not a field this API extracts. The response lists what is available.
RESEARCH_QUOTA_EXCEEDED429Daily 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 sendWhat we crawl
url onlyYour 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 onlyWe resolve the organization's website ourselves, from IRS filings and our own verified records.
bothYour URL — unless it is a parked or hijacked domain, in which case we resolve the real website from the EIN instead.
Send the EIN even when you have the URL. Nonprofit domains lapse, and a lapsed one is frequently re-registered as a parked page or a spam site. Reading one of those would produce a confident, verbatim, and completely wrong mission. With an EIN we can find the organization's real website instead; without one we can only tell you the URL is no good.

The response always reports which we used:

FieldMeaning
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.
reasonWhy 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.

reasonMeaningRetry?
website_deadThe 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_fileWe hold no website for this organization, and a web search found none.No
caller_url_parked / caller_url_hijackedThe 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_deniedWe 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.

A site we could not read is a normal 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.

FieldWhat it contains
missionText 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_proxyA 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

ValueMeaning
labeledFound under an explicit mission heading — "Our Mission", "Mission Statement".
unlabeledA 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.

StatusMeaningRetry?
okCrawled cleanly.
partialSome pages were reachable, some were not.
js_requiredThe site renders entirely in JavaScript and we could not read enough of it.Unlikely to help
fetch_failedDNS, TLS, 404, or timeout.Yes, later
rate_limitedThe site throttled us.Yes, later
robots_deniedThe site's robots.txt disallows crawling. We respect it.No
no_websiteThe 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:

FieldMeaning
cachedtrue when the result came from an earlier crawl; false when this job produced it.
crawled_atWhen 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?”
OrderingStable, by EINBy relevance
Depth limitNone — page until has_more is false2,000 results
RecordLean: identity + locationRich: financials, mission, contact, NTEE detail
County filterYesNo
Free-text searchNoYes, 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

GET   /v1/organizations   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.

viewReturns
list (default)Organizations.
facetsCounts along one dimension. Needs by.
changesThe 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”:

ScopeOrganizations
city=HOUSTON15,977
county=Harris20,207
The nine-county Houston metro31,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.

ParameterTypeDescription
statestringTwo-letter code. Required when county is given.
regionstringA named region — bay area, midwest, north florida… Counts as a scope on its own. See Regions.
countystringOne 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.
citystringCity as the IRS records it, matched case-insensitively.
zipstring5-digit ZIP. A ZIP+4 is accepted and truncated.
nteestringComma-separated NTEE codes, matched by prefix. B matches every B*; B21 matches B21.
ntee_sourcestringirs (default), inferred, or inferred_soft. See below.
subsectionstringComma-separated 501(c) subsections. 03, 3, 501c3 and 501(c)(3) all mean the same thing.
viewstringlist (default), facets, changes.
ruling_sincestringYYYY-MM — only organizations ruled at or after this month.
sortstringein (default), revenue_desc, revenue_asc, assets_desc, name_asc, ruling_desc. See Sorting.
limitinteger1–2000, default 250. Bigger is markedly cheaper per organization and costs fewer calls — see Sorting Pagination.
cursorstringOpaque. 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:

StateRegions
CAbay area, central coast, central valley, nocal, nor cal, norcal, northern california, so cal, socal, southern california
FLcentral 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
NYupstate, upstate new york, upstate ny
TXcentral 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:

Request501(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:

ValueMatchesAccuracy
irs (default)Only codes the IRS assignedAuthoritative
inferredAlso codes we assigned to uncoded organizations~84–88% major-letter
inferred_softAdditionally our below-threshold guesses~55–58% major-letter

What each tier is worth, across all 1,957,340 registered organizations:

ntee_sourceOrganizations with a codeCoverage
irs1,382,89370.7%
inferred1,641,85083.9%
inferred_soft1,722,05988.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

FieldDescription
einHyphenated 9-digit EIN.
nameLegal name as the IRS records it.
dba_nameDoing-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.
streetStreet address from the Business Master File.
city, state, zipAs filed. zip may be ZIP or ZIP+4.
countyDerived from the ZIP via the US Census crosswalk. null for 7.9% of organizations whose ZIP is not in that file.
ntee_codeNTEE code. Read together with ntee_source.
ntee_sourceirs, inferred, inferred_soft, or null when the organization has no code at any tier.
subsection501(c) subsection, zero-padded. 03 is a public charity.
display_namename, cased for display (“Hope Center Inc”). Additionalname itself is unchanged. null until the nightly refresh reaches the organization.
display_streetstreet, cased for display.
display_citycity, cased for display.

Two forms of every string

name, street and city come back exactly as the IRS publishes them, which means ALL CAPSHOPE 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:

VerbatimDisplay
name — HOPE CENTER INCdisplay_name — Hope Center Inc
street — 3618 STASSEN STdisplay_street — 3618 Stassen St
city — HOUSTONdisplay_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.

ValueMeaning
ein (default)By EIN ascending. Stable; use it to enumerate.
revenue_descLargest first.
revenue_ascSmallest first — read the warning below.
assets_descMost assets first.
name_ascAlphabetical.

⚠️ Only 28.6% of organizations have a revenue figure

Across all 1,957,340 registered organizations:

Reported revenueOrganizations
above zero560,280  (28.6%)
exactly zero825,946  (42.2%)
nothing reported569,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):

limitCallsTotal time
1001,767148s
250 (default)70789s
50035461s
100017751s
2000 (max)8935s

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

GET   /v1/organizations?view=facets&by={dimension}   Organizations

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

GET   /v1/organizations?ruling_since={YYYY-MM}&sort=ruling_desc   Organizations

“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

GET   /v1/organizations?view=changes&since_id={id}   Organizations

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 typeMeaning
new_filingA new Form 990 arrived.
revenue_swingRevenue moved materially year over year.
officer_changeThe reported top officer changed.
revocation_addedThe organization appeared on the IRS auto-revocation list.
pub78_droppedThe organization left IRS Publication 78.
pub78_restoredIt returned to Publication 78 after a drop.
adverse_mediaAdverse 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.

FieldCoverageWhy
ein, name, street, city, state, zip100%Straight from the IRS Business Master File.
county92.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_name1.5%Only organizations that filed a DBA on a Form 990 have one.
subsection100%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.

▶ API Playground live calls · your key stays in your browser

Request preview