Charity Verify API
One API call to check tax-deductible eligibility, revocation status, group exemptions, and OFAC sanctions screening for any U.S. nonprofit.
Overview
The Charity Verify API cross-references multiple IRS data sources and OFAC sanctions lists to give you a single, definitive answer on whether a nonprofit can receive tax-deductible donations.
Data sources checked on every request:
- IRS Exempt Organizations Business Master File (EO BMF)
- IRS Publication 78 (eligible donees)
- IRS Auto-Revocation List (with reinstatement detection)
- Group Exemption resolution (subordinate → parent lookup)
- OFAC SDN & Consolidated sanctions lists (fuzzy matching)
- Form 990 leadership screening against OFAC
Authentication
All API requests require an API key passed in the x-api-key header.
# Include your key in every request curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/verify?ein=53-0196605"
To get an API key, contact support@givalgo.ai. Keys are scoped to a usage plan (Free, Basic, or Pro) which determines your rate limits.
Keep your API key secure. Do not expose it in client-side code or public repositories. If compromised, contact support for a key rotation.
Rate-limit headers
Every response to a request made with your key reports your plan's limits and where you stand against them. The limits apply to all endpoints on the key together.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per day (UTC). |
X-RateLimit-Remaining | Requests left today. |
X-RateLimit-Reset | When the daily count resets, as Unix time in seconds (00:00 UTC). |
X-RateLimit-Per-Second | Sustained requests per second. |
X-RateLimit-Burst | Requests allowed in a short burst above that rate. |
A request over either limit returns 429. Over the per-second rate, retry after a short pause; over the daily limit, retry after the reset. For type-ahead search, wait for a pause in typing (about 250 ms) before sending a request, and cancel a request still in flight when the user keeps typing.
Quickstart
Get up and running in 60 seconds.
cURL
# Verify American Red Cross curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/verify?ein=53-0196605"
Python
import requests response = requests.get( "https://api.givalgo.ai/v1/verify", params={"ein": "53-0196605"}, headers={"x-api-key": "YOUR_API_KEY"} ) data = response.json() if data["deductible"]: print(f"{data['organization']['name']} is eligible!") else: print(f"Status: {data['status']} - {data['message']}")
JavaScript
const response = await fetch( "https://api.givalgo.ai/v1/verify?ein=53-0196605", { headers: { "x-api-key": "YOUR_API_KEY" } } ); const data = await response.json(); console.log(data.status, data.organization?.name);
Verify Endpoint
Returns a complete verification of the nonprofit identified by the given EIN, including tax-deductible eligibility, revocation history, group exemption status, OFAC sanctions screening, and California state compliance (when applicable).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ein | string | Yes | Employer Identification Number. Accepts 13-2875808, 132875808, or 13 1837418. |
donor_state | string | No | Accepted for backward compatibility; it does not change the result. The California checks run on every request. See California Compliance. |
ca_entity_id | string | No | California SOS/FTB entity number for direct lookup in the CA AG Registry and FTB Revocation list. Provides the highest coverage for California compliance checks. |
Response Fields
| Field | Type | Description |
|---|---|---|
status | string | Overall verification result (see Statuses) |
deductible | boolean | null | true: deductible on IRS evidence. false: not deductible (NOT_DEDUCTIBLE, REVOKED). null: IRS data does not settle it (NOT_FOUND, most CAUTION, and a REVOKED governmental unit, code governmental_unit). null is not the same as false. See Statuses. |
message | string | Human-readable explanation |
status_reasons New | object[] | Present only on CAUTION and REVOKED, and then never empty. One {code, message} per reason, in the order found; an organization can have several. Build logic on code and show message. See Status reason codes. |
in_master_file New | boolean | On every response. true when the EIN is in the current IRS Business Master File (when organization is present). |
pub78_bmf_conflicts New | string[] | On every response, never null. One code for each way Publication 78 and the IRS Business Master File disagree about this organization; empty when they agree. It reports the disagreement and never changes status or deductible. See Pub 78 vs master file conflict codes. |
bulletin_revocation New | object | Present only when a revocation the IRS announced in the Internal Revenue Bulletin decides the answer (REVOKED, or CAUTION while Publication 78 still lists the organization): the announcement, issue, effective date, the name and location as printed, and a link. |
organization | object | IRS EO BMF data (name, city, state, subsection, foundation type, assets, income, NTEE code) |
pub78 | object | Publication 78 listing and deductibility type (PC, PF, POF, SO, etc.; combined codes such as EO,LODGE are described part by part). via_group_exemption: true when the answer comes from the group parent's listing; church_exception: true when it rests on the IRS church classification. |
revocation | object | Auto-revocation history and reinstatement status |
group_exemption | object | Present when a 501(c)(3) subordinate is covered by a group exemption whose parent is in Pub 78. Shows the parent's details. The status is usually ELIGIBLE, and CAUTION when the subordinate's own record contradicts the coverage. |
sanctions_screening | object | OFAC screening results (see Sanctions Screening below for match object details) |
state_compliance | object | Always present. Contains the california sub-object with the AG May Not Operate list result and FTB status. See California Compliance. |
daf_grant New | object | null | What kind of charity this is and whether a donor-advised fund grant needs expenditure responsibility. Present on ELIGIBLE and CAUTION, null otherwise. See DAF grants. |
data_freshness | object | Per source (eo_bmf, pub78, revocation, ofac_sdn, ofac_cons, ca_ag, ca_ftb, irb): the agency's publication date (for irb, the newest Internal Revenue Bulletin issue read) (ISO) of the file version loaded in our database and used for this verdict. Null only when we have no date. |
last_checked | object | Per source: when we last confirmed with the agency that nothing newer than the data_freshness file exists (ISO 8601, UTC). Checked daily at about 01:45 UTC (the Bulletin at about 01:55); if a check fails, the source keeps its last successful time. Null until a check is recorded. |
stale_sources | string[] | The data_freshness sources that may be behind the agency: a newer file published a day or more ago has not loaded, a newer file is held for review because its row count changed far more than usual, or the agency's server has been unreachable for two days. Empty when everything is current. |
checked_at | string | ISO 8601 timestamp of this verification |
sanctions_screening object
| Field | Type | Description |
|---|---|---|
status | string | CLEAR, POTENTIAL_MATCH, STRONG_MATCH, or NOT_SCREENED |
organization_screening | object | Org name screened against OFAC entity entries |
.checked_name | string | Organization name that was screened |
.matches_found | integer | Number of OFAC matches |
.matches[] | array | Array of match objects (see below) |
leadership_screening | object | Form 990 officers/directors screened against OFAC individuals |
.people_data_tax_year | integer | null | Tax year of the Form 990 people data used for screening (e.g., 2023). Null if no people data found. |
.people_source | string | null | form_990 (Part VII roster of the latest Form 990 / 990-EZ) or form_990n_principal_officer (no roster on file; the principal officer named on the latest Form 990-N e-Postcard was screened). Null if no names were available. |
.people_checked | integer | Number of people screened (Form 990 roster, or 1 for an e-Postcard filer's principal officer) |
.people_screened[] | array | List of officers/directors/key employees screened, each with name and title |
.name | string | Person's name as reported on Form 990 |
.title | string | Person's title/role (e.g., "EXECUTIVE DIRECTOR", "BOARD PRESIDENT") |
.matches_found | integer | Number of OFAC matches |
.matches[] | array | Array of match objects (see below) |
checked_against | array | ["OFAC SDN", "OFAC Consolidated"] |
screened_at | string | ISO 8601 timestamp of screening |
Match object (within matches[])
| Field | Type | Description |
|---|---|---|
source | string | "OFAC SDN" or "OFAC Consolidated" |
matched_name | string | Name from OFAC list that matched |
matched_via | string | "primary_name" or "alias (a.k.a. ...)" |
primary_name | string | Primary OFAC name (only present for alias matches) |
similarity_score | number | Fuzzy match score (0.7–1.0). ≥0.9 = STRONG, ≥0.7 = POTENTIAL |
match_strength | string | "STRONG" or "POTENTIAL" |
sdn_type | string | OFAC entity type ("individual", "N" for entity/org) |
program | string | OFAC sanctions program (e.g., "SDGT", "IRAN") |
entry_id | integer | OFAC entry number |
remarks | string | OFAC remarks: often contains date of birth, nationality, passport numbers, and other identifying information. Key for false positive disambiguation. |
addresses | array | Known addresses for this OFAC entry. Each has address, city_state_zip, and country fields. |
Verification Statuses
Every EIN gets exactly one status, and the message explains the specific reason. Each verification checks the IRS Business Master File, IRS Publication 78, the IRS auto-revocation list and California’s registries.
| Status | Deductible | Meaning |
|---|---|---|
ELIGIBLE | true | IRS records confirm the organization can receive tax-deductible contributions: it is listed in Publication 78, covered by a listed parent’s group exemption, or a church (churches need not appear in Publication 78). |
CAUTION | null, or true | The IRS records conflict or do not settle deductibility, or a California finding applies. deductible stays true when the only caveat is a California finding or a reinstatement the IRS has not yet confirmed in Publication 78. Confirm before a material grant, for example in the IRS Tax Exempt Organization Search. |
NOT_DEDUCTIBLE | false | IRS records establish that contributions are not deductible, for example because the organization is not a 501(c)(3). |
REVOKED | false, or null | The IRS auto-revoked the organization’s exemption for not filing, and there is no evidence of reinstatement; or the IRS announced a revocation of its 501(c)(3) status in the Internal Revenue Bulletin and Publication 78 no longer lists it. |
NOT_FOUND | null | Not in the IRS master file or Publication 78, and not revoked. Churches are not required to apply for recognition, so a church can legitimately be absent. |
null means IRS data does not settle it, which is not the same as false. Customers receive our full Verification Methodology, which sets out the rules behind each status, under NDA. Contact sales@givalgo.ai.
Status reason codes
Every CAUTION and REVOKED response lists why in status_reasons. Codes are stable: we may reword a message, but we never rename or reuse a code. We may add codes, so treat one you do not recognize as “review manually”.
| Code | Status | Meaning |
|---|---|---|
auto_revoked | REVOKED | Automatically revoked by the IRS for not filing for three consecutive years, with no evidence of reinstatement. |
bulletin_revoked | REVOKED | The IRS announced a revocation of its 501(c)(3) status in the Internal Revenue Bulletin, usually after an examination, and Publication 78 no longer lists it. See bulletin_revocation. |
governmental_unit | REVOKED | Added to bulletin_revoked when the IRS classifies the organization as a governmental unit: a gift to a governmental unit for public purposes can be deductible under IRC §170(c)(1) without 501(c)(3) status, so deductible is null. Confirm before granting. |
not_on_pub78 | CAUTION | A 501(c)(3) the IRS master file marks deductible, but not listed in Publication 78. |
deductibility_undetermined | CAUTION | A 501(c)(3) the IRS has not assigned a deductibility determination, not listed in Publication 78. |
treaty_only | CAUTION | A foreign organization deductible only by tax treaty. |
irs_church_classification_contradicted | CAUTION | The IRS classifies it as a church, but its own record says contributions are not deductible. |
group_exemption_contradicted | CAUTION | Covered by a listed parent’s group exemption, but its own record says contributions are not deductible. |
foreign_address | CAUTION | A church or group subordinate whose IRS record places it outside the United States. |
auto_revoked_parent_listed | CAUTION | Auto-revoked with no recorded reinstatement, but covered by a group exemption whose parent is listed today. |
auto_revoked_later_ruling | CAUTION | Auto-revoked with no recorded reinstatement; an IRS determination dated after the revocation is the only sign it was recognized again. deductible stays true when this is the only reason. |
bulletin_revoked_still_on_pub78 | CAUTION | The IRS announced a revocation of its 501(c)(3) status in the Internal Revenue Bulletin, but Publication 78 still lists it. Reliance on the listing ends with the announcement (Rev. Proc. 2018-32, section 4.01). See bulletin_revocation. |
ca_not_in_good_standing | CAUTION | On California’s May Not Operate list or revoked by the Franchise Tax Board. Federal deductibility is unaffected, so deductible stays true when this is the only reason. |
Pub 78 vs master file conflict codes
pub78_bmf_conflicts lists where IRS Publication 78 and the IRS Business Master File disagree, on any status. It is informational: status and deductible are decided the same way with or without it. Codes are stable and never renamed; we may add codes, so treat one you do not recognize as “review manually”.
| Code | Meaning |
|---|---|
pub78_not_in_bmf | Listed in Publication 78 but absent from the master file. |
bmf_deductible_not_in_pub78 | The master file marks it a deductible 501(c)(3), but it is not listed in Publication 78 although it would be expected to be. |
subsection_mismatch | Publication 78 lists it as a 501(c)(3) type of charity, but the master file classifies it under another subsection. |
charity_type_mismatch | Publication 78 and the master file name different types of charity (for example public charity and private foundation). |
DAF grants
daf_grant tells a donor-advised fund sponsor what kind of charity the organization is and whether a grant to it needs expenditure responsibility under IRC §4966. It is present on ELIGIBLE and CAUTION, and null on REVOKED, NOT_DEDUCTIBLE and NOT_FOUND.
| Field | Values |
|---|---|
charity_type | public_charity, supporting_organization, private_operating_foundation, private_foundation, other_170c (eligible for deductible gifts but not a public charity, such as a veterans organization or fraternal lodge), or unknown. |
supporting_org_type | For a supporting organization: type_i, type_ii, type_iii_functionally_integrated, type_iii_non_functionally_integrated, type_i_ii_or_iii_functionally_integrated or unknown. Otherwise null. |
expenditure_responsibility | not_required, required, or review when IRS data cannot settle it. |
note | A sentence or two on why, and what to check when the answer is review. |
"daf_grant": { "charity_type": "supporting_organization", "supporting_org_type": "type_i", "expenditure_responsibility": "review", "note": "A supporting organization (IRC §509(a)(3)). A DAF grant needs no expenditure responsibility unless a donor or advisor of the fund, or a related person, controls an organization it supports (IRC §4966(d)(4)). Check that before granting." }
Sanctions Screening
Every verification includes an automated OFAC screening of the organization name and its leadership (officers, directors, key employees from Form 990). Matches are checked against both SDN and Consolidated lists, including primary names and aliases.
Overall status
| Status | Meaning |
|---|---|
CLEAR | No OFAC matches found for the organization or its leadership. |
POTENTIAL_MATCH | Fuzzy match with 70–89% similarity. Manual review recommended. |
STRONG_MATCH | Fuzzy match with 90%+ similarity. Very likely the same entity. |
NOT_SCREENED | OFAC screening was not performed (e.g., organization not found in IRS database). See the reason field for details. |
Disambiguation fields
When matches are found, each match object includes remarks and addresses to help you distinguish true positives from false positives:
remarks: OFAC remarks field containing date of birth, nationality, passport numbers, and other identifying informationaddresses: known addresses for the matched OFAC entry (street, city/state/zip, country)
Example match object
{
"source": "OFAC SDN",
"matched_name": "SMITH, John A.",
"matched_via": "primary_name",
"similarity_score": 0.912,
"match_strength": "STRONG",
"sdn_type": "individual",
"program": "SDGT",
"entry_id": 12345,
"remarks": "DOB 15 Mar 1970; nationality Iran; Passport A1234567",
"addresses": [
{
"address": "123 Example St",
"city_state_zip": "Tehran",
"country": "Iran"
}
]
}In this example, a board member named "John Smith" matched an OFAC entry, but the remarks show an Iranian DOB/passport, and the addresses show Tehran. If your board member is based in Ohio with a different DOB, this is a false positive you can confidently dismiss.
California Compliance
California AB 488 requires fundraising platforms to verify that nonprofits are in good standing with three authorities before facilitating donations involving a California donor or a California-based nonprofit:
- IRS: federal tax-exempt status (always checked)
- CA Attorney General: May Not Operate or Solicit for Charitable Purposes List (11 CCR § 312). This is a negative list: being on it is the finding, and absence from it is the AB 488 good-standing signal (11 CCR § 316(c)).
- CA Franchise Tax Board: state tax-exempt status (revocation check)
The California checks run on every request, for every organization, wherever it is based. The Attorney General’s list includes organizations based in other states, and AB 488 covers every organization a platform solicits for. donor_state is accepted but does not change the result. Pass ca_entity_id, the California SOS/FTB entity number, for a direct lookup.
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 elsewhere understands why the overall status is CAUTION.
match_method in the response says how the organization was matched to the list: ca_entity_id, fein, or name_match (name within the same city).
state_compliance.california object
| Field | Type | Description |
|---|---|---|
ag_registry | object | Result against the CA Attorney General's May Not Operate list |
.status | string | NOT_LISTED (not on the list), or the finding for a listed org: REVOKED, SUSPENDED, DELINQUENT, CEASE_AND_DESIST, LISTED (unclassified, but still not in good standing) |
.registration_status | string | Verbatim AG registration status (e.g., "Revoked", "Delinquent", "Delinquent Platform Charity"). Null for Cease-and-Desist rows |
.entity_status | string | Verbatim AG entity status (e.g., "Listed", "Cease and Desist Order", "Merged Out") |
.notice | string | Present for NOT_LISTED (what the result does and does not establish) and for a listed organization based outside California (barred from soliciting in California; says nothing about its home-state standing) |
.reg_number | string | State charity registration number (e.g., CT0252232) |
.match_method | string | ca_entity_id, fein, or name_match |
.matched_name | string | Present for name_match: the CA AG name that matched |
.name_similarity | number | Present for name_match: similarity score of the name match |
ftb | object | CA Franchise Tax Board revocation result |
.status | string | CLEAR or REVOKED |
.revocation_date | string | Date of FTB revocation (if revoked) |
state_status | string | Roll-up: CLEAR (not on the AG list, no FTB revocation), NON_COMPLIANT (on the AG list), or REVOKED (FTB) |
Impact on overall status
| AG Registry | FTB | state_status | Overall status |
|---|---|---|---|
| NOT_LISTED | CLEAR | CLEAR | No change |
| REVOKED / SUSPENDED / DELINQUENT / CEASE_AND_DESIST / LISTED | CLEAR | NON_COMPLIANT | ELIGIBLE → CAUTION |
| Any | REVOKED | REVOKED | ELIGIBLE → CAUTION |
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 20,000 EINs per job, in a JSON body (a semicolon-separated string or an array) or as a CSV upload, processes them asynchronously via background workers, and delivers results via polling endpoint, webhook callback, or CSV download.
Each EIN goes through the same verification as the single Verify endpoint: IRS data cross-referencing, group exemption resolution, and OFAC sanctions screening.
How it works
- Submit: POST your EINs to
/v1/bulk-verify. Get back ajob_idimmediately (202 Accepted). - Process: EINs are split into batches of 10 and verified in parallel by background workers.
- Retrieve: Poll
GET /v1/bulk-verify/{job_id}for progress and results, or receive a webhook callback on completion.
| Input Method | Max EINs | Format |
|---|---|---|
| JSON body (semicolon-separated string, or an array) | 20,000 | application/json |
| CSV file upload | 20,000 | multipart/form-data |
Authentication
All API requests require an API key passed in the x-api-key header, the same key used for the single Verify endpoint.
# Include your key in every request curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"eins": "13-2875808;53-0196605"}'
To get an API key, contact support@givalgo.ai. Keys are scoped to a usage plan (Free, Basic, or Pro) which determines your rate limits and max EINs per job.
Job ownership: Jobs are scoped to your API key. You can only view the status and results of jobs you created.
Quickstart
Submit a bulk job and poll for results in under a minute.
cURL: Submit + Poll
# 1. Submit a bulk job curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "eins": "13-2875808;53-0196605;06-0646973", "format": "both" }' # Response: {"job_id": "bulk_abc123", "status": "running", ...} # 2. Poll for results curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/bulk-verify/bulk_abc123"
Python
import requests, time # Submit bulk job resp = requests.post( "https://api.givalgo.ai/v1/bulk-verify", headers={"x-api-key": "YOUR_API_KEY"}, json={ "eins": "13-2875808;53-0196605;06-0646973", "format": "both" } ) job = resp.json() job_id = job["job_id"] # Poll until complete while True: status = requests.get( f"https://api.givalgo.ai/v1/bulk-verify/{job_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "done": print(f"Done! {status['summary']}") break time.sleep(2)
JavaScript
// Submit bulk job const resp = await fetch("https://api.givalgo.ai/v1/bulk-verify", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ eins: "13-2875808;53-0196605;06-0646973", format: "both" }) }); const { job_id } = await resp.json(); // Poll until complete const poll = async () => { const res = await fetch( `https://api.givalgo.ai/v1/bulk-verify/${job_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); const data = await res.json(); if (data.status === "done") return data; await new Promise(r => setTimeout(r, 2000)); return poll(); }; const results = await poll();
Submit Bulk Job
Submit EINs for bulk verification. Returns 202 Accepted with a job_id for tracking.
JSON body (semicolon-separated EINs, or an array)
curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "eins": "13-2875808;53-0196605;06-0646973", "webhook_url": "https://your-server.com/hook", "format": "both" }'
CSV file upload
curl -X POST "https://api.givalgo.ai/v1/bulk-verify" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@organizations.csv" \ -F "webhook_url=https://your-server.com/hook"
CSV must have an ein column header (case-insensitive). Falls back to the first column if no header match.
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
eins | string | Yes* | Semicolon-separated EINs. *Not needed for CSV upload. |
webhook_url | string | No | URL to POST results to on job completion. |
format | string | No | Result format: "json", "csv", or "both" (default). |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
skip_invalid | boolean | false | If true, skip invalid EINs and process only valid ones. If false, return a 400 error listing all invalid EINs. |
Response (202 Accepted)
{
"job_id": "bulk_a1b2c3d4e5f6",
"status": "running",
"total_eins": 247,
"duplicates_removed": 3,
"estimated_seconds": 8,
"poll_url": "/v1/bulk-verify/bulk_a1b2c3d4e5f6",
"created_at": "2026-03-25T14:30:00Z"
}Validation error (400)
If any EINs fail validation (and skip_invalid is not set), you get a 400 with details:
{
"error": "VALIDATION_ERROR",
"message": "3 EINs failed validation",
"invalid_eins": [
{ "ein": "123", "error": "EIN must be exactly 9 digits" }
],
"valid_count": 247,
"hint": "Add ?skip_invalid=true to process only valid EINs."
}Poll Job Status
Check the progress and retrieve results of a bulk verification job. Jobs are scoped to your API key: you can only access jobs you created.
In progress
{
"job_id": "bulk_a1b2c3d4e5f6",
"status": "running",
"progress": {
"total": 247,
"completed": 180,
"failed": 2,
"percent_complete": 73.7
}
}Completed
For jobs with 1,000 or fewer EINs, results are included inline. Larger jobs return a csv_download_url only. Each result in the array is a full single-verify response, identical to what you'd get from the /v1/verify endpoint, including organization details, Pub78, revocation history, and OFAC sanctions screening.
{
"job_id": "bulk_a1b2c3d4e5f6",
"status": "done",
"summary": {
"eligible": 200,
"not_deductible": 30,
"revoked": 5,
"not_found": 8,
"ofac_flags": 3
},
"results": [
{
"ein": "13-2875808",
"ein_raw": "132875808",
"status": "ELIGIBLE",
"deductible": true,
"message": "Organization is eligible to receive tax-deductible donations.",
"organization": { /* full org details: name, city, state, NTEE, assets, ... */ },
"pub78": { /* listed, deductibility_code, description */ },
"revocation": { /* ever_revoked, currently_revoked, dates */ },
"sanctions_screening": { /* status, org & leadership screening, matches */ },
"checked_at": "2026-03-25T23:11:10Z",
"api_version": "v1"
},
// ... one entry per EIN, same structure as single /v1/verify response
],
"csv_download_url": "https://...presigned...",
"completed_at": "2026-03-25T14:30:07Z"
}CSV download
The csv_download_url (pre-signed S3 URL, valid 1 hour) contains a flattened version of the full results with 49 columns covering organization details, Pub78 status, revocation history, and OFAC screening. Column headers:
ein, status, deductible, message, reason_codes, 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, in_master_file, pub78_bmf_conflicts
The eight ca_* columns are populated on every row, because 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.
reason_codes holds the code of every entry in status_reasons, separated by ;, and is empty unless the status is CAUTION or REVOKED. See Status reason codes.
in_master_file and pub78_bmf_conflicts are the fields of the same name on GET /v1/verify; the conflict codes are separated by ; and the column is empty when the records agree. See Pub 78 vs master file conflict codes.
sanctions_status is sanctions_screening.status from GET /v1/verify: CLEAR, POTENTIAL_MATCH, STRONG_MATCH or NOT_SCREENED (no name to screen, e.g. an EIN not found in IRS records). The summary's ofac_flags counts the POTENTIAL_MATCH and STRONG_MATCH rows.
Job statuses
| Status | Meaning |
|---|---|
running | Being processed by background workers. |
done | All EINs have been verified. Results are available. |
failed | The job stopped before finishing. Partial results may be available. |
Webhooks
If you provide a webhook_url when submitting a bulk job, we'll POST a notification when the job completes. This is the recommended approach for large jobs instead of polling.
Webhook payload
# POST to your webhook_url # Headers: # Content-Type: application/json # User-Agent: Givalgo-Webhook/1.0 # X-Givalgo-Event: bulk_verify.completed # X-Givalgo-Job-Id: bulk_a1b2c3d4e5f6 { "event": "bulk_verify.completed", "job_id": "bulk_a1b2c3d4e5f6", "status": "done", "total_eins": 247, "completed": 245, "failed": 2, "summary": { "eligible": 200, "not_deductible": 30, "ofac_flags": 3 }, "csv_download_url": "https://...presigned-url...24hr-expiry...", "poll_url": "/v1/bulk-verify/bulk_a1b2c3d4e5f6" }
Retry policy
Webhook delivery is attempted up to 3 times with exponential backoff (1s, 5s delays). If your endpoint returns a 2xx status, delivery is marked as successful. The csv_download_url is a pre-signed S3 URL valid for 24 hours.
Throughput
| EINs | Estimated Time |
|---|---|
| 10 | ~5 seconds |
| 50 | ~15 seconds |
| 250 | ~30 seconds |
| 1,000 | ~2 minutes |
| 20,000 | ~40 minutes |
Job Statuses
A bulk job progresses through these statuses:
| Status | Meaning |
|---|---|
running | Being processed by background workers. Poll for progress updates. |
done | All EINs have been verified. Results are available inline (for jobs ≤ 1,000 EINs) and via CSV download URL. |
failed | The job stopped before finishing. Partial results may be available in progress. |
Verification Statuses
Each EIN in a bulk job receives the same verification statuses as the single Verify endpoint:
| Status | Deductible | Meaning |
|---|---|---|
ELIGIBLE | true | Listed in IRS Pub 78 (directly or via group exemption), or a domestic church under the statutory church exception. Eligible for tax-deductible donations. |
NOT_DEDUCTIBLE | false | In IRS records but not tax-deductible (e.g., 501(c)(4), 501(c)(6)). |
REVOKED | false | Tax-exempt status auto-revoked by the IRS for non-filing. May appear even if the organization has dropped off the Business Master File. |
CAUTION | null, or true | The IRS records do not settle it, or a California finding applies. The reason_codes column lists every reason, separated by ; (see Status reason codes), and message explains them. Confirm via the IRS Tax Exempt Organization Search. |
NOT_FOUND | null | Not in IRS records and not on the revocation list. Churches and self-declared orgs may not appear. |
Limits & Throughput
These limits apply to every bulk job.
| Limit | Value |
|---|---|
| Max EINs (JSON body) | 20,000 |
| Max EINs (CSV upload) | 20,000 |
| Max concurrent jobs | 5 per API key |
| Result retention | 30 days from submission (results_expire_at), then 410 JOB_EXPIRED |
| CSV download URL expiry | 24 hours |
Estimated processing times
| EINs | Estimated Time |
|---|---|
| 10 | ~5 seconds |
| 50 | ~15 seconds |
| 250 | ~30 seconds |
| 1,000 | ~2 minutes |
| 20,000 | ~40 minutes |
Processing time depends on current load and Lambda warm-start state. For time-sensitive workloads, use a webhook for instant notification on completion.
Report API
Generate professional, branded PDF verification reports for any U.S. nonprofit, ready to share with donors, board members, or compliance teams.
Overview
The Report API takes an EIN and returns a multi-page PDF document containing the same verification data as the Verify endpoint, presented in a polished, print-ready format with Givalgo branding.
Each report includes a cover page, verification outcome with status badge, IRS data breakdown, OFAC sanctions screening results, financial snapshot, and a branded back page.
application/pdf
Authentication
The Report API uses the same API key authentication as the Verify and Bulk Verify endpoints. Include your key in the x-api-key header:
curl -H "x-api-key: YOUR_API_KEY" \ -H "Accept: application/pdf" \ "https://api.givalgo.ai/v1/report?ein=13-2875808" \ -o report.pdf
Quickstart
Generate your first PDF report in seconds:
# Download a verification report for Human Rights Watch (EIN 13-2875808) curl -H "x-api-key: YOUR_API_KEY" \ -H "Accept: application/pdf" \ "https://api.givalgo.ai/v1/report?ein=13-2875808" \ -o verification-report.pdf # Open the PDF open verification-report.pdf
Tip: Include the Accept: application/pdf header to receive decoded binary PDF.
In Postman, use Send and Download (dropdown next to Send) to save the file.
Report Endpoint
Takes an EIN and returns a professionally formatted PDF verification report. The underlying data is identical to the /v1/verify endpoint; the Report API simply renders it into a branded, multi-page PDF document.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ein | string | Yes | Employer Identification Number. Accepts 13-2875808, 132875808, or 13 1837418. |
Headers
| Header | Required | Description |
|---|---|---|
x-api-key | Yes | Your API key |
Accept | Recommended | Set to application/pdf for decoded binary response |
Success Response
| Property | Value |
|---|---|
| Status Code | 200 OK |
| Content-Type | application/pdf |
| Content-Disposition | inline; filename="npo-verify-{ein}.pdf" |
Error Responses
Error responses are returned as JSON (not PDF):
| Status | Error Code | Description |
|---|---|---|
400 | MISSING_EIN | The ein query parameter was not provided |
400 | INVALID_EIN | EIN format is invalid (not 9 digits) |
403 | Forbidden | Missing or invalid API key |
500 | INTERNAL_ERROR | Server-side error during PDF generation |
Note: All valid verification statuses (ELIGIBLE, REVOKED, NOT_FOUND, CAUTION, NOT_DEDUCTIBLE) return a 200 with a PDF. Only auth or input errors return JSON.
Report Sections
Each generated PDF contains the following pages:
| Page | Section | Content |
|---|---|---|
| 1 | Cover Page | Givalgo branding, organization name, EIN, report generation date |
| 2 | Outcome & Details | Large status badge (color-coded), deductibility verdict, organization details (city, state, subsection, NTEE code, ruling date) |
| 3 | IRS Status Breakdown | EO BMF status & deductibility, Pub 78 listing, group exemption details, revocation history with dates |
| 4 | OFAC Screening | Organization & leadership sanctions screening status, match details (if any), data source year |
| 5 | Financial Snapshot | Total assets, annual income, annual revenue (from IRS BMF data) |
| 6 | Back Page | Disclaimer, API version, generation timestamp, contact info |
Verification Statuses
The report displays the same verification statuses as the Verify endpoint, rendered as color-coded badges:
| Status | Badge Color | Deductible | Meaning |
|---|---|---|---|
ELIGIBLE | ● Green | Yes | Listed in IRS Pub 78 (directly or via group exemption), or a domestic church under the statutory church exception. |
NOT_DEDUCTIBLE | ● Orange | No | In IRS records but not tax-deductible. |
REVOKED | ● Red | No | Tax-exempt status auto-revoked by the IRS. |
CAUTION | ● Yellow | Unconfirmed | The IRS records do not settle it, or a California finding applies. The report states the case. |
NOT_FOUND | ● Gray | Unknown | Not in IRS records. Churches and self-declared orgs may not appear. |
Important Notes
Response Time
PDF generation typically takes 2–4 seconds (cold start may add 3–5 seconds on first request). The API Gateway timeout is 29 seconds.
Data Source
Reports contain the same data as the /v1/verify endpoint. The PDF is generated on-the-fly from live verification data and 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 and financials, 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 two kinds of criteria, alone or together:
search_terms: an EIN ("13-2875808") or an organization name ("Human Rights Watch"). Keywords match organization names, not mission text and not cause. To search by cause usefilters.organization.causes(plain English) orntee_major_codes(codes).filters: structured criteria for cause (plain English or 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)
Every request returns the same lean org record (~40 fields): identity, geography, cause, IRS standing, latest financial toplines, plus the organization's logo, website, and website-scraped contact details where available.
When you've found the organization you care about, go deep with the Data Pro API:
POST /v1/dataData: search & discovery; lean recordsGET /v1/data/suggestData: autocomplete for a search box, organizations as the user types a name or EINGET /v1/data-proPro: the full single-org profile (~410 fields: full financial detail, compensation cube, governance, schedules, registration)
Migrating from another nonprofit data API? Familiar request-body conventions and field aliases are accepted where the data overlaps; unsupported fields return a 400 with a hint naming the nearest supported equivalent. See Filters.
Tiers: Data vs Data Pro
The two tiers differ by how you interact, not just field count. The Data API answers "which organizations?" (search and filter) with lean records. Data Pro answers "tell me everything about this one" with a single-EIN deep profile. Both require the same x-api-key header.
| /v1/data Data | /v1/data-pro Pro | |
|---|---|---|
| Interaction | Search: EIN / name / keywords, structured filters | Lookup: one EIN per request |
| Response | Page of lean org records (~40 fields each) | One deep profile (~410 fields) |
| Best for | Discovery, screening, autocomplete, pipeline building, monitoring | Due diligence, profiles, analysis |
The deep-profile sections below (organization, financials, trends, compensation, …) document the Data Pro response:
| Section | /v1/data-pro Pro |
|---|---|
organization: identity, NTEE, mission | ✓ |
financials: revenue, expenses, balance sheet | ✓ |
trends: 6-year annual series + CAGR + trend labels | ✓ |
compensation: officers, by_role, peer benchmarks | ✓ |
funding_sources: grantmakers received from | ✓ |
programs: program descriptions + expenses | ✓ |
governance: board + policies | ✓ |
fundraising: events, professional fundraisers | ✓ |
financial_profile: liquidity, HHI, expense composition New | ✓ |
related_organizations | ✓ |
foreign_activities, lobbying_and_political, donor_advised_funds | ✓ |
data_freshness | ✓ |
financials.detail: Part VIII/IX/X line items + Schedule D blocks Pro | ✓ |
governance.detail: full board roster, policy flags, compliance filings | ✓ |
disclosures: Schedules K/L/M/N/F/A/O | ✓ |
registration: IRS BMF + Pub 78 + California AG/FTB | ✓ |
New indicates fields added in the April 2026 release.
Authentication
All requests require an API key passed in the x-api-key header. Keys are scoped to a usage plan (Free, Basic, Pro) which determines rate limits.
# 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"
cURL: autocomplete as the user types
curl -H "x-api-key: YOUR_API_KEY" \ "https://api.givalgo.ai/v1/data/suggest?q=red%20cro&session_token=8f14e45f-ceea-467a-9b7e-5d4f8c1a2b3c"
See Autocomplete for the parameters and response.
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 }
Python: search, then go deep with Data Pro
import requests HEADERS = {"x-api-key": "YOUR_API_KEY"} # 1. Find California food banks with $10M+ revenue, biggest first search = requests.post( "https://api.givalgo.ai/v1/data", json={ "filters": { "geography": {"state": ["CA"]}, "organization": {"ntee_major_codes": ["K"]}, "financials": {"total_revenue": {"min": 10000000}}, }, "sort": "revenue_desc", }, headers=HEADERS, ).json() top = search["results"][0] print(top["name"], top["ein"], top["website"]) # 2. Full profile for the top hit deep = requests.get( "https://api.givalgo.ai/v1/data-pro", params={"ein": top["ein"]}, headers=HEADERS, ).json() print(deep["financial_profile"]["expense_composition"]["program_expense_ratio"])
JavaScript
const r = await fetch("https://api.givalgo.ai/v1/data", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ search_terms: "Human Rights Watch" }), }); const { results, total } = await r.json(); console.log(total, results[0].name, results[0].mission);
Endpoints
The Data API. JSON body with search_terms (EIN / organization name), filters, or both; 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}. The deep Form 990 profile is served on /v1/data-pro.
Autocomplete: up to 10 organizations matching a partly typed name or EIN, for a search box dropdown. See Autocomplete.
The deep single-org profile (~410 fields): all core sections plus financials.detail, governance.detail, disclosures, and registration. Documented under Data Pro Response.
Parameters (GET endpoints)
| Parameter | Type | Required | Description |
|---|---|---|---|
ein | string | Yes | Employer Identification Number. Accepts 53-0196605, 530196605, or 53 0196605. |
tax_year | integer | No | /v1/data-pro only: 4-digit fiscal year (e.g. 2022). Omit to get the most recent filing. If the requested year has no filing, the response is a 404 with a years_available array. Ignored on GET /v1/data (lean records are latest-year only). |
There is no include parameter. The endpoint is the tier contract: /v1/data-pro returns every Pro section on every request, and data_freshness.includes_applied is always ["all"]. An ?include= on the query string is ignored: a request with and without it returns byte-identical JSON. You never need it to obtain a section.
Search Request Body
POST /v1/data takes a JSON body. At least one of search_terms and filters is required.
| Field | Type | Description |
|---|---|---|
search_terms | string | EIN ("13-2875808") or organization name. Keywords match organization names only, not mission or cause. Combines with filters. For cause, use filters.organization.causes or ntee_major_codes. |
filters.organization.causes | string | string[] | Search by cause in plain English: "hunger relief", "adult literacy". Resolved to NTEE codes server-side; composes with every other filter. Only the cause is read; place and money inside the phrase are ignored (use filters.geography / filters.financials). A phrase that maps to nothing returns 400 UNRESOLVED_CAUSES, never an empty page. Max 5, 200 chars each. Daily limit on new phrases: each key can resolve 100 phrases a day that have never been resolved before (resets at UTC midnight). A phrase already resolved once is free and unlimited, as are NTEE codes passed directly. Every response that uses causes carries causes_quota {period, limit, used, remaining, resets_at}. |
filters | object | Structured search. See Filters. |
sort | string | object | relevance (default), revenue_desc, revenue_asc, assets_desc, grants_desc, name_asc; or a {"sort_by": "total_revenue", "ascending": false} object. |
from | integer | Result offset (default 0). Must be a multiple of size; from + size ≤ 2000. |
size | integer | Results per page, 1–50 (default 25). |
Filters
Three sections: geography, organization, financials. Filters AND together across fields; lists within a field are OR. Alternate field nesting (organization.properties.*, financials.most_recent_year.*) is accepted for painless migration from other nonprofit data APIs, as are the aliases noted below. Unsupported fields (e.g. subject_codes, county, last_updated) return 400 UNSUPPORTED_FILTER with a hint naming the nearest equivalent.
geography
| Field | Type | Description |
|---|---|---|
state | string | list | 2-letter state code(s), e.g. ["OH", "MI"]. |
city | string | list | City name(s). Metro aliases expand: "New York" covers all five boroughs. |
zip | string | 5-digit ZIP. |
organization
| Field | Type | Description |
|---|---|---|
ntee_major_codes | list | NTEE major letters, e.g. ["B", "E"]. |
ntee_codes | list | Full NTEE codes, e.g. ["B21"]. Alias: ntee_minor_codes. |
ntee_mode | string | any (default) or all. |
subsection_codes | list | IRS subsection. Accepts "03", 3, "501c3", or "501(c)(3) Public Charity". |
org_type | string | foundation (grantmakers) or nonprofit (grantseekers). Roles are non-exclusive. |
exclude_revoked_organizations | bool | Drop revoked orgs. Also accepted under properties and as exclude_defunct_or_merged_organizations. |
financials
Ranges are {"min": n, "max": n} objects (either bound optional). Also accepted nested under most_recent_year.
| Field | Description |
|---|---|
total_revenue, total_assets, net_assets | Latest-filing dollar ranges. |
grants_made | Grants the org paid out (latest filing). Alias: total_giving. |
revenue_5y, grants_5y | Peak revenue / grants paid across the last 5 filings, which catches orgs whose latest year understates them. |
program_expense_ratio | 0–1. Program share of total expenses. |
months_of_cash | Liquid reserves in months of operating expenses. |
revenue_concentration | 0–1 concentration of revenue sources (higher = more concentrated). |
revenue_growth_streak_min, revenue_decline_streak_min, grants_growth_streak_min, grants_decline_streak_min | Integer 1–5: minimum consecutive years of YoY growth / decline. |
The financial-health screens (ratio, cash, streaks, concentration) aren't available in traditional nonprofit data APIs; they're computed from the full filing history.
Data API Response: the Lean Org Record
{
"mode": "search",
"result_kind": "orgs",
"total": 512,
"total_is_capped": false,
"from": 0, "size": 25, "took_ms": 412,
"suggestion": null,
"results": [ { /* lean org record */ } ]
}Each result is a lean org record (~40 fields):
| Group | Fields |
|---|---|
| Identity | ein, name, legal_name (the IRS legal name; the same as name unless a familiar name is shown instead, e.g. name “University of Michigan”, legal_name “Regents of the University of Michigan”), 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, which renders tone-aware) |
| Geography | city, state, zip, address |
| Contact | website, contact.email, contact.phone, contact.contact_url (website-scraped) |
| Classification | ntee_code, ntee_description (its major group), ntee_sub_description (the code’s own title: “Universities” for B43), ntee_inferred (our predicted cause code for the ~259K organizations the IRS never coded; present only when ntee_code is null, so an inferred code never overwrites an IRS one), subsection, deductibility, org_type, is_grantmaker, is_grantseeker, legal_status |
| Standing | irs_status, pub78_verified, delisted, delisted_reason, ruling_year |
| Financials | latest_tax_year, total_revenue, total_expenses, total_assets, net_assets, grants_paid, grants_received_total, total_employees |
| Screens | program_expense_ratio, months_of_cash, revenue_growth_streak, revenue_decline_streak, grants_growth_streak, grants_decline_streak, revenue_concentration, relevance_score |
Autocomplete
GET /v1/data/suggest fills a search box dropdown as the user types. Call it with the text typed so far and show the rows it returns. It matches names and EINs only, with no causes or financial filters. When the user submits the box, run the full search with POST /v1/data; when they pick a row, open that organization by its ein.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Yes | The text typed so far: part of a name (red cro) or of an EIN (13-28). Every word must match the start of a word in a name, so any word may be partly typed. Three or more digits alone (with or without the hyphen) are read as the start of an EIN. Fewer than 2 characters returns an empty list; at most 100. |
size | integer | No | Rows to return, 1 to 10 (default 8). |
state | string | No | Two-letter state code. Only organizations in that state are returned. |
session_token | string | Recommended | An ID you create for one search session (a UUID works) and send on every call in that session: 8 to 64 letters, digits, hyphens or underscores. It lets usage be measured per search session rather than per keystroke. Start a new token when the box is cleared or a row is picked. |
Response
# GET /v1/data/suggest?q=doctors%20witho&size=1 { "suggestions": [ { "ein": "13-3433452", "name": "Medecins Sans Frontieres USA Inc", "dba_name": "Doctors Without Borders USA Inc", "city": "New York", "state": "NY" } ], "took_ms": 24 } # GET /v1/data/suggest?q=care%20usa&size=1 { "suggestions": [ { "ein": "13-1685039", "name": "Cooperative for Assistance and Relief Everywhere Inc", "matched_name": "Care USA", "city": "Atlanta", "state": "GA" } ], "took_ms": 21 }
| Field | Type | Description |
|---|---|---|
suggestions | object[] | Up to size rows, best match first. Empty when nothing matches. |
suggestions[].ein | string | Hyphenated EIN. Pass it to /v1/data, /v1/data-pro or /v1/verify. |
suggestions[].name | string | The organization’s name, the same name POST /v1/data returns. It does not change with what was typed. |
suggestions[].dba_name | string | Present when the organization does business under another name: the same dba_name POST /v1/data returns. |
suggestions[].matched_name | string | Present when the text matched a name the organization is widely known by, such as an abbreviation or a common form of its name (MSF, Care USA), rather than name or dba_name. Show it so users can see why the row matched, for example as “Also known as”. |
suggestions[].city, suggestions[].state | string | null | Location, cased for display. |
suggestions[].close_match | boolean | Present, and true, only on rows found by a close spelling (harvrad). They come only when 3 or fewer organizations match the text as typed, and always after those rows. Show them as their own group, for example under “Did you mean”. |
took_ms | integer | Server time for the request. |
Which name to show first is up to you. A common layout leads with dba_name when the typed text starts it, otherwise name, and shows the other name on a second line.
How matching works
- A word matches the start of a word in any of the organization’s names: its legal name, the names it does business as, and short names it is widely known by. Typing more only narrows the regular rows: one more letter never adds an organization to them.
- Names that start with the text come first. Among equally good matches, larger organizations come first.
- Suggestions are not an eligibility check. An organization whose tax exemption was revoked can still appear, ranked below an equally good active match. Confirm with
GET /v1/verifybefore you grant. - Names are updated daily.
Calling it from a search box
- Wait about 150 ms after the last keystroke before calling, and ignore any response that arrives after a newer one.
- Call it from your server, not the browser, so your API key stays private.
Errors
See Error Codes; the suggest-specific ones are listed there under GET /v1/data/suggest.
Data Pro Response: Top-Level
The top-level blocks of the deep single-org response served by GET /v1/data-pro. (For the Data API's lean search records, see Data API Response.) Customers receive the complete field-by-field reference; see Full field reference.
Form 990-N (e-Postcard) filers. Organizations that have only ever filed the postcard 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.
| Field | Type | Description |
|---|---|---|
ein | string | Hyphenated EIN (e.g. "53-0196605"). |
tax_year | integer | Fiscal year of the filing returned. |
return_type | string | "990", "990EZ", "990PF", or "990N" (e-Postcard). |
data_availability | object | Present only when return_type is "990N". {financials: false, reason: "…"}. The IRS collects no financial data on the e-Postcard, so those sections are empty because nothing was filed, not because we are missing it. The key’s absence means a full return, where empty financials would be a real gap. |
filing_date | string (ISO date) | Date IRS received the filing. |
organization | object | Identity, address, NTEE code, mission, formation year, total employees/volunteers. |
financials | object | Summary, revenue breakdown, expenses, balance sheet, and (Pro only) detail sub-object. |
trends | object | Up to 6 years of annual data + computed CAGRs + trend labels. |
compensation | object | People array, by-role highlights, per-employee average, peer benchmark cubes. |
funding_sources | object | Grants received: total_unique_grantmakers, grantmakers[], annual_totals[]. |
grants_made | object[] | Outbound grants (Schedule I), top 50 for the year. Data Pro only, always present. |
programs | object[] | Program service accomplishments. |
governance | object | Board structure, policies, meeting minutes flags. |
fundraising | object | metrics, events[], contractors[], activity_methods, licensed_states. |
financial_profile | object | Liquidity, revenue concentration (HHI), operating sustainability, expense composition New. All with peer benchmarks. |
related_organizations | object[] | Schedule R-style related entities. |
foreign_activities | object[] | Schedule F Part I region-level summary. |
lobbying_and_political | object | Schedule C summary. |
contributor_summary | object | Always an empty object. The IRS redacts Schedule B in public filings, so no contributor data is available to return. |
donor_advised_funds | object | null | DAF flags + balances (if Schedule D.I present). |
conservation_easements New | object | Schedule D Part II. Always-on; present only when held. |
art_collections New | object | Schedule D Part III. Always-on; present only when held. |
private_foundation New | object | Form 990-PF metrics. Always-on; 990-PF filers only. |
hospitals New | object | Schedule H. 501(c)(3) hospital filers only; always present for them on Data Pro. |
disclosures Pro | object | Schedules K/L/M/N/F/A/O. Data Pro only, always present. |
registration Pro | object | IRS BMF + Pub 78 + California AG/FTB status, joined at request time. |
data_freshness | object | filing_source, most_recent_tax_year, filing_date, last_filing_date, years_available[], includes_applied[]. |
Full field reference
Data Pro returns about 410 fields across the blocks above: line-item financials, Schedule detail, governance and compensation records, registration status, and peer benchmarks across size, sector and geography. The complete field-by-field reference, with definitions and benchmark methodology, is provided to customers, and to prospects under NDA. Contact sales@givalgo.ai.
data_freshness
| Field | Type | Description |
|---|---|---|
filing_source | string | Always "IRS TEOS e-file". |
most_recent_tax_year | integer | Latest filing year ingested for this EIN. |
filing_date | string | Filing date of the response year. |
last_filing_date | string | Most recent filing date for this EIN overall. |
years_available | integer[] | All tax years on file. |
includes_applied | string[] | Diagnostic: which internal bundles applied to this response (empty on base). |
Error Codes
| Status | Error | Meaning |
|---|---|---|
| 400 | MISSING_EIN | The ein query param is required. |
| 400 | INVALID_EIN | EIN is not 9 digits / not parseable. |
| 400 | INVALID_TAX_YEAR | tax_year must be a 4-digit integer. |
| 403 | IP_NOT_ALLOWED | The calling IP is not on the allowlist for this API key. |
| 403 | ENDPOINT_NOT_LICENSED | Your key reached an endpoint your agreement does not include; access is scoped per product. The body names the product you asked for (requested_product) and lists the ones your key holds (licensed_products), so you can tell a licensing boundary apart from a bug without contacting us. |
| 403 | Forbidden | Missing or invalid x-api-key header. |
| 404 | NOT_FOUND | No filing of any kind for this EIN, including Form 990-N (or none for the requested year). Response includes years_available[], which counts postcard years too. Data Pro only. |
| 500 | INTERNAL_ERROR | Unexpected server error. request_id included for support tickets. |
Search-specific errors (POST /v1/data)
| Status | Error | Meaning |
|---|---|---|
| 400 | EMPTY_REQUEST | Body needs search_terms, filters, or both. |
| 400 | PROMPT_NOT_SUPPORTED | prompt is not accepted. Send search_terms and filters instead; filters.organization.causes takes a cause in plain English. |
| 400 | UNKNOWN_FIELD | Unrecognized field; the message names the JSON path. |
| 400 | UNSUPPORTED_FILTER | A recognized field from other nonprofit data APIs that we don't carry; the hint names the nearest equivalent. |
| 400 | INVALID_FILTERS / INVALID_RANGE / INVALID_SORT / INVALID_SIZE / INVALID_PAGINATION / INVALID_SEARCH_TERMS | Field-level validation; the message says which field and why. |
| 400 | MULTI_EIN_NOT_SUPPORTED | One EIN per request (multi-EIN monitoring is planned). |
| 429 | CAUSE_QUOTA_EXCEEDED | The request has more never-resolved causes phrases than the key has left today. Nothing ran. Body carries new_causes (the phrases that would count) and causes_quota; Retry-After header set. Phrases resolved before, and NTEE codes, still work. |
| 502 | UPSTREAM_ERROR | Search engine temporarily unavailable; retry. |
| 504 | UPSTREAM_TIMEOUT | Search engine timeout; retry. |
Suggest-specific errors (GET /v1/data/suggest)
| Status | Error | Meaning |
|---|---|---|
| 400 | UNKNOWN_PARAMETER | A query parameter other than q, size, state and session_token. |
| 400 | INVALID_QUERY | q is longer than 100 characters. |
| 400 | INVALID_SIZE | size is not a whole number from 1 to 10. |
| 400 | INVALID_STATE | state is not a two-letter code. |
| 400 | INVALID_SESSION_TOKEN | session_token is not 8 to 64 letters, digits, hyphens or underscores. |
| 503 | SUGGEST_UNAVAILABLE | Suggestions are temporarily unavailable; retry. |
FaithVerify API
Verify a church from its name, city and state. No EIN required; for most churches, none exists.
POST /v1/faith-verify
{"name": "First Baptist Church", "city": "Dallas", "state": "TX"}
Every response carries a verdict with a plain-language recommendation, and the evidence behind it.
Why churches are different
A church is exempt under 501(c)(3) automatically, without applying, and is not required to file a Form 990 or appear in Publication 78 (IRC §508(c)(1)(A)).
Two consequences shape this API:
- The EIN often does not exist.
GET /v1/verifystarts from one, so it cannot answer for a congregation that never applied. - Absence from IRS lists is normal, not suspicious. A church missing from Publication 78 has not failed anything: it was never required to be there. Treating that absence as a negative signal would decline legitimate churches at scale.
So FaithVerify gathers evidence in layers and tells you which layer answered, rather than reducing everything to present-or-absent in one list.
Quickstart
curl -X POST https://api.givalgo.ai/v1/faith-verify \
-H "x-api-key: $GIVALGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "First Baptist Church", "city": "Dallas", "state": "TX"}'
A church the IRS or a denomination vouches for answers immediately:
{
"triage": { "status": "resolved", "matched_in": ["denominational_directory", "irs_bmf"] },
"irs": { "ein": "75-0800696", "irs_classified_church": true },
"verdict": { "status": "ELIGIBLE", "deductible": true, "basis": "group_exemption" }
}
If the records do not settle it, you get 202 and a poll URL instead. See Website evidence.
Verify a church
POST /v1/faith-verify
| Field | Required | Notes |
|---|---|---|
name | yes | As you know it. Abbreviations resolve: “First UMC” finds “First United Methodist Church”. |
city | yes | |
state | yes | Two-letter code (OH) or the full name (Ohio), either case. Send DC or District of Columbia for the District; Washington alone is the state. |
street | no | Needed only when a call returns needs_street. Supplying it up front never hurts. |
skip_website_evidence | no | Stay synchronous: never return 202, take the record answer as final. |
force_refresh | no | Re-crawl even if a review from the last 30 days exists. |
Poll a website review
GET /v1/faith-verify/{job_id}
Returns the complete verification (triage, irs, denominational, website_evidence and verdict), not just the crawl. You never hold half an answer.
The record-layer findings are exactly as they stood when the job was created, so the verdict stays pinned to the facts that produced it.
Cadence: most reviews finish in under two minutes. Poll first at ~20s, then every 10s, and give up after 5 minutes.
status is queued or running while it works, then done, including when the review was inconclusive, which is a result rather than a failure. failed means our own error and is safe to retry.
Job ids are scoped to the key that created them; polling another key’s returns 404.
Three outcomes
All three return 200. Branch on triage.status.
| Status | Meaning | What to do |
|---|---|---|
resolved | We know which church you mean. | Read the verdict. |
needs_street | We cannot pin the church to one record from the name alone: either several match, or the closest listing names a town rather than a congregation and does not carry your name. | Re-send with street; triage.candidate_addresses lists what we hold. When reason is location_only_listing the website review runs too, so a street adds a record match rather than identifying the church. |
not_found | No record matched: not in the IRS BMF, not in the 28 denominational directories, in your city or within 5 miles of it. It is not a statement that the church does not exist. | Nothing: this is what launches the website review. If that review comes back empty too, check the spelling or try the name as it appears on the building. |
A name that identifies a town is not an answer. Some denominations file congregations under the place rather than a congregation name: an LDS ward listed as Batesville, a Kingdom Hall listed as its address. Such a record is only returned when you wrote that same name; ask about a different church in that town and you get needs_street with the addresses to choose from, never the ward.
A church one town over is still found. Congregations routinely file with the IRS under a neighbouring municipality, so matching your city exactly reports real churches as missing: A Restoration Church, Glenview IL is in the IRS file under Northbrook, 3.8 miles away. When your city matches nothing, we search the towns within 5 miles and return triage.nearby_city_match naming the city we actually found it in and the distance. A church in the town you named always wins; the radius is only a fallback.
When we hold no record of your church, the website review runs. That is what it is for. We ask you to choose an address only when the choice is real: several congregations we genuinely cannot tell apart, or a record carrying your church's name at a different address. If nothing we hold matches the name you gave, you get not_found and a website review, not a list of other people's churches.
needs_street and not_found are answers, not errors. Your request was well-formed; we could not settle it. A 4xx would put them in the same bucket as a malformed body and invite a retry instead of an action.
A city holds six Kingdom Halls and four St Mary’s. When a name identifies a denomination and a town rather than a congregation, guessing would produce a confidently wrong answer, so we ask instead.
The three evidence layers
Gathered strongest first, stopping at the first that answers. An answer from the IRS is not improved by reading a website.
| Layer | Source | Response |
|---|---|---|
| 1 · IRS | EO BMF → Publication 78, directly or via the parent’s group exemption. Includes revocation and OFAC screening. | Immediate |
| 2 · Denominational | Current congregation directories published by denominations. A listing shows the congregation exists and is affiliated. | Immediate |
| 3 · Website | The five characteristics the IRS uses to recognise a church, read from the church’s own site. | 202 → poll |
Many churches are covered by a denomination’s group exemption rather than holding their own determination. We follow that link: a subordinate not listed in Publication 78 is checked against its parent, and irs.group_exemption reports the parent’s EIN and standing.
Website evidence
Reached only when neither record layer settles the question. We find the church’s website, confirm it belongs to that congregation, read it, and grade five criteria.
| Criterion | What comes back |
|---|---|
ministers | Organized leadership: every named leader or officer with their title, plus how leadership is constituted. Satisfied by ordained clergy or a named governing body: the IRS criterion is "an organization of ordained ministers" or "a definite and distinct ecclesiastical government", and many traditions ordain nobody. |
doctrine | The denomination claimed, the topics the statement covers, and the church’s own wording of what it believes. |
services | Each regular gathering as day, time, and the schedule line exactly as printed. |
calendar | Named events with dates, recurring programmes, and what the church’s month looks like. |
contact | Street address, city, state, ZIP, phone and email. |
Each carries a status, a summary, and evidence_url (the page the finding came from), so you can check it yourself.
Every value is verified against the page
Anything you might act on (an address, a phone number, an email, a service time, a person’s name) is dropped if we cannot find it on the page we read, and listed in facts.unverified_dropped. That distinction matters: “the church publishes no phone number” and “a phone number was produced that we could not confirm” are different facts.
Doctrinal statements are kept with verbatim: false when they are close paraphrases rather than quotes.
not_found is not fail
A criterion is not_found when no page addressing it was available to read, and fail when the topic was covered and the characteristic was genuinely absent. A church with a thin website has not failed the test.
What a denominational listing means, and what it does not
A listing is evidence the congregation exists and is affiliated with that body. It is not confirmation of IRS group-exemption coverage. Where coverage exists we report it from the organisation’s own IRS record, under irs.group_exemption, never inferred from the directory, and never estimated from how the denomination usually files.
What the sanctions screening covers
The organization’s name, and nothing else. A church does not file a Form 990 (that is the premise of this endpoint), so we hold no officer or director records for one. Rather than return an empty leadership_screening block that reads as “we checked the leadership and found nothing”, the block is omitted and sanctions_screening.scope says what was covered.
Leaders named on the church’s own website appear under website_evidence and have not been screened. If a church does have officers on record, the leadership_screening block is present and real.
We do not score it
evidence.tally counts the five criteria by status: pass, partial, fail, not_found. That is all it does. There is no threshold and no pass mark, because the IRS sets none: its own guidance says “there is no minimum number of criteria an organization must meet” and warns that a numerical approach “might unconstitutionally favor established churches at the expense of newer, less traditional institutions”. We collect the evidence; the rule for acting on it is yours.
Results are cached for 30 days per church. Two requests arriving together share one crawl.
Limited release
FaithVerify is enabled per API key while we finish validating it against real queries. If your key does not have access yet, every FaithVerify route answers 403 with error: "FORBIDDEN". Your key is fine, this endpoint just is not switched on for it. Email support@givalgo.ai to request access. Every other endpoint is unaffected.
Verdicts
The status says where the church was found, not how eligible we judge it. What was found lives in deductible, basis and message.
| Status | Meaning |
|---|---|
FOUND_IRS | An IRS record: Publication 78, a group exemption whose parent is listed, or the church classification itself. |
REVOKED_ON_IRS | The IRS has auto-revoked the organization’s exempt status. Also an IRS record, but kept separate because it is the one finding that means no; it overrides every positive basis, and carries deductible: false. |
FOUND_DENOMINATIONAL_DIRECTORY | The congregation appears in a denomination’s own current directory, and nowhere in IRS data. |
FOUND_WEBSITE_EVIDENCE | No record in either, so we read the church’s own website against the five IRS characteristics. |
UNDETERMINED | We looked everywhere and came back empty: no IRS record, no directory listing, and either no website found or a review that produced nothing. Not the same as ineligible: no evidence, not evidence against. |
Revocation is the exception, and it has its own status. Everything else the status tells you is where we found the church; deductible and basis tell you what we found. Two cases are 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", and a group subordinate whose own row contradicts the group listing returns basis: "group_exemption_contradicted", also with deductible: null. 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 the church's own Publication 78 listing settles the question, deductible stays true and the contradictory code is reported next to it in caveat. Where the answer rests on a group ruling or on the church classification alone, the code is not outvoted: deductible becomes null and the basis is group_exemption_contradicted or irs_church_classification_contradicted. /v1/verify answers the same way for the same organization, with CAUTION.
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
Send an EIN and get back a Diligence Brief: the same brief grantmakers generate on Givalgo Discover, as JSON and as a branded PDF.
A brief reads the organization's Form 990 filings, runs the compliance checks of Verify (IRS status, Pub 78, auto-revocation, OFAC on the organization and its leaders, California registries), benchmarks it against its peers, reads its website and checks what the website says against what it filed. It leads with at most three findings that matter, and ends with the questions worth asking the organization.
verified; what the organization says on its own website is self_reported; the website checked against the filings is reconciled; the summary, the wording of each finding and the questions are written by AI and marked synthesis. The AI works only from the facts in the brief. See Provenance and AI.
A brief is decision support, not a score, rating or recommendation to donate.
Authentication
Send your API key in the x-api-key header on every request, exactly as with the other Givalgo APIs.
curl -X POST https://api.givalgo.ai/v1/research \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{"ein": "12-3456789"}'
Don't have a key? Contact support@givalgo.ai.
Quickstart
Step 1: request the brief.
curl -X POST https://api.givalgo.ai/v1/research \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{"ein": "12-3456789"}'
{
"brief_id": "br_Q2x9mVt4cL0aZ7pK1nYw3g",
"status": "running",
"ein": "12-3456789",
"cached": false,
"poll": "/v1/research/br_Q2x9mVt4cL0aZ7pK1nYw3g",
"pdf": "/v1/research/br_Q2x9mVt4cL0aZ7pK1nYw3g/pdf",
"expected_ready_in_seconds": 180,
"quota": {"plan": "pro", "period": "month", "limit": 50, "used": 3,
"remaining": 47, "resets_at": "2026-11-01T00:00:00Z"}
}
Step 2: poll every 15 seconds until status is done. A new brief takes about 2 minutes, and most finish within 4.
curl https://api.givalgo.ai/v1/research/br_Q2x9mVt4cL0aZ7pK1nYw3g \
-H "x-api-key: YOUR_API_KEY"
{
"brief_id": "br_Q2x9mVt4cL0aZ7pK1nYw3g",
"status": "done",
"ein": "12-3456789",
"requested_at": "2026-10-06T15:02:11Z",
"completed_at": "2026-10-06T15:04:03Z",
"pdf": "/v1/research/br_Q2x9mVt4cL0aZ7pK1nYw3g/pdf",
"brief": {
"name": "Example Community Food Bank",
"verdict": {
"standing": {"state": "items_to_confirm"},
"org_one_liner": "A county food bank distributing groceries through 40 partner pantries.",
"at_a_glance": {"compliance": "ELIGIBLE", "revenue_latest": 4210000,
"months_of_cash": 5.2, "program_pct": 0.91},
"provenance": "synthesis"
},
"glaring_flags": [
{"id": "funder_concentration", "severity": "medium", "category": "funding",
"title": "Concentrated institutional funding",
"detail": "One funder supplies about 60% of the grants tracked in IRS filings.",
"provenance": "verified"}
],
"question_set": [
{"question": "How would you replace your largest funder's support if it ended?",
"why": "One funder supplies most institutional grants.",
"category": "financial", "basis": "desk_limit"}
],
"compliance": {"verdict": "ELIGIBLE", "deductible": true, "provenance": "verified"},
"financials": {"latest": {"tax_year": 2024, "total_revenue": 4210000}, "provenance": "verified"},
"website_research": {"fetch_status": "ok", "provenance": "self_reported"}
}
}
The example is shortened; the full shape is in What a brief contains.
Step 3 (optional): download the PDF.
curl https://api.givalgo.ai/v1/research/br_Q2x9mVt4cL0aZ7pK1nYw3g/pdf \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/pdf" -o brief.pdf
Request a brief
POST /v1/research
| Field | Type | Required | Description |
|---|---|---|---|
ein | string | Yes | The organization's EIN, with or without the hyphen. Send it as a string: a JSON number drops leading zeros. |
force_refresh | boolean | No | Write a new brief even when one from the last 30 days is on file. Counts against your quota. If a brief is already being written, you join that one instead. |
Any other field is refused with 400 UNKNOWN_FIELD. Briefs written against a funding mandate are not available through the API yet (400 MANDATE_NOT_AVAILABLE).
Always returns 202 with a brief_id. status is running while the brief is being written, or done when one was already on file ("cached": true). The brief is never in this response, so always poll; done only means your first poll returns it.
| Field | Meaning |
|---|---|
brief_id | Your id for this request. Scoped to your API key. |
cached | true when a brief from the last 30 days answered this request. Only a request that starts a new brief counts against your quota. |
poll, pdf | The paths to poll and to download the PDF. |
expected_ready_in_seconds | A guide for your first poll. 0 when the brief is already written. |
quota | Your monthly allowance as of this response: limit, used, remaining, resets_at. See Quota and caching. |
Poll a brief
GET /v1/research/{brief_id}
status | Meaning |
|---|---|
running | Being written. Poll again in 15 seconds. |
done | The full brief is in brief, and the PDF is available. |
failed | We could not write it. message says what to do. A failed brief does not count against your quota; request it again. |
A brief_id always returns the brief it was issued for, even after the organization's brief is rewritten for someone else. Brief ids are scoped to the API key that created them; another key's id returns 404 BRIEF_NOT_FOUND. We keep your requests for 12 months.
Download the PDF
GET /v1/research/{brief_id}/pdf with Accept: application/pdf
The brief as a branded PDF, the same document Discover users download. Available once polling returns done; before that you get 409 BRIEF_NOT_READY. Downloading does not count against your quota.
Errors
| Error | Status | Meaning |
|---|---|---|
MISSING_EIN, INVALID_EIN | 400 | No EIN, or not nine digits. Send the EIN as a string. |
UNKNOWN_FIELD, INVALID_FIELD | 400 | A field this endpoint does not take, or force_refresh that is not true or false. |
MANDATE_NOT_AVAILABLE | 400 | Briefs written against a funding mandate are not available through the API yet. |
BRIEFS_NOT_IN_PLAN | 403 | Your plan does not include briefs. Contact sales@givalgo.ai. |
NO_FILINGS | 404 | We hold no Form 990 filings for this EIN, so there is nothing to brief. Organizations that do not file can still be checked with Verify. |
BRIEF_NOT_FOUND | 404 | No brief with that id for your API key. |
BRIEF_NOT_READY, BRIEF_FAILED | 409 | PDF only: poll until done, or request a new brief. |
BRIEF_QUOTA_EXCEEDED | 429 | This request needs a new brief and your monthly allowance is used up. Carries Retry-After and a quota object. Briefs already on file are still served. |
UPSTREAM_TIMEOUT, UPSTREAM_ERROR | 504, 502 | Our research service did not answer. Retry; the request did not count against your quota. |
What a brief contains
Sections arrive in the order a reader meets them in the PDF: the answer first, then the evidence.
| Section | Provenance | What it holds |
|---|---|---|
verdict | synthesis | standing.state (below), a one-sentence org_one_liner, and at_a_glance: compliance status, latest revenue, months of cash, program share, number of institutional funders. |
exec_summary | synthesis | headline and narrative. |
glaring_flags | per flag | At most three findings that matter: id, severity (high, medium, low), category, title, detail, provenance. |
appendix_flags | per flag | Every other finding, same shape. |
question_set | synthesis | Questions to put to the organization, each with why, a category, and a basis: divergence (the website and the filings disagree) or desk_limit (public records cannot answer it). |
compliance | verified | The Verify answer for this EIN when the brief was written: verdict, deductibility, Pub 78, revocation history, OFAC screening of the organization and its leaders, California registries. |
financials | verified | One row per filed year, the latest year, and computed trends. |
funders | verified | Grants received, from the funders' own IRS filings, with the top funder's share. Supporters named on the website are checked against them (reconciled). |
programs, program_reconciliation | verified, reconciled | Programs as filed, matched against the programs the website describes. |
governance, leadership | verified | Board size and independence, governance policies, the highest-paid person and their compensation. |
peers | verified | Each metric with the organization's value and its peer group's 25th percentile, median and 75th percentile, and the peer group (cause, size and state). |
website_research | self_reported | What the website states (mission, leadership, impact claims, named supporters, board, staff, audit firm), each with its source_url; each claim checked against the 990 in audit_findings; and which annual reports, impact reports and audited statements we found. |
verdict.standing.state
Computed from the compliance checks and the glaring findings, never by AI.
| Value | Meaning |
|---|---|
compliance_issue | Revoked, or not eligible for tax-deductible gifts. |
material_items | A sanctions screening returned a potential match, or a high-severity finding. |
items_to_confirm | Findings worth confirming with the organization. |
clear | No glaring issues in the verified data. |
When we could not read the website (website_research.fetch_status other than ok or partial), the brief stands on the filings alone. On a private foundation (org.return_type 990PF), governance and peer sections can be empty: the brief is designed for public charities.
Provenance and AI
provenance | Source |
|---|---|
verified | IRS filings, Pub 78 and revocation lists, Treasury OFAC, California registries. |
self_reported | The organization's own website, as it states it. |
reconciled | The website checked against the filings. |
synthesis | Written by AI from the facts in this brief. |
Quota and caching
One brief is kept per organization, fresh for 30 days, and shared: whoever asked first, a request inside those 30 days gets the same brief at no cost to your quota. The same is true while a brief is already being written. generated_at in the brief says when it was written; force_refresh: true writes a new one.
Each API key has a monthly allowance of new briefs, shown on every request as quota. It resets on the first of the month (UTC).
| Counts | Does not count |
|---|---|
A request that starts a new brief, including force_refresh | A brief already on file ("cached": true), joining one being written, a brief that failed, polling, the PDF |
At the limit, briefs already on file are still served; only a request that needs a new brief returns 429 BRIEF_QUOTA_EXCEEDED, with Retry-After. For a larger allowance, contact sales@givalgo.ai.
Organizations API
List every nonprofit in a place (a county, a city, a ZIP, or a whole state) with a lean identity-and-location record and a cursor that does not run out.
Built for the case where you need the whole set rather than the best matches: a community foundation mapping its service area, a directory keeping a county in sync, a dashboard counting nonprofits per county.
When to use this instead of the Data API
| /v1/organizations Organizations | /v1/data Data | |
|---|---|---|
| Question | “Give me every org in here” | “Which orgs best match this?” |
| Ordering | Stable, by EIN | By relevance |
| Depth limit | None: page until has_more is false | 2,000 results |
| Record | Lean: identity + location | Rich: financials, mission, contact, NTEE detail |
| County filter | Yes | No |
| Free-text search | No | Yes |
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",
"ntee_inferred": null,
"subsection": "03"
}
],
"count": 100,
"next_cursor": "010610009",
"has_more": true
}
Then walk the cursor until has_more is false:
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&subsection=03&limit=100&cursor=010610009"
List organizations
One endpoint. view chooses what comes back: organizations (the default), counts, or the change feed. Every filter below means the same thing in all three.
view | Returns |
|---|---|
list (default) | Organizations. |
facets | Counts along one dimension. Needs by. |
changes | The change feed: events, not organizations. |
A scope is required: at least one of state, county, city or zip. Enumerating the whole Business Master File is a bulk export rather than a page; contact us if that is what you need.
Typical latency is ~90ms state-wide and ~180ms for a county, regardless of how large the county is.
A city is not a metro
These three scopes answer three different questions about “Houston”:
| Scope | Organizations |
|---|---|
city=HOUSTON | 15,977 |
county=Harris | 20,207 |
| The nine-county Houston metro | 31,951 |
The city field is the municipality the organization files under, which is not the same as the area it serves. For a metro, request each county and merge.
Filters
All four endpoints take the same filter vocabulary, so learning one teaches you the rest.
| Parameter | Type | Description |
|---|---|---|
state | string | Two-letter code. Required when county is given. |
region | string | A named region: bay area, midwest, north florida… Counts as a scope on its own. See Regions. |
county | string | One county or a comma-separated list of up to 25. With or without the County suffix. Louisiana parishes and Alaska boroughs match under their census names. Requires state. |
city | string | City as the IRS records it, matched case-insensitively. |
zip | string | 5-digit ZIP. A ZIP+4 is accepted and truncated. |
ntee | string | Comma-separated NTEE codes, matched by prefix. B matches every B*; B21 matches B21. |
ntee_source | string | irs (default), inferred, or inferred_soft. See below. |
subsection | string | Comma-separated 501(c) subsections. 03, 3, 501c3 and 501(c)(3) all mean the same thing. |
view | string | list (default), facets, changes. |
ruling_since | string | YYYY-MM. Only organizations ruled at or after this month. |
sort | string | ein (default), revenue_desc, revenue_asc, assets_desc, name_asc, ruling_desc. See Sorting. |
limit | integer | 1–2000, default 250. Bigger is markedly cheaper per organization and costs fewer calls (see Sorting and Pagination). |
cursor | string | Opaque. Pass back the previous response's next_cursor. |
Regions
Rather than naming counties, ask for a region. region uses the same vocabulary as Ask on Givalgo Discover, so “Bay Area” means the same set of organizations in both products. It counts as a geographic scope on its own; no state needed.
Multi-state regions (these constrain the state):
deep south, east coast, great lakes, gulf coast, mid-atlantic, midatlantic, midwest, new england, northeast, northwest, pacific northwest, rust belt, rustbelt, southeast, southwest, sun belt, sunbelt, tri-state, tristate, west coast
Sub-state regions (these constrain state and city):
| State | Regions |
|---|---|
| CA | bay area, central coast, central valley, nocal, nor cal, norcal, northern california, so cal, socal, southern california |
| FL | central florida, fl panhandle, florida panhandle, ne florida, north florida, northeast florida, northern florida, northwest florida, nw florida, se florida, south east florida, south florida, south west florida, southeast florida, southern florida, southwest florida, space coast, sw florida, tampa bay, tampa bay area, tampa bay region, treasure coast |
| NY | upstate, upstate new york, upstate ny |
| TX | central texas, dallas fort worth, dfw, east texas, greater houston, gulf coast texas, houston metro, metroplex, north texas, south texas, texas gulf coast, texas triangle, west texas |
Hyphens, underscores and capitalisation are ignored, so Bay Area, bay-area and BAY_AREA are the same request.
How a sub-state region is defined
A sub-state region matches its counties OR its cities: a union, not an intersection.
Counties are the primary definition: socal is the ten Southern California counties (Imperial, Kern, Los Angeles, Orange, Riverside, San Bernardino, San Diego, San Luis Obispo, Santa Barbara, Ventura). The city list is kept as a safety net, because 7.9% of organizations have a ZIP the US Census crosswalk cannot place and so carry no county at all (1,566 of them in Houston alone). Matching on counties only would silently drop them.
Regions deliberately overlap where geography does: Santa Barbara and Ventura are both socal and central coast; Kern is both socal and central valley; Brevard is both central florida and space coast. A nonprofit in Ventura is honestly in both, and forcing a partition would mean picking a loser.
Passing a state the region does not contain returns 400 REGION_STATE_CONFLICT rather than an empty page, because 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: the county form finds more, because county lines also catch the smaller towns nobody lists by name. Use region to match how Ask on Givalgo Discover 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
Many registered nonprofits were never assigned an NTEE code by the IRS. We infer codes for many of them, and ntee_source decides whether the ntee filter considers those:
| Value | Matches |
|---|---|
irs (default) | Only codes the IRS assigned. Authoritative. |
inferred | Also codes we assigned to uncoded organizations with high confidence. |
inferred_soft | Additionally our lower-confidence assignments. |
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.
ntee_source only decides what the filter matches and what fills ntee_code. Whatever you pass, a record the IRS never coded still carries our guess in ntee_inferred, so a full load gets every inferred code without a second pass.
Note: POST /v1/data folds inferred codes into its NTEE matching with no way to opt out. The same ntee=B therefore returns different counts on the two endpoints. This endpoint defaults to IRS-only on purpose.
The record
| Field | Description |
|---|---|
ein | Hyphenated 9-digit EIN. |
name | Legal name as the IRS records it. |
dba_name | The organization's doing-business-as name: the newest one it filed on a return, else the IRS's secondary name for it when that is a name the organization goes by. The same value /v1/verify and /v1/data-pro return as also_known_as, display-cased (“Doctors Without Borders USA Inc”). Sparse, because most organizations have none. |
street | Street address from the Business Master File. |
city, state, zip | As filed. zip may be ZIP or ZIP+4. |
county | Derived from the ZIP via the US Census crosswalk. null where the ZIP is not in that file. |
ntee_code | NTEE code. Read together with ntee_source. |
ntee_source | irs, inferred, inferred_soft, or null when the organization has no code at any tier. |
ntee_inferred | Our inferred cause code for an organization the IRS never coded, on every record whatever ntee_source you passed. The same object /v1/data returns: code, description, confidence, precision, secondary, tier (gated or soft; check it before you trust the code) and method. null whenever the IRS assigned a code, and when we have no guess. It never changes ntee_code or what the ntee filter matches. |
subsection | 501(c) subsection, zero-padded. 03 is a public charity. |
display_name | How the organization is shown on Discover: a hand-set name, else name cased and repaired (“Hope Center Inc”; a word the IRS split or cut completed). Additional: name itself is unchanged. The same value /v1/verify and /v1/data-pro return. null until the nightly refresh reaches the organization. |
display_street | street, cased for display. |
display_city | city, cased for display. |
Two forms of every string
name, street and city come back exactly as the IRS publishes them, which means ALL CAPS: HOPE CENTER INC, HOUSTON, 3618 STASSEN ST. That is the stable form, and the one to match against your own records.
Alongside each is a display-cased twin:
| Verbatim | Display |
|---|---|
name: HOPE CENTER INC | display_name: Hope Center Inc |
street: 3618 STASSEN ST | display_street: 3618 Stassen St |
city: HOUSTON | display_city: Houston |
These are additional fields, not replacements. Match on the verbatim ones, render the display ones. Nothing you already integrated against changes.
Casing is computed once per Business Master File reload and shared with the rest of the platform, so the same organization can never appear differently in two places. It is a hard problem (YMCA is an acronym, KNIGHTS is a word, CT is Connecticut in a name and Court in an address), so a display field is null rather than wrong when we have not computed one yet.
Sorting
Default is ein, a stable enumeration order, which is what you want when mirroring a place. Add sort to rank instead.
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&subsection=03&sort=revenue_desc&limit=1000"
That is the 1,000 largest charities in Houston: Memorial Hermann Health System, Methodist Hospital Group, Texas Children's Hospital, and on down to about $700K.
| Value | Meaning |
|---|---|
ein (default) | By EIN ascending. Stable; use it to enumerate. |
revenue_desc | Largest first. |
revenue_asc | Smallest first. Read the warning below. |
assets_desc | Most assets first. |
name_asc | Alphabetical. |
Many organizations have no revenue figure
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 organizations 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 every zero-revenue organization 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: the maximum page size takes a fraction of the calls and time of the default.
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, so large pages are much smaller on the wire.
If your client does not send the header you still get plain JSON, byte for byte as before.
Treat the cursor as opaque: a value we did not issue returns 400 INVALID_CURSOR. Ordering is stable (by EIN), so a page never repeats or skips a record while you walk.
Counts (view=facets)
The dashboard question (“how many nonprofits in each county in Texas?”) answered without transferring every record.
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 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, because counting every county in the country reads the entire dataset.
Newly formed: a filter, not a view
“Who just formed in my service area?” is not a separate view; it is a filter (ruling_since) and an ordering (sort=ruling_desc) over the same rows. Every record carries a ruling_month.
Because it is a plain filter it composes with any sort, which a dedicated endpoint could not do. The largest organizations formed since 2015:
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&ruling_since=2015-01&sort=revenue_desc"
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?state=TX&county=Harris&ruling_since=2026-01&sort=ruling_desc"
since is a month, not a day, because that is the resolution the IRS publishes.
Two things this date is not
- A determination can be backdated, so a ruling month is not the month the organization appeared in our data.
- The IRS Business Master File reloads monthly, so “new since last month” can lag reality by up to that reload.
It is still the right field: it is the only date the IRS publishes for this, and it never changes. Re-running the same query tomorrow gives the same answer for the same period.
The cursor here is a composite YYYYMM:EIN, because ordering is by ruling month and many organizations share one. Treat it as opaque, like any other cursor.
Changes (view=changes)
An append-only feed of IRS status changes (revocations, and removals from and returns to Publication 78), 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, since the cursor already bounds the response.
curl -H "x-api-key: $GIVALGO_API_KEY" \
"https://api.givalgo.ai/v1/organizations?view=changes&state=TX&county=Harris&since_id=448817"
| Event type | Meaning |
|---|---|
revocation_added | The organization appeared on the IRS auto-revocation list. |
pub78_dropped | The organization left IRS Publication 78. |
pub78_restored | It returned to Publication 78 after a drop. |
Filter with event_type, comma-separated: event_type=revocation_added,pub78_dropped. For an organization's current status, including an OFAC screen, use the Verify Endpoint.
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
The IRS republishes its revocation list and Publication 78 about once a month. A daily poll sees nothing for weeks and then 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
| Field | Coverage |
|---|---|
ein, name, street, city, state, zip, subsection | Every record, from the IRS Business Master File. |
county | Derived from ZIP via the US Census crosswalk; null where the ZIP is not in that file. |
ntee_code | IRS-assigned where the IRS assigned one; ntee_source=inferred fills most of the rest. |
dba_name | Only where the organization filed a DBA or the IRS records a secondary name it goes by. |
What the list contains
The universe is the IRS Business Master File: organizations registered with the IRS as tax-exempt. The file also keeps organizations whose exemption has since been revoked, so this endpoint tells you an organization exists and is registered, not that 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 checks for new filings daily, and the IRS publishes them in batches every few weeks.