---
title: "Search organizations (Candid Essentials-shaped)"
method: POST
path: "/api/v1/essentials"
tags: ["Organizations"]
---

# Search organizations (Candid Essentials-shaped)

`POST /api/v1/essentials`

## Request

- Base URL: `https://data.useplinth.com`
- URL: `https://data.useplinth.com/api/v1/essentials`
- Auth: API key in header `X-API-Key`

Nonprofit SEARCH, in the POST-with-a-body shape a Candid Essentials v4 client already builds: `search_terms`, `from`, `size`, `filters`, `sort`. This is the endpoint that corresponds to Candid's `/essentials/v4` — `GET /api/essentials/{ein}` is the single-record lookup, and mapping a search API onto it is the migration mistake worth avoiding.

Use `jurisdiction=ca` for Canadian organizations; the default is `us`. Financial filters use the filing currency, identified in `x_plinth.dataset`; currencies are never combined. Filters cover geography, IRS classification and financial ranges, all read from the organization's own filing. Candid's PCS subject, profile-participation and demographic filters have no counterpart; send one and it comes back named in `x_plinth.warnings` rather than being dropped.

## Query parameters

- `jurisdiction` string, nullable

## Request body

- EssentialsSearch: Candid Essentials v4 speaks POST-with-a-body, and that is the shape a migrating client already builds. `search_terms`, `from`, `size`, `filters` and `sort` therefore keep their Candid names and meanings; the FILTER VOCABULARY inside `filters` is ours, because Candid's is built on PCS and on profile data we do not hold (see config/candid-compat.json → essentialsFilters). `filters` is a free-form object rather than a nested model on purpose: an unknown filter key is REPORTED in x_plinth.warnings, and a strict schema would instead 422 the whole request and tell the caller nothing about which of their filters we understood.
  - `search_terms` string, nullable: Organization name or keyword.
  - `from` integer, nullable: 0-based offset, as Candid.
  - `size` integer, nullable: Page size. Default 25, capped at 1000 (Candid's ceiling is 25).
  - `mode` string, nullable: text (default) | semantic. `semantic` matches on mission meaning rather than name, where the embedding store is staged.
  - `filters` object, nullable: See the Essentials filter matrix. Unknown keys are reported, not ignored.
  - `sort` union: {"field": "total_revenue", "order": "desc"} — or the field name alone. Sortable: total_revenue, total_expenses, net_assets, grants_paid, name.
    - object
    - string
  - `strict` boolean, nullable: Reject the request if any filter could not be honored.

## Response `200`

Organizations matching the search.

- EssentialsSearchResponse: Paged with `from`/`size` — Candid's essentials shape, NOT the `page`/`limit` of /grants/*.
  - `code` integer
  - `message` string, required
  - `x_plinth` XPlinth: Non-Candid metadata. Safe to ignore for compatibility; `unsupported` is the one to assert on.
    - `warnings` ResponseWarning[], nullable
      - `parameter` string, required
      - `severity` string, required: `caveat` informs; anything else means a filter did NOT apply.
      - `reason` string, required
    - `unsupported` string[], nullable: Parameters that did not apply. Non-empty means the answer is not the question asked.
    - `dataset` DatasetStamp: Provenance of the slice that answered. `version` is the sync signal — poll it, re-pull when it moves.
      - `source` string, nullable
      - `refresh` string, nullable
      - `polled` string, nullable: How often we LOOK for a new IRS release, as distinct from how often the IRS publishes one.
      - `reporting_lag` string, nullable: Why a figure is dated to a fiscal year rather than to today.
      - `basis` string, nullable: 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` string, nullable: Opaque build stamp of the data slice. Changes only when the data does.
      - `generated_at` string, nullable: ISO 8601 timestamp the slice was built.
      - `license` string, nullable: Licence the source data is published under, where it carries one. Absent for public-domain sources.
      - `attribution` string, nullable: Attribution the source licence obliges you to reproduce. Present it verbatim wherever you show figures from this jurisdiction.
      - `currency` string, nullable: Currency of every amount in the response, when it is not US dollars. Amounts are never converted — they are the filing's own figures.
    - `datasets` DatasetStamp[], nullable: 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.
      - `source` string, nullable
      - `refresh` string, nullable
      - `polled` string, nullable: How often we LOOK for a new IRS release, as distinct from how often the IRS publishes one.
      - `reporting_lag` string, nullable: Why a figure is dated to a fiscal year rather than to today.
      - `basis` string, nullable: 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` string, nullable: Opaque build stamp of the data slice. Changes only when the data does.
      - `generated_at` string, nullable: ISO 8601 timestamp the slice was built.
      - `license` string, nullable: Licence the source data is published under, where it carries one. Absent for public-domain sources.
      - `attribution` string, nullable: Attribution the source licence obliges you to reproduce. Present it verbatim wherever you show figures from this jurisdiction.
      - `currency` string, nullable: Currency of every amount in the response, when it is not US dollars. Amounts are never converted — they are the filing's own figures.
    - `excluded` object, nullable: 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` Unidentified: Grants held OUT of a recipient ranking because their recipient is not an organization.
      - `grants` integer, nullable
      - `dollars` integer, nullable
      - `note` string, nullable
      - `unavailable` string, nullable: Present only when the size could not be computed. The ranking above is unaffected.
  - `hits` integer, required
  - `size` integer, required
  - `results_count` integer, required
  - `mode` string, nullable
  - `results` object[], required
  - `from` integer, required: Zero-based offset of the first row returned.

## Other responses

- `401`: No API key, or a key we do not recognize.
- `402`: 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).
- `422`: Validation Error

---

[API](https://skmtc.dev/useplinth/apis/plinth-grants-api.md) · [All operations](https://skmtc.dev/useplinth/apis/plinth-grants-api/llms.txt) · [OpenAPI document](https://skmtc.dev/useplinth/apis/plinth-grants-api/revisions/15273cd72ecf?raw)
