---
name: opengrants-api
description: Query the OpenGrants REST API for grants, government contracts, and funders. Use when you need programmatic access to US public funding data, or when an OpenGrants API call is returning 401 and you need to tell a gateway rejection apart from a bad key.
---

# OpenGrants REST API

Base URL: `https://ops.opengrants.io/functions/v1`
Specification: <https://ops.opengrants.io/openapi.json> (OpenAPI 3.0.3)
Documentation: <https://ops.opengrants.io/api-docs>

## Authentication

Every endpoint takes an OpenGrants API key as a bearer token:

```
Authorization: Bearer <your-40-character-key>
```

Keys are minted in the Developer portal at <https://ops.opengrants.io/app/developer> and require
an active Pro or Developer subscription. The key is an opaque string, **not** a JWT.

## Endpoints

| Method | Path | Returns |
| --- | --- | --- |
| GET | `/grants-api` | Paged list of grants |
| GET | `/grants-api/{id}` | One grant |
| GET | `/contracts-api` | Paged list of government contracts |
| GET | `/contracts-api/{id}` | One contract |
| GET | `/funders-api` | Paged list of funding organizations |
| GET | `/funders-api/{id}` | One funder, with its opportunities |
| POST | `/match-grants-api` | Grants ranked against an organization profile |

### Useful query parameters on the list endpoints

- `search` — free text. Pair with `search_mode=hybrid` to get vector relevance rather than
  keyword-only ranking.
- `states` — comma-separated USPS codes, e.g. `CA,NY`. Returns state-specific opportunities only
  unless you also pass `include_national=true`. Forgetting that flag is the most common reason a
  state query looks empty.
- `opportunity_type` — `open` (default) returns applyable opportunities. `awarded` returns
  previously funded projects for research, which have no deadline. `all` returns both.
- `min_amount` / `max_amount`, `deadline_after` / `deadline_before`, `category`.

### Matching

`POST /match-grants-api` takes `{ profile: { mission, sector, geography, ... }, focus_keywords[],
limit }`. `profile.mission` is required — it is embedded and used for semantic ranking. Scoring
also uses `annual_budget_usd`, `applicant_type`, and `is_501c3` when supplied. `naics_code` and
`certifications` are accepted but do not currently affect grant ranking.

## Triaging a 401 — read the response body

Two completely different failures both surface as 401. The body tells you which:

- `{"code":"UNAUTHORIZED_INVALID_JWT_FORMAT","message":"Invalid JWT"}` — the **gateway** rejected
  the request before OpenGrants ever validated the key. The endpoint you called expects a session
  JWT, and your API key is not one. **Your key is fine; the endpoint is wrong.** Only the seven
  endpoints listed above accept an API key.
- `{"error":...,"code":"INVALID_API_KEY","request_id":...}` — OpenGrants' own validator. **This one
  means the key is actually bad, inactive, or the subscription lapsed.**

If you get a 403 with `NO_API_ACCESS`, the key is valid but the subscription tier does not include
API access.

## Rate limits

Responses carry rate-limit headers, and exhaustion returns 429. Back off on 429 rather than
retrying immediately; the limit is per key.

## Related

- MCP server, if you would rather have tools than HTTP: see the `opengrants-mcp` skill.
- End-to-end workflow: see the `find-funding` skill.
