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-credentials-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 Credentials 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: Credentials
description: Getting a key, including the one an agent can mint for itself.
paths:
/api/v1/agent-key:
get:
tags:
- Credentials
operationId: describeAgentKey
security: []
summary: How to mint a trial key by signing a request
description: Returns the signing requirements, the tier ladder and a worked example request. No key, no account, no signature needed to read this.
responses:
'200':
description: 'How to get a key: the keyless challenge route, the signing alternative, and the tier ladder.'
content:
application/json:
schema:
type: object
description: Human- and machine-readable instructions. `what_this_is` leads with the route that needs no credential; `signing_instead` is the higher-tier alternative; `tiers` is the full ladder.
post:
tags:
- Credentials
operationId: mintAgentKey
security: []
x-plinth-auth: none (challenge) | rfc9421-web-bot-auth (higher tiers)
summary: Mint a trial API key — no account, no host, no signature
description: 'Mints a short-lived, read-only evaluation key. No account, no card, no browser, and nothing created in anyone else''s name.
**The ordinary route needs no credential of any kind.** POST here with `purpose` and `source` and you get a key immediately — but LOCKED: it may make only the calls its challenge names. Make them, read the fields the questions ask about, POST those values to `/api/v1/agent-key/answer`, and the key unlocks for general use. When that budget is spent, take another challenge. As often as you like, free.
Every fiscal year in the corpus is readable; what is small is the row cap and the per-challenge budget, both announced in `x_plinth.warnings` at `caveat` severity.
**Signing is the alternative, not the requirement.** A caller that can publish an Ed25519 key directory and sign per RFC 9421 (Web Bot Auth profile: cover `@authority` and `signature-agent`, tag `web-bot-auth`, with `created`, `expires`, a `nonce` and a `keyid` that is the base64url JWK SHA-256 thumbprint) is issued at a higher tier with a larger budget and no challenges. That allowance IS a total and never refills. GET this path for the full ladder.'
parameters:
- name: Signature-Agent
in: header
required: false
schema:
type: string
description: 'The https origin publishing your key directory, as a structured-field string: "https://your-host.example".'
- name: Signature-Input
in: header
required: false
schema:
type: string
description: RFC 9421 covered components and parameters, e.g. sig=("@authority" "signature-agent");created=…;expires=…;keyid="…";alg="ed25519";nonce="…";tag="web-bot-auth"
- name: Signature
in: header
required: false
schema:
type: string
description: 'sig=:<base64 ed25519 signature>: Send all three signature headers or none.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AgentKeyRequest'
responses:
'201':
description: A trial key, its expiry, its budget, and the challenge it arrives locked to.
content:
application/json:
schema:
$ref: '#/components/schemas/TrialKey'
'400':
description: '`purpose` or `source` missing or too short.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'401':
description: A signature was presented and did not verify, or was replayed. The body says which.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'429':
description: This signature-agent host has minted its allowance of trial keys today.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'502':
description: Your key directory could not be read — your signature may be fine.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'503':
description: Trial keys are at capacity; get a free key with an account instead.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
/api/v1/agent-key/challenge:
post:
tags:
- Credentials
operationId: takeChallenge
security: []
x-plinth-auth: trial-key
summary: Take another challenge on a key you hold
description: A trial key whose grant is spent asks for three new calls, is locked to them, answers, and is unlocked again — as often as needed. Replacing an outstanding challenge is allowed; it grants extra calls only if the previous questions turned out to be unanswerable from our side too.
responses:
'200':
description: Three calls this key may now make, and where to answer.
content:
application/json:
schema:
$ref: '#/components/schemas/ChallengeIssued'
'401':
description: No key, or not a live trial key.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
get:
tags:
- Credentials
operationId: describeChallenge
security: []
summary: How the challenge loop works
description: Readable without a key, so an agent that arrives here learns the shape rather than meeting a 401.
responses:
'200':
description: The loop, in order.
content:
application/json:
schema:
type: object
description: How the mint / answer / re-challenge cycle fits together.
/api/v1/agent-key/answer:
post:
tags:
- Credentials
operationId: answerChallenge
security: []
x-plinth-auth: trial-key
summary: Answer a challenge and unlock the key
description: Send the values of the fields the questions named. We re-read those calls to check, so nothing is stored at issue time and nothing can drift from the data it described.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AnswerRequest'
responses:
'200':
description: Unlocked, with the general call budget added.
content:
application/json:
schema:
$ref: '#/components/schemas/UnlockResult'
'400':
description: 'Wrong answers, or the wrong number of them. Which one was wrong is deliberately not said. The last attempt on a challenge REPLACES it: `error: challenge_replaced`, with fresh questions in the same body.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'401':
description: No key, or not a live trial key.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'409':
description: That key has no challenge outstanding.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'429':
description: Too soon after a wrong answer. `Retry-After` and `retry_after_seconds` say how long; the attempt was neither counted nor checked.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
components:
schemas:
TrialKey:
type: object
properties:
code:
type: integer
message:
type: string
key:
type: string
description: The credential. Send as X-API-Key.
key_prefix:
type: string
plan:
type: string
tier:
type: string
tier_label:
type: string
expires_at:
type: string
format: date-time
calls_ceiling:
type: integer
calls_used:
type: integer
calls_remaining:
type: integer
budget_is:
type: string
max_limit:
type: integer
max_page:
type:
- integer
- 'null'
scope:
type: string
challenge:
type: object
description: Present when the key arrives LOCKED. Until these are answered the key may make only the calls named here.
properties:
questions:
type: array
items:
$ref: '#/components/schemas/ChallengeQuestion'
answer_at:
type: string
attempts:
type: string
description: How many answers this challenge takes and how they are spaced. Running out draws a FRESH challenge rather than locking the key.
terms:
type: object
description: What is enforced, and what we ask of you.
required:
- code
- key
- tier
- expires_at
AgentKeyRequest:
type: object
required:
- purpose
- source
properties:
purpose:
type: string
minLength: 10
description: What you are evaluating. Recorded, never verified, never gates — but required.
examples:
- Evaluating grant-graph coverage for a funder-research tool
source:
type: string
description: 'Who or what is calling: an agent name, product or host. Recorded, unverified, required.'
examples:
- acme-research-agent/1.0
contact:
type: string
description: Optional. A URL or address we can reach you at.
AnswerRequest:
type: object
required:
- answers
properties:
answers:
type: array
items:
type: string
description: One value per question, in the order given. Compared case-insensitively; "yes"/"true" and "$1,234"/"1234" are equivalent.
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
ChallengeIssued:
type: object
properties:
code:
type: integer
message:
type: string
questions:
type: array
items:
$ref: '#/components/schemas/ChallengeQuestion'
answer_at:
type: string
attempts:
type: string
description: How many answers this challenge takes and how they are spaced. Running out draws a FRESH challenge rather than locking the key.
required:
- code
- questions
- answer_at
ChallengeQuestion:
type: object
properties:
call:
type: string
description: The exact request to make, method and URL.
ask:
type: string
description: What to read out of that response, in plain English.
required:
- call
- ask
UnlockResult:
type: object
properties:
code:
type: integer
message:
type: string
calls_remaining:
type: integer
expires_at:
type: string
format: date-time
when_spent:
type: object
terms:
type: object
required:
- code
- calls_remaining
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