Plinth US Grants Data Batch API

Bulk lookup of many organizations in one request, with a discoverable list of selectable fields.

GitHub OpenAPI

Operations 2

0 of 2 operations are conformant: apis.io called them with no credentials and the responses matched this contract; 1 more answered but could not be checked against a schema. Checked 2026-11-12. how this works · status file

GET /api/v1/batch/fields The field vocabulary POST /batch answers, with the plan each needs observed #
POST /api/v1/batch Look up one field for each of up to 500 organizations in one call #

Work with this as data

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 Specification

plinth-us-grants-data-batch-api-openapi.yml Raw ↑
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