---
title: "Run a read-only SQL query over the warehouse"
method: POST
path: "/api/v1/sql"
tags: ["SQL"]
---

# Run a read-only SQL query over the warehouse

`POST /api/v1/sql`

## Request

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

Query the grants warehouse directly when the fixed endpoints can't express the question.

Accepts ONE read-only statement: `SELECT` or `WITH ... SELECT`. Multiple statements, any DDL/DML, and
the file-reading functions (`read_parquet`, `read_csv`, …) are rejected — you query named views, not
files. The view list, every column, and the caveats that decide whether an answer is right are
documented at https://data.useplinth.com/developers/schema.

`grants` is the US corpus in US dollars; Canadian T3010 filings are a separate view, `grants_ca`, in
Canadian dollars. Neither is a filter on the other, because a total spanning both adds CAD to USD.

Two ceilings, and they bound different things. Results are capped at 2,000 rows
(`truncated: true` says you hit it) — aggregate in SQL rather than paging. Separately, a query is
cancelled after 30 seconds: the row cap limits the response, this limits the scan behind
it. A cancelled query returns 400, so narrow it with a `tax_year` or `funder_ein` filter and retry.

Every response carries `x_plinth.datasets`: the licence and required attribution for each corpus you
can reach here. Which one answered is not knowable per query, so all of them are listed. The Canadian
attribution is a condition of the licence — reproduce it wherever you publish figures drawn from it.

Requires a paid key — there is no free SQL tier — and costs one call from your monthly allowance, the
same as any other endpoint. Asset-intelligence and governance tables additionally need Pro.

## Request body

- object
  - `sql` string, required: A single read-only SELECT (or WITH ... SELECT) statement.

## Response `200`

The rows the query returned.

- SqlResponse: `truncated` is the one to check: true means the row cap cut the answer, so aggregate in SQL.
  - `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.
  - `rows` object[], required
  - `row_count` integer, required
  - `truncated` boolean, required

## 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).

---

[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)
