Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/plinth-us-grants-data:plinth-us-grants-data-batch-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Plinth Grants Batch API
summary: US foundation and nonprofit funding data from public IRS Form 990 and 990-PF filings.
description: 'A read API over the US grantmaking universe, read from public IRS Form 990, 990-EZ and 990-PF e-file filings.
The funder-grantee graph traverses in BOTH directions: filter by `funder_id` for everything a foundation funded, or by `recip_id` for every funder behind a nonprofit.
Start with `/api/v1/search` to turn an organization name into an EIN and a canonical page URL — it needs no key. The grant endpoints need a free key (50 calls a day); the organization endpoints (/compliance, /essentials, /premier) need a paid one.
**No CORS headers, deliberately.** This is a server-side API: a key in browser JavaScript is a key you have published, and every allowance here is per-key. Call it from your backend, or from an agent. There is no browser-callable subset planned.
Figures are read directly from named IRS filings. The IRS releases e-file data on a 12-24 month lag, so every figure is dated to its fiscal year rather than to today. Funding relationships are reported as association, never as causation. Organizations that do not e-file may be absent.'
version: 1.0.0
contact:
name: Plinth
url: https://data.useplinth.com/developers
license:
name: 'Source data: IRS public domain'
url: https://www.irs.gov/charities-non-profits/tax-exempt-organization-search-bulk-data-downloads
termsOfService: https://data.useplinth.com/terms
servers:
- url: https://data.useplinth.com
description: Plinth US philanthropy data
security:
- ApiKeyAuth: []
tags:
- name: Batch
paths:
/api/v1/batch/fields:
get:
summary: The field vocabulary POST /batch answers, with the plan each needs
description: Keyless. The same list the Sheets and Excel functions offer as autocomplete, read from one configuration file so the client and the server cannot disagree about a field.
operationId: listBatchFields
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
tags:
- Batch
security: []
/api/v1/batch:
post:
summary: Look up one field for each of up to 500 organizations in one call
operationId: batchLookup
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BatchRequest'
required: true
responses:
'200':
description: One result per request row, in order.
content:
application/json:
schema:
$ref: '#/components/schemas/BatchResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
tags:
- Batch
description: The spreadsheet endpoint. Each row names an organization (an EIN, or a name to resolve) and ONE field from the published vocabulary (`GET /api/v1/batch/fields`); the values come back in the same order, each with a status that says whether it is an answer, an unresolved name, a field the key's plan does not reach, or an error. Costs one call per 50 rows rather than one per row, charged against the same allowance as every other endpoint. Rows the request cannot pay for are refused as a whole before any work is done, with the number of rows the balance does cover.
components:
schemas:
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
BatchResponse:
properties:
code:
type: integer
title: Code
default: 200
message:
type: string
title: Message
x_plinth:
anyOf:
- $ref: '#/components/schemas/XPlinth'
- type: 'null'
results_count:
type: integer
title: Results Count
calls_charged:
type: integer
title: Calls Charged
description: 'Calls this request cost: one per 50 rows.'
results:
items:
$ref: '#/components/schemas/BatchResult'
type: array
title: Results
type: object
required:
- message
- results_count
- calls_charged
- results
title: BatchResponse
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
input:
title: Input
ctx:
type: object
title: Context
type: object
required:
- loc
- msg
- type
title: ValidationError
ApiError:
type: object
description: Every failure answers with this envelope; `message` is safe to show a user.
properties:
code:
type: integer
message:
type: string
required:
- code
- message
ResponseWarning:
properties:
parameter:
type: string
title: Parameter
severity:
type: string
title: Severity
description: '`caveat` informs; anything else means a filter did NOT apply.'
reason:
type: string
title: Reason
type: object
required:
- parameter
- severity
- reason
title: ResponseWarning
description: A parameter we recognized but could not honor, named rather than silently dropped.
BatchRequest:
properties:
rows:
items:
$ref: '#/components/schemas/BatchRow'
type: array
title: Rows
description: Up to 500 rows, answered in order.
type: object
required:
- rows
title: BatchRequest
ExcludedClass:
properties:
grants:
anyOf:
- type: integer
- type: 'null'
title: Grants
description: Rows dropped. Null when the cost could not be measured — see `unavailable`.
dollars:
anyOf:
- type: integer
- type: 'null'
title: Dollars
description: Dollars dropped. Null when the cost could not be measured.
definition:
anyOf:
- type: string
- type: 'null'
title: Definition
description: What this class means, single-sourced from config/candid-compat.json.
unavailable:
anyOf:
- type: string
- type: 'null'
title: Unavailable
description: Present only when the accounting failed. The totals in the response are unaffected — a failed measurement never fails a correct answer.
type: object
title: ExcludedClass
description: What one `?exclude=` class cost, measured against the caller's own filter.
BatchRow:
properties:
ein:
anyOf:
- type: string
- type: 'null'
title: Ein
description: EIN in any format (131837418 or 13-1837418). Either this or `name`.
examples:
- 13-1837418
name:
anyOf:
- type: string
- type: 'null'
title: Name
description: Organization name to resolve when no EIN is known. Matched exactly after normalizing case and punctuation; a name several organizations share answers `ambiguous` with the candidates, and `state` narrows it.
state:
anyOf:
- type: string
- type: 'null'
title: State
description: Two-letter state, used only to narrow a `name` that matches in several states.
field:
type: string
title: Field
description: One key from GET /batch/fields.
examples:
- grants_made_total
year:
anyOf:
- type: integer
- type: 'null'
title: Year
description: Filing year, for the grant-graph fields. Ignored, with a note, on fields that read the latest filing.
to:
anyOf:
- type: string
- type: 'null'
title: To
description: 'Second EIN, for the pair fields (grants_to_*): the recipient, where the row''s organization is the funder.'
type: object
required:
- field
title: BatchRow
DatasetStamp:
properties:
source:
anyOf:
- type: string
- type: 'null'
title: Source
refresh:
anyOf:
- type: string
- type: 'null'
title: Refresh
polled:
anyOf:
- type: string
- type: 'null'
title: Polled
description: How often we LOOK for a new IRS release, as distinct from how often the IRS publishes one.
reporting_lag:
anyOf:
- type: string
- type: 'null'
title: Reporting Lag
description: Why a figure is dated to a fiscal year rather than to today.
basis:
anyOf:
- type: string
- type: 'null'
title: Basis
description: Grants PAID versus grants approved — the reconciliation that catches people comparing a total here to a funder's own published giving figure. Read it before filing a discrepancy.
version:
anyOf:
- type: string
- type: 'null'
title: Version
description: Opaque build stamp of the data slice. Changes only when the data does.
generated_at:
anyOf:
- type: string
- type: 'null'
title: Generated At
description: ISO 8601 timestamp the slice was built.
license:
anyOf:
- type: string
- type: 'null'
title: License
description: Licence the source data is published under, where it carries one. Absent for public-domain sources.
attribution:
anyOf:
- type: string
- type: 'null'
title: Attribution
description: Attribution the source licence obliges you to reproduce. Present it verbatim wherever you show figures from this jurisdiction.
currency:
anyOf:
- type: string
- type: 'null'
title: Currency
description: Currency of every amount in the response, when it is not US dollars. Amounts are never converted — they are the filing's own figures.
type: object
title: DatasetStamp
description: Provenance of the slice that answered. `version` is the sync signal — poll it, re-pull when it moves.
Unidentified:
properties:
grants:
anyOf:
- type: integer
- type: 'null'
title: Grants
dollars:
anyOf:
- type: integer
- type: 'null'
title: Dollars
note:
anyOf:
- type: string
- type: 'null'
title: Note
unavailable:
anyOf:
- type: string
- type: 'null'
title: Unavailable
description: Present only when the size could not be computed. The ranking above is unaffected.
type: object
title: Unidentified
description: Grants held OUT of a recipient ranking because their recipient is not an organization.
BatchResult:
properties:
i:
type: integer
title: I
description: Index of the request row this answers. Results are in request order, but a client should key on this rather than on position.
ein:
anyOf:
- type: string
- type: 'null'
title: Ein
description: The EIN the row resolved to, when it did.
name:
anyOf:
- type: string
- type: 'null'
title: Name
description: The organization's name, when the row resolved.
field:
type: string
title: Field
year:
anyOf:
- type: integer
- type: 'null'
title: Year
value:
title: Value
description: The answer. Null whenever `status` is not `ok`, and sometimes when it is (a filer with no revenue line).
status:
type: string
title: Status
description: ok · not_found · unresolved · ambiguous · upgrade · capped · unknown_field · invalid · error. Anything but `ok` has a `note`.
note:
anyOf:
- type: string
- type: 'null'
title: Note
candidates:
anyOf:
- items:
additionalProperties: true
type: object
type: array
- type: 'null'
title: Candidates
description: 'On `ambiguous`: the organizations the name matched, each with ein, name and state.'
type: object
required:
- i
- field
- status
title: BatchResult
XPlinth:
properties:
warnings:
anyOf:
- items:
$ref: '#/components/schemas/ResponseWarning'
type: array
- type: 'null'
title: Warnings
unsupported:
anyOf:
- items:
type: string
type: array
- type: 'null'
title: Unsupported
description: Parameters that did not apply. Non-empty means the answer is not the question asked.
dataset:
anyOf:
- $ref: '#/components/schemas/DatasetStamp'
- type: 'null'
datasets:
anyOf:
- items:
$ref: '#/components/schemas/DatasetStamp'
type: array
- type: 'null'
title: Datasets
description: 'Every corpus this response could have drawn on, each with its own licence terms. /sql only: you write the query, so which corpus answered is not knowable per response — honour every licence listed.'
excluded:
anyOf:
- additionalProperties:
anyOf:
- $ref: '#/components/schemas/ExcludedClass'
- type: boolean
type: object
- type: 'null'
title: Excluded
description: Per-class cost of `?exclude=`, keyed by class name, plus `overlapping` — true when classes were counted independently and therefore sum to more than the whole. /grants/summary only.
unidentified:
anyOf:
- $ref: '#/components/schemas/Unidentified'
- type: 'null'
description: 'On /grants/recipients: the grants held out of the ranking because their recipient is a person, a class of people, or nobody the filer named. Add these back to reconcile with /grants/summary.'
type: object
title: XPlinth
description: Non-Candid metadata. Safe to ignore for compatibility; `unsupported` is the one to assert on.
responses:
PaymentRequired:
description: The call allowance is spent, the account is inactive, or the endpoint needs a paid plan — the message says which, and names the period it resets on (daily on free and non-profit plans, monthly on Pro).
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
example:
code: 402
message: API limit of 50 calls a day reached.
Unauthorized:
description: No API key, or a key we do not recognize.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
example:
code: 401
message: missing or invalid API key
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: Free key includes 50 calls a day across /grants/*; the organization endpoints and SQL need a paid plan. Get one at https://data.useplinth.com/pricing.
externalDocs:
description: Developer documentation
url: https://data.useplinth.com/developers